Best Practices for great unit tests
- Test Only one code unit at a time
A unit of code usually has multiple use cases. We should always test each use case in a separate test case.
For example, if a function has multiple parameters, we must test each parameter separate. First parameter can be null one by one and 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.
- Mock out all external services and states[SE2]
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 that 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.)
- Name your unit tests clearly and consistently
E.g. Test case names should be like:
1) TestCreateEmployee_NullId_ShouldThrowException
2) TestCreateEmployee_NegativeId_ShouldThrowException
3) TestCreateEmployee_DuplicateId_ShouldThrowException
4) TestCreateEmployee_ValidId_ShouldPass
- Do not write your own catch blocks that exist only to fail a test
If any method in test code throws some exception then do not write a catch block only to catch the exception and fail the test case. Instead, use throws Exception statement in test case declaration itself. I will recommend to use Exception class and do not use specific subclasses of Exception. This will increase the test coverage also.
- Do not rely on indirect testing
Each test case should only test an explicit use case.
- Integrate test cases with build script
Its better if you can integrate your test cases with build script so that they will get executed in your production environment automatically. This increases the reliability of application as well as test setup.
- 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 test that target exceptions
If some of your test cases, which expect the exceptions to be thrown from application, use “expected” attribute like this. Try avoiding catching exception in catch block and using fail/ or asset method to conclude the test.
- 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. If you have used then re-initalize for each test case
- Do not skip unit tests
- Capture results using the XML formatter
It is for feel good factor. It will definitely not bring direct benefit but can make running unit tests interesting and entertaining. You can integrate Junit with ant build script and generate test cases run report in xml format using some color coding also. It is also a good practice to get followed.