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.
- Make each test independent to all the others[SE1]
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 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.)[SE3]
- Don’t unit-test configuration settings[SE4]
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.”
- 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.
...
- Create unit test that target exceptions
If 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.
@Test(expected=SomeDomainSpecificException.SubException.class)
- 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.
[SE1]This is not universally accepted any longer as best practice and personally I do not believe. Code does not live isolated and does not need to be tested in isolation. Initializing a database for each method doesn’t make sense when the previous test inserted the data you want to retrieve in the next test. Tests need to be atomic and ACID but that can be done using well written tests not needless setup There are many cases of this type for example when testing a UI via Selenium if not done in sequence will fail. For this reason I have moved all of my testing to TestNG where the order of tests can be specified. I would remove this requirement because it is not accepted as best practice any longer.
[SE2]Tests should never call out to external services unless they are maven failsafe because they will fail during CI builds. That said the idea you need to mock the services is poor practice and does not provide you with real integration tests that are worth running. Databases in particular can easily be tested within the application unit tests w/o the need for mocks. This goes for testing REST and SOAP services – so why mock. I would rewrite this requirement to say - If you don’t know how to write tests for external services using embedded resources like database or WS and if you don’t know how to use maven failsafe then go ahead – mockit.
[SE3]This is so important - !!!! Class variables should (almost) NEVER change state – make them final unless there is a reason you can argue not to! And not just for testing!
[SE4]Really? Go ahead and test your configuration loading code – do you know how many people get classpaths wrong?