Unit Testing Best Practices: JUnit Reference Guide
Before writing this post, I am assuming that you know the basics of JUnit. If you do not have the basic knowledge, first read this link. Further, in this post, I will write the best practices you must consider before writing your test cases.
“It’s overwhelmingly easy to write bad unit tests that add very little value to a project while inflating the cost of code changes astronomically.”
Sections in this post:
Unit testing is not about finding bugs Tips for writing great unit tests Test only one code unit at a time Don’t make unnecessary assertions Make each test independent to all the others Mock out all external services and state Don’t unit-test configuration settings Name your unit tests clearly and consistently Write tests for methods that have the fewest dependencies first, and work your way up All methods, regardless of visibility, should have appropriate unit tests Aim for each unit test method to perform exactly one assertion Create unit tests that target exceptions Use the most appropriate assertion methods. Put assertion parameters in the proper order Ensure that test code is separated from production code Do not print anything out in unit tests Do not use static members in a test class Do not write your own catch blocks that exist only to fail a test Do not rely on indirect testing Integrate Testcases with build script Do not skip unit tests Capture results using the XML formatter Conclusion
In programming, “Unit testing
is a method by which individual units of source code are tested to
determine if they are fit for use.” Now, this unit of source code can
very on different scenarios.
For example: in procedural programming a unit could be an entire module but is more commonly an individual function or procedure. In object-oriented programming a unit is often an entire interface, such as a class, but could be an individual method. Intuitively, one should view a unit as the smallest testable part of an application.
Unit testing is not about finding bugs
Well, its important to understand the motive behind unit testing. Unit tests are not an effective way to find bugs or detect regressions. Unit tests, by definition, examine each unit of your code separately. But when your application is run for real, all those units have to work together, and the whole is more complex and subtle than the sum of its independently-tested parts. Proving that components X and Y both work independently doesn’t prove that they’re compatible with one another or configured correctly.
So, if you’re trying to find bugs, it’s far more effective to actually run the whole application together as it will run in production, just like you naturally do when testing manually. If you automate this sort of testing in order to detect breakages when they happen in the future, it’s called integration testing and typically uses different techniques and technologies than unit testing.
“Essentially, Unit testing should be seen as part of design process, as it is in TDD (Test Driven Development)”. This should be used to support the design process such that designer can identify each smallest module in the system and test it separately.
Tips for writing great unit tests
Test only one code unit at a time
First of all and perhaps most important. When we try to test a unit of code, this unit can have multiple use cases. We should always test each use case in separate test case. For example, if we are writing test case for a function which is supposed to take two parameters and should return a value after doing some processing, then different use cases might be:
- First parameter can be null. It should throw Invalid parameter exception.
- Second parameter can be null. It should throw Invalid parameter exception.
- Both can be null. It should throw Invalid parameter exception.
- Finally, test the valid output of function. It should return valid pre-determined output.
This helps when you do some code changes or do refactoring then to test that functionality has not broken, running the test cases should be enough. Also, if you change any behavior then you need to change single or least number of test cases.
Don’t make unnecessary assertions
Remember, unit tests are a design specification of how a certain behavior should work, not a list of observations of everything the code happens to do.
Do not try to Assert everything just focus on what you are testing otherwise you will end up having multiple testcases failures for a single reason, which does not help in achieving anything.
Make each test independent to all the others
Do not make chain of unit test cases. It will prevent you to identify the root cause of test case failures and you will have to debug the code. Also, it creates dependency, means if you have to change one test case then you need to make changes in multiple testcases unnecessarily.
Try to use @Before and @After methods to setup per-requisites if any for all your test cases. If you need to multiple things to support different test cases in @Before or @After, then consider creating new Test class.
Mock out all external services and state
Otherwise, behavior in those external services overlaps multiple tests, and state data means that different unit tests can influence each other’s outcome. You’ve definitely taken a wrong turn if you have to run your tests in a specific order, or if they only work when your database or network connection is active.
Also, this is important because you would not love to debug the test cases which are actually failing due to bugs in some external system.
(By the way, sometimes your architecture might mean your code touches static variables during unit tests. Avoid this if you can, but if you can’t, at least make sure each test resets the relevant statics to a known state before it runs.)
Don’t unit-test configuration settings
By definition, your configuration settings aren’t part of any unit of code (that’s why you extracted the setting out in some properties file). Even if you could write a unit test that inspects your configuration, then write only single or two test cases for verifying that configuration loading code is working and that’s all.
Testing all your configuration settings in each separate test cases proves only one thing: “You know how to copy and paste.”
Happy Learning !!