Unit Testing Principles, Practices, and Patterns with Vladimir Khorikov
8 Dec 2025 · 29 min
Vladimir Khorikov discusses his book on unit testing, outlining four pillars of good tests: protection against regressions, resistance to refactoring, fast feedback, and maintainability. He explains when to use mocks (for out-of-process dependencies, not in-process ones) and why you shouldn't test private methods directly.
Chapters
Five, four, three, two, one.
Drop that.
Welcome to the Test Guild Automation Podcast. We're all getting together to learn more about software testing and automation with your host, Joe Calantonio.
Hey, it's Joe. And welcome to another episode of the Test Guild Automation Podcast. Today we'll be talking with Vladimir Korakov all about his new book, Unit Testing Principles, Practices and Patterns. Vladimir is a software engineer and author of several popular pluralsight courses. He's been professionally involved in software development for over 15 years, including mentoring teams on the ins and outs of unit testing. He's also the founder of the Enterprise Craftsmanship Blog where he started as an advisor on general programming topics. But he's lately shifted his focus to unit testing, with the central message of teaching software developers how to make unit testing painless. And if you're a hardcore Test Guild Podcast listener, you probably remember Vladimir from episode 77 all the way back in 2015. I can't believe it's been that long since we've spoken with him, so I'm really excited to have him back. You don't want to miss this episode. Check it out.
The Test Guild Automation Podcast is sponsored by the fantastic folks at Sauce Labs. Their cloud based test platform helps ensure your favorite mobile apps and websites work flawlessly on every browser, operating system device. Get a free trial. Visit testguild.com sourcelabs and click on the exclusive sponsor section to try it for free for 14 days. Check it out.
Hey Vladimir, welcome back to the show.
Hey Joe, thanks for having me again.
Great to have you. It's been quite a while. Is there anything I missed in your bio that you've done since. Since last we spoke?
No, that's basically it. I started teaching general principles and practices of software development and lately I've been shifting more towards unit testing. So yeah, that's spot on.
So I always ask this about authors and just curious to know. It takes a lot of time to write a book. Why did you decide to write a book, especially on unit testing, which to me seems like there's a lot of information already out there on unit testing.
Indeed, there is a lot of material already. And the primary reason why I decided to write a book is because a lot of existing books and a lot of material online, they target primarily beginners. And if you already mastered all the foundations of unit testing and maybe have a year or two experience and want to advance further, there's not much you can do to do that. There's not much of material online that will help you do that. And with this book I tried to fill this gap. I tried to provide a clear guidance for people who already have some experience on how they can advance to the next level.
So is there a point where a beginner gets to a certain point and then there are other topics about unit testing that they may not even be aware of? Like what would be some of those things that you think once someone has the basics under their belt, they may not have known unit testing.
So by basics I mean that people tried to write unit tests and seen some maybe issues with those unit tests. And in the book I tried to.
Take an end to end approach and describe why we are doing unit testing and what the pillars of good unit testing are from first principles. So the book follows this progression where first you're reading about why you will need those unit tests and then what comprises a good unit test, and then all the principles and guidelines, they grow as implications from those from those pillars of good unit tests that are laid out in the beginning of the book. We can talk more about those principles.
Absolutely. So before we get into the pillars, how do you explain to someone then what is a unit testing? What is unit testing? What makes for good unit testing?
So first, why do you need unit testing? There are a lot of opinions on what the reason of why you need unit tests. And I think the main goal of unit testing is to enable sustainable growth of your software project. So you may hear some people saying that unit tests help you to improve code design. And while it's certainly true for some cases, I think the main goal is to just enable sustainable growth by reducing the number of bugs in your software. So when you write your project in the beginning, everything usually works great because there is not a lot of bugs code in your project yet. And so you pretty much can handle all your code base. You can just keep it in the memory because the code base is not large enough yet. But as the time, as the project grows.
You have to spend more and more time maintaining its quality because the larger the code base, the more surface area there is for potential bugs to sneak in. And so the need for unit testing, it grows with time as well.
And.
It'S not enough to just have unit tests, it's important to also have good unit tests. Because if you just write unit tests and don't spend time, don't pay attention to quality of those unit tests, you basically have the same situation as with a project with no unit tests at all. But it's just this point in time where your project starts to slow down, it's being pushed back A little bit.
So with good unit tests, you can pretty much sustain the project growth for a long time, where the speed of development keeps the same for the whole period of the project. Because your unit tests allow you to catch regressions early, they help you get feedback about the existing functionality.
So yeah, that's the difference between good and bad unit tests.
Awesome. It almost sounds like a good unit test would be like a safety net so developers would have confidence knowing if I refactored something, hey, I broke it. Because they trust their test, I guess.
Exactly, yes. And that's actually one of the main. Yeah, we're going into pillars of good unit testing already. So that's one of the main pillars of unit testing of a good unit test. I call it resistance to refactoring. And it's when you refactor your production code and unit tests. Well, actually there are two of them. First is protection against regressions, and second, resistance to refactoring. So protection against regressions help you reveal any issues that you might introduce in your code base. And protection against.
Resistance to refactoring helps you refactor your code and do it in a way that it doesn't trigger your tests. So your tests will not turn red without good reason. And we can talk more about the other pillars as well.
Absolutely. Before we get to the other pillars, I just thought of another concept. Just curious to get your thoughts on it. It seems like unit testing would be a no brainer, but I guess what I've seen is in large enterprises, a lot of times a developer may be dealing with a code base that's a legacy code that may not have unit tests. So do you ever recommend that a developer add unit tests to already existing code? Or is unit testing always an activity that's only done for new development? Does that make any sense?
Yeah, it does. So a project without tests is almost always a pain. And I've been there a lot of legacy projects that you might inherit from on your day job, they may not have good unit tests or they might not have unit tests at all. And in this situation, you need to evaluate what the trajectory of this project is. And if it's been actively developed, still.
You will have to introduce unit tests because without them, you will have to spend a lot of time on manual debugging on manual regression testing. And so, yeah, if the project is still being developed, if the project is still under development, you will have to slowly introduce unit testing to this project. And I can recommend another book on this topic. In my book, it wasn't the main focus.
The topic of legacy projects. But there is a great book from Michael Fathers working effectively with legacy code bases. I might butcher the name but it's basically something.
It's about legacy code basis. And he lays out step by step approach of how you should deal with legacy code bases that don't have tests yet. And in my experience the best way to deal with those code bases is just start with end to end tests and, and work your way through.
Lower code levels. The best approach wouldn't be necessarily unit testing, but something like end to end or integration testing. Because usually when you write code without tests, you structure it in a way that it's very hard to add unit tests later on, but it's still possible to add end to end tests. Something like UI tests or, or functional tests that don't deal with code specifics but test the project from the end user's perspective. And then when you have a good test suite consisting of those end to end tests, you can then start refactoring your code base and adding unit tests to it.
The book looks like it's made of four parts. The first one is the bigger picture, which I think we already went over. And the second part is making your tests work for you. And once again, the four pillars. Can we just talk a little bit more about the four pillars?
Absolutely, yeah. There are four pillars of unit testing, as I said. The first two are protection against regressions. The second is resistance to refactoring. There's also fast feedback and maintainability. And it's probably better to start with the last two because they are the easiest one ones and the first two we can discuss it, discuss them in detail later. So fast feedback is pretty much self explanatory. It's how fast your test runs because the faster your test run, the faster they provide you with feedback and the less time you will spend going in the wrong direction with your code. So, and if you. So the earlier you catch a bug, the easier it is to fix that bug. Because for example, a bug that you catch in production takes orders of magnitude more effort to fix than the bug that you introduced that you noticed as you write the code.
The maintainability metric is about how easy it is to understand and maintain your tests. So you can subdivide this metric into two submetrics and the first is, as I said, how easy it is to understand the test and basically a factor of how large the test is because larger the test the harder it is to read that test and understand. And also it's important to write tests in a way that preserves readability, because that's important factor as well. You should treat your tests as production code. You shouldn't cut corners when writing tests. And the second submetric of the overall maintainability metric is how hard it is to keep the dependencies of that test operational. So for example, if your test reaches out to out of process dependencies such as databases or a message bus, you will have to keep that database operational. You'll have to resolve any networking issues that might come up and that adds up to this maintainability metric. For that reason, unit tests that don't reach out to any out of practice dependencies, they are the most maintainable because you basically don't need to maintain any out of process dependencies. But tests like end to end tests or integration tests take more time to maintain. And the first two, again, the first one is protection against regressions. This is pretty self explanatory too.
It's a metric that tells you.
How likely the test to catch regression in your code in case you introduce that regression. This metric is the factor of the amount of code that gets executed by the test. So the larger this amount, the more code that gets executed, the more likely that the test will reveal a regression. And the second metric is protection against regression. And this one is the least understood among developers because what it does is it protects you from false positives from your tests. So for example, if you write a code, write some code, if you see that it's working and all the tests are running, and then decide to refactor it and your tests.
Start to fail and you are trying to figure out why they are failing and it turns out that you didn't do anything wrong. And it's just the tests were written in such a way that they will turn red with any change of the underlying code. That's called false positive, false alarm. And the metric of resistance to refactoring helps you avoid those false positives. This metric is the least understood because the importance in refactoring is not immediate. So when you start project, your code base is usually shiny, it's mostly flawless. And even if you have some false positives, they are not as important because you have your code base basically in mind.
It's fresh in mind and you can figure out which of the failures, which of the test failures is false positives and which is not. But as your project grows, the importance of refactoring grows with it. And the metric of resistance to refactoring becomes as important as protection against regressions because you don't want your tests to fail with no good reason because that will lead to losing trust to your test suite. A good metric to judge your tests by is by the signal to noise ratio. So the metric of protection against regressions provides you with signal, so it shows you any bugs you might introduce in your code. And the metric of resistance to refactoring shows you how good your tests are at reducing the noise. Both of these metrics are important because even if you have tests that can reveal any bug in your software, they are worthless if your tests produce a lot of noise because all those bugs, all those true positives will be lost in the sea of irrelevant information. That's basically the four pillars of a good unit test.
A lot of the issues you describe are the same issues people usually describe, but functional automation that causes people to lose trust in their test because they stop failing randomly because they're flaky or whatever.
Exactly, yes. And I personally separate flakiness from this metric of resistance to refactoring because both are basically false positives. But flaky tests, they produce false positives because of maintainability issues, because your database is not available, or because of network issues. But false positives that appear because of refactoring, I categorized them into this second category because the reason for those false positives is different.
So what are some ways people can approach unit testing to help avoid some of these brittle type tests? Is mocks usually the answer? When you can create a unit test that's truly independent, that you can use a mock maybe to help get rid of maybe some dependencies that sometimes causes issues.
Mocks is an interesting topic, and it's a bit of a controversial topic really. At least it was a couple of years ago. And when I wrote this book, I was expecting the topic of mocks to be the most controversial in this book. But judging by the feedback so far, I wouldn't say that people deemed this topic in the book as controversial. I even got feedback like I finally crystallized for myself when and why to use mocks. So why it is controversial? There is basically two schools of thought in unit testing. One is for MUX, for using MOCs for almost any dependencies except for immutable dependencies. And the other school of thought is for using mocks for as little as few dependencies as possible. And the second school of thought is the classical school of unit testing. And the first one is the London School of Unit Testing. And with the book, I also wanted to put an end to this argument and provide a clear Guidance of where you should use MUX and where you shouldn't. So what is it? Let me actually back up a little bit, because to describe where you actually should use mux, I first need to describe the topic of dependencies. All dependencies in your application can be categorized in process dependencies and out of process dependencies. In process dependencies is any dependency that reside in the same process as your production code. So for example, if you're testing some user class and this class has a dependency on another domain class, such as company, the user class would become a system under test, a class under test, and that company will become an in process dependency. Out of process dependencies are any dependency that reside outside of the main production code process, for example a database or a message bus, or anything like that. And you can further subdivide out of process dependencies into two subcategories, managed and unmanaged dependencies. And what managed dependencies are.
Let me first provide you with concrete, with exact definitions. So, managed dependencies are dependencies, interactions with which are not visible to the outside world. And then unmanaged dependencies are the opposite of that. When you interact with unmanaged dependencies interactions, those interactions become visible to the outside world. And by outside world I mean any external application. So for example, when your application sends an email or a message or puts a message on a message bus, that would be that email server, that SMTP server or the message bus will be an unmanaged dependency because its primary goal, its main goal is to enable communication between applications. So for example, an email will be received by the client software of the email receiver. That software will so that side effect, that email will be visible to other applications. And unmanaged dependencies are. So a usual good example of an unmanaged dependency is an application database that it's a database to which only your application has access to and no other applications can access that database. And so you should use MUX to replace unmanaged dependencies, but you shouldn't use mocks to replace managed dependencies. And this is important because.
The primary consequence of using a mock is that it sets in stone the communication pattern between the system under test and the dependency. For example, when you say that you expect this class to call that method on that other class on its dependency, if you change that communication pattern, for example, you are adding another parameter, or the system under test starts to call another method on that dependency, your tests will break, they will turn red saying that something went wrong and it's fine to set this communication pattern in stone for unmanaged dependencies, because you do want to preserve that communication pattern with other applications. For example, when your application sends a message, but puts a message to a message bus, you want this message to preserve its structure and all its parameters because you want to maintain backward compatibility. But for managed dependencies, you don't want to do that because how exactly your application.
Talks to your database, talks to your database. It's up to that application, the external clients and the external applications, they don't have a say into how this communication should be done. So they basically don't care about that. The only thing they care is the side effects of that communication. So the state of your system should remain the same and how exactly that state have been achieved. It's up to your application. So I hope that clarifies a little bit when you should and you shouldn't use mocks.
So once you understand the use of mocks in part three, you go over integration testing. Now integration, it might just be that it's me, but integration to me has always been like a gray area where the developers sometimes don't know that they're responsible for integration testing. What do you consider integration testing and why do you think it's important?
It's important because all developers at least once been in a situation where every unit test, every unit test passes, but the software is broken nonetheless. And that's a bad situation to be in. And a lot of the main reason why the situation comes up is because you didn't write integration tests or you didn't write them correctly. Integration tests are important because it's important to not only verify how your application works in isolation from other dependencies, from out of process dependencies, but to also see how it works with those out of process dependencies. So for example, when you do everything right in your domain model and all the logic is correct, but you don't properly save the results of that logic to the database, you do want to know that. You do want to cover that part with tests as well. And that's where integration tests come handy. They allow you to verify the integration between your application and those out of practice dependencies.
And the last piece of the book is part four, Unit Testing. Best practices. Folks should buy the book to find out all the best practices. But is there one best practice you find most helpful for your unit testing?
The most important best practice from that part is that you shouldn't test private methods.
You may hear a lot questions like how I should test private methods. And people even come up with libraries that help you to reveal those private methods and.
Enable testing of them. And that's a mistake, because private methods shouldn't be tested at all, at least directly. They should be tested indirectly through the public API of your application.
And testing private methods have the same effect as testing communications, as using mocks for managed dependencies. They reduce the application's resistance to refactoring because as soon as you modify those private methods, those internals, your tests will start failing. That's not a good situation because you don't want to. You don't want your tests to fail. Unless you modify the observable behavior of your application, the internal details may change freely, and you shouldn't tie your tests to those internal details.
So Vlad, this book is written by Manning publication. I think they were cool enough to give us some codes, so could you just talk about maybe how people can get their hands on one of these codes so they can get you a book at a discount?
Write a comment on this podcast page on Test Guild and I will choose five people randomly and will give a quote from Manning that will give you a free ebook.
Okay Vladimir, before we go, is there one piece of actual advice you would give someone to help them overall with the unit testing efforts and what's the best way to find Contact you and get a copy of the book Unit Testing Principles, Practices and Patterns.
The main advice I would give is start unit testing. Everything starts with practice and you will not be able to. You. You have to start somewhere. And the best way to start somewhere is to just start unit testing. And when you have some experience with unit testing, you will have all the same questions all people and myself included had about this subject. And that's where my book will start handy and you can find it and myself@enterprisecraftsmanship.com so that's the best place to find me and the book.
Thank you Vlad for your unit testing awesomeness. The links to everything of value we covered in this episode. Head on over to testguild.com a294 and while you're there, make sure to click on the Try it for Free Today link under the Exclusive Sponsor section to learn all about Source Labs awesome products and services. So that's it for this episode of the Test Guild Automation Podcast. I'm Joe and my mission is to help you succeed with creating end to end full stack Automation awesomeness. As always, test everything and keep the good. Cheers.
Thanks for listening to the Test Guild Automation Podcast podcast. Head on over to testguild.com for full show notes. Amazing blog articles and online testing conferences. Make sure to subscribe to join the Guild and continue your testing journey. This has been a Joe Calantonio production.
More from JoeBot
Automation Testing Podcast 2026: New Schedule, Events, Discounts with Joe Colantonio
Automation Guild Kickoff, AWS Nova, Karate v2, and AI Testing Reality and more TGNS177