Your Unit Tests Should Mind Their Own Business
Your Unit Tests Should Mind Their Own Business
Join the DZone community and get the full member experience.Join For Free
Atomist automates your software deliver experience. It's how modern teams deliver modern software.
As the unit testing debates continue on my project, I can't help but noticing that people are spending all sorts of time pontificating over the right way to unit test, without stepping back to consider what they're trying to achieve with unit testing. And because they don't know where they're going, they're not reaching any conclusions on how to get there. Sound familiar?
There are actually a number of fabulous reasons for doing unit testing:
1. They help us think about our design in a way that we might not otherwise.
2. They lead to cleaner code because it's often easier to re-factor our code to be more testable then it is to write unit tests that exercise obfuscated or otherwise hard to access code.
3. They validate that our modules are behaving correctly.
4. They provide a safety net to ensure our existing modules continue to function properly as our application evolves.
These aren't all intuitive and so a lot of people will only consider #3 when they think about unit testing. And yet, what's interesting is that #3 typically provides the smallest return on our unit testing investment because we tend to find WAY more defects through #1 and #2 as we work out how to develop our tests, and then of course in #4 as we continue to change our code over the application's lifetime. And, unfortunately, by focusing only on #3, it can lead us to make some faulty decisions when we try to reason about the best way to do unit tests.
One of the most common questions people ask is whether to unit test private methods. I struggle to find any reasons why we should, but I do know a lot of reasons why we should not. I'm not saying that there's never a reason to unit test private methods, I'm just saying to make sure you understand what that reason is before you go to all the effort.
In my opinion, the best way to achieve our unit test objectives is by validating our class's interfaces. And when I think about interfaces, I'm thinking about the public methods we expose to other modules in our application. If we can validate that each of these methods is behaving as expected, then I'm going to feel satisfied that the class works correctly. For the purposes of unit testing, I don't really care how the method achieves the result, as long as it's the right one. I don't care if the method figures out the result in-line, or by calling external methods or through internal private methods. If I call calculator.add( 2, 2 ) and get 4 then I'm happy. If I get 5, there's a problem.
In fact, if we're being good developers and remembering to re-factor our code as the application evolves, then there's a good chance that the method we use to determine that result is going to change over the application's lifetime. That gets back to reason #4 for having unit tests - they provide a safety net to ensure that our modules continue to function as we make changes to them. If those unit tests are dependent on how we provide results, then it means that every time we decide to change our class's inner workings, we have to update our unit tests. And if we have to rewrite our unit tests, then what is happening to our safety net?
"being able to quickly create, move around, and change the functionality of private methods is vital to remaining agile" -- Charles Miller, The Fishbowl
Okay, sure if we change our public interfaces, we have to update our unit tests. But that makes sense. It means our interface specifications have changed and so our tests better be updated accordingly. Changing our public interfaces should incur a significant cost because other code relies on them. In fact, one might even argue that having high costs (such as having to update our unit tests) provides a good reminder to us that it's in our best interest to keep our public interfaces stable.
On the other hand, the private parts of our implementations - those details that have been hidden behind our class's interface - should not be costly to change. If we give them the high cost of having to redo our unit tests then it puts up a barrier against refactoring that we really don't want. If we find that we're using the same logic in 3 different places in our class, we should be able to easily move that logic into a single private method and then simply re-run our unit tests as they already exist to make sure our class continues to function as expected. We shouldn't have to go write another unit test at this point because we haven't added any new functionality. We've simply refactored our existing code to be more maintainable.
"Pure" Unit Tests with Mock Objects
Another big question people ask about unit testing revolves around how pure our unit tests need to be. Pure unit testing requires isolating an individual unit to validate its correctness.
So, for example, if our class interacts with external servers, we might create a stub to take the place of that server (such as a dummy server that simply listens on a port and returns the expected response). Our unit test can then validate our class using the stub rather than the real server. This enables us to validate that our class is doing its part correctly without depending on the external server acting right.
But what about if our class interacts with other classes? How do we factor them out? A popular idea is to use mock objects. Say we want to unit test our Training class, which contains a method enroll( Student ). Rather than passing it a real Student object, we could create a MockStudent object that implements the same interface as Student. We then tell MockStudent what methods we expect to be called, such as getFirstName() and getLastName(), and what to return for them.
Once we call Training.enroll( MockStudent), our mock object validates that every method we expected to be called was, indeed, called. We even have the option of requiring that these methods are called in a specific order. If any of the expected methods were not called, or if any unexpected methods were called, our test fails.
Well, I really like the idea of mock objects - up until that last little bit. We seem to be back to the same concern that I have with unit testing private methods, and that is who cares which methods in Student are called, much less in what order? All I care about is that the student was successfully enrolled in the training. Maybe Training.enroll() was only using the student's first and last name to set a unique ID and the developer later realized that it's a bad idea to have data in our key, so updated the method to use a GUID instead. Should the Training.enroll() unit test now break because that method is no longer calling Student.getFirstName()? I don't think so. And, again, I think we're setting up unnecessary barriers to refactoring.
So, this isn't meant as a "never use mock objects" anymore than a "never test private methods." Just a recommendation to consider your unit testing objectives and what's going to best serve them before blindly diving in to the latest cool thing you just read on some schmo's blog.
Here are some great resources if you'd like to learn more:
» The Flawed Theory behind Unit Testing (Michael Feathers)
» Testing Private Methods with JUnit and SuiteRunner (great stuff, even if you're not using these tools)
» Mocks Aren't Stubs (Martin Fowler)
Reposted from: The Hacker Chick Blog
Opinions expressed by DZone contributors are their own.