I know when it comes to my articles I can focus much of test automation effort on building end-to-end and solution-oriented test frameworks, largely because these are the more complex systems to build this. This doesn’t discredit the extreme importance of the unit test, however. In fact, unit tests should remain a primary focus of all companies because the sooner you catch a defect, the better and what better time to do that than when you are executing your unit tests.
In any company, the majority of your test make-up should consist of unit tests. It’s the best opportunity for catching early defects and testing each and every small function of your code to maximize your test coverage before passing on to your more complex automated systems. So, if the majority of your development team is going to expend a considerable amount of effort writing unit tests, it only makes sense that they do it correctly. Many aspects of unit testing are common knowledge, but old habits die hard and many developers continue to get caught up in things which might mean your unit tests are not as effective as they could be.
The key to unit testing is simplicity and speed. It should be something that can easily ensure your code performs as expected, in the simplest and shortest space possible. A unit test should essentially:
1) Test one thing at a time
2) Test only the result and not the process
3) Be through
4) Make sure tests are independent
5) Build tests for Rapid execution
Focus on only one thing at a time
Unit tests should only test one aspect of your code at a time so that it is easy to understand the purpose of each unit test. Properly written, and well-decomposed unit tests are an excellent source of documentation. This may mean that you will need to write multiple unit tests in order to fully exercise one method.
Unit tests are supposed to be as isolated as possible and not worry about the external functions. That is what the bigger solution tests do. If your method does call public methods in other classes, and these calls are guaranteed by your interface, then simply use a mock framework. Don’t waste time in trying to replicate systems – again you don’t want your developers expending too much effort to create test systems that form part of bigger integration layers. You may want to catch the defect early by doing this, but it’s not the most efficient use of developer time.
Focus on only the result
The important thing with a unit test is to focus on the result a not how it is achieved. A unit test should not look inside the method to see what it is doing. This is important because it means code can be more easily changed without causing tests to fail. In the same way, unit tests should not directly test that private methods are being called, this should form a higher level of integration tests which then measures the code coverage to ensure this gets done.
You should not use the method itself (or any of the internal code it uses) to generate the expected results dynamically. The expected result should be hard-coded into your test case so that it does not change when the implementation changes. Here's a simplified example of what a unit test should do:
In this small example, note that how the result is calculated is not checked - only that the result is correct.
The most important aspect of well-written unit tests is that your unit tests must be thorough. You should only be satisfied with your unit tests when you’re confident that your unit tests thoroughly verify that your code works as we expect it to work – as close to 100% code coverage as you can get. To do this, your tests must use the various System.assert() calls. Unit tests should exercise the expected as well as the unexpected conditions in your code. Special attention should be paid to areas of code that are particularly likely to break.
Repeatability and Independence
Every one of your unit tests should be able to be run repeatedly and continue to produce the same results, regardless of the environment in which the tests are being run. When your unit tests can be run in a repeatable and consistent manner, you can trust that your unit tests will only detect and alert you to real bugs, and not environmental differences.
One common mistake that might prevent you from writing repeatable unit tests is relying upon hard-coded information. Unit tests are by nature run in different sandbox environments all the time and so you need to reduce dependencies on anything that might be anything environment or data. Instead of relying on hard-coded information, unit tests should create their own data.
Keep in mind that each of your unit tests must be independent from your other unit tests. Unit tests should not alter the state of a system, and they are not guaranteed to run in a particular order, so you cannot rely upon one unit test to do the setup work for another unit test.
Keep it light and fast
Making unit tests as simple and specific as possible reduces the need for complex logic and decisions within your test. By nature unit tests should run fairly quickly, but the mistake that perhaps gets made too much is running them excessively.
If a piece of code has not been touched then there is no need to run these unit tests again. Instead of having a continues integration environment that runs every single unit test each execution focus only on unit testing the changes and then push the unit tests onto your bigger integration or solution regression cases. I am all for running regular regression on your platform, but this should be exclusively done in the evening and not during hours when developers need responses in a matter of minutes or less.
Unit tests can often seem like an unnecessary burden to developers, but when done right allow for early error detection and reduce the need for too many complex and slower executing solution tests. While you may not want to spend too much time on this as a developer, doing this correctly will save you a lot of effort in the future.