There are a lot of articles and blog posts written about unit testing best practices, patterns etc. Over the course of years after writing many varieties of unit test cases with and without mocking frameworks and reviewing many tests, there are few patterns that I have noticed many times again and again. For a change instead of writing about best practices I have compiled a list of testing bad practices to avoid.
Ideally this could be part of code review rules that a team could check as part of team code reivews. The practices listed below can apply to not only unit tests but any type of tests like integration tests etc. Instead of writing what to do,I have compiled a list of things that should avoided when writing tests. Lets look at them in detail.
Best Bad practices
- Tests that test too little – We have all written tests like this at some point. Writing a test that tests only one flow through the public method is fine,as long as that is not the only test case. There has to be a fine balance between test coverage and test case size. As a general rule have atleast 3 tests for each public method, one happy path, one invalid path and one error handler condition. If the public method being tested is doing too many things then its most likely a good reason to refactor the code to break it down to more manageable units of work, thereby making it easier to write unit tests.
- Tests that test too much – This is like an addendum to the first rule. If one test is trying to test too much or the test method is quite big then its mostly likely the method being tested is doing too many things, and implies that the method should be refactored to more manageable units. More often than not, when the unit tests are written well following this rule, the code would be easier to read and maintain.
- Tests that don’t cover all possible scenarios – As mentioned earlier, each public method that is to be tested should have atleast 3 tests at a minimum each of the following scenarios.
– happy path
– non happy path
– error scenarios
These apply to about 90% of test cases. There may be some instances where error scenarios may not apply and thats fine to skip them at that point. Again these are the bare minimum.The more tests for each scenario, the less chances of new bugs creeping in your code.
- Tests that are not updated and added as per code changes – This is one of the most common issue I have seen with many teams. Its not that teams do not want to maintain the test case. Adding a test is sometimes, unfortunately, seen as extra work to get the build to pass. Test cases are part of the code. Even though they are testing the code that was written earlier, it is still used to validate and ensure that there is test coverage during the build process. Treat tests as part of the deliverable code, even though they would not be delivered to production. Whenever code is updated, ensure the tests are also up to date, however trivial that may seem. Your other developers would thank you.
- Tests that use PowerMock to mock methods that could otherwise be mocked with Mockito or EasyMock – PowerMock is a very powerful mocking framework, that allows you to mock private, static and other types of hard to test methods. I have seen code where all tests were written to run with PowerMock just because one or two tests require to mock private methods. There are lot of arguments about using PowerMock to mock normal methods and that is an overkill in some instances. This is a classic case of using a very powerful tool to do a trivial thing, like swatting a fly with a hammer. I have used PowerMock along with frameworks like Mockito and EasyMock. They coexist and work beautifully together and can make your life so much easier in the long run. There are certain use cases where PowerMock is absolutely necessary and saves the day for a developer. But most of the time they are few and far in between. If you have to write a test case for a private method then its most likely a candidate to be refactored to make it easier to maintain. Still there could be some instances where you are mocking a method in a library and PowerMock is the best candidate for that job
- Tests that take a long time to run – Some tests require a lot of setup and do take quite a while to run. Its totally understandable if its a performance test,using a tool like JMeter, or an integration test that is making calls to db to setup values etc. In case of a unit test case it should be pretty fast and should not take a long time to execute. If a unit test does take a long time then the test case or the underlying code being tested could be refactored to more manageable modules.
- Tests that use real resources – Unit tests should not depend on external resources for any part of the test. This is a very simple rule to follow, use Mocks to mock all dependencies or resources. Do not pass in actual resource references in a unit test case. For e.g. say there is a class that loads property files and provides getter methods to read values. Writing a test case for this class could use mock objects to test the actual getter methods.
- Tests that don’t use mock frameworks – Unit tests that test’s a method that has references to other classes or libraries should use mocking frameworks like Mockito, EasyMock or PowerMock. Using the correct framework depends on developer familiarity and specific use case. You can write a test that does not use any mock frameworks as well, and that’s totally fine. But if you find writing a lot of boilerplate code for the test case, then maybe you can look at leveraging some of these frameworks.
- Tests that change state – Similar to the previous rule, unit tests should not change any state as a result of test execution. Unit tests should be repeatable, and they should not affect any data or change state that would affect subsequent test execution.
- Tests that update a database or back-end – Again similar to the previous rule, unit tests should not change state of underlying data or prevent repeatability of tests. Simply put they should not update any values that the public method being tested could update in normal course of execution.
- Tests that require a lot of setup – Tests should be easy to setup and quick to run. When a unit test case requires a big setup or teardown method, then it could mean that the unit test case could be doing too many things. Most of the time the @Before or @After annotated method would initialize the object being tested or mock some of the dependencies that would be mocked for the tests.
The above mentioned rules are what not to do when writing test cases for your code, specifically unit test cases. These were based on what I have seen over the past 2 decades in the IT world. More to come on unit testing for Java.