This page covers some common testing best practices and anti-patterns.
Follow the Test-Driven Development Test Cycle.
First, come up with an initial list of end to end, component and/or unit tests that will be needed to show that the user story, requirement or improvement works as expected or issue has been fixed.
Then for each one:
Test behaviors, not methods or lines of code.
Write each test following the Behavior-Driven Development structure, i.e., Given/When/Then. In other words make sure each test:
Tests should have names that clearly indicate the behavior being tested. For instance, catalogReturnsMetacardIdWhenIngestSucceeds or exceptionThrownWhenInvalidUserNameProvided.
Always remember to tests all positive and negative test scenarios and ensure that the proper results (error code, exception, message, etc.) are returned.
Some error scenarios are extremely difficult to test using end-to-end or component tests, which makes testing all exception scenarios at the unit test level even more critical. |
When writing a test, always:
Never assert or verify the same behavior in more than one test.
When writing unit or component tests, mock all dependencies that may fail. This allows the tests to guarantee that the unit or component under test behaves as excepted when one of its dependencies fails.
This is especially important in unit tests as these may be the only tests where some of those exception scenarios can be tested.
Always make sure that tests clean up after themselves to ensure that a test failure doesn't impact other tests.
Use the test tool's fixture capabilities (i.e., cleanup/tear-down methods) to perform any required cleanup and reset the test class to a known state instead of using finally
blocks.
Before changing or refactoring existing code, always make sure that tests exist and pass first.
If not, write unit test for the code that is about to be changed or refactored.
When dealing with legacy code that would be difficult to unit test, consider writing component tests instead. Those should be easier to write while still making it safe to refactor or change the code as needed.
A test that is difficult to name usually indicates that it is trying to verify multiple behaviors and should be broken up. It may also indicate that the class under test is doing too much and is breaking the Single Responsibility Principle and may need to be refactored.
A test that requires many mocks usually indicates that the class under test is breaking the Single Responsibility Principle or the Law of Demeter and may need to be refactored.
Classes that make it difficult to mock their dependencies usually need to be refactored to follow the Dependency Inversion Principle and use dependency injection. For cases where the class to be mocked is instantiated inside the class multiple times, consider creating and injecting a factory class or a Supplier
, or use the Factory Method design pattern.
Sleeps in tests should be avoided as they open the door to timing issues and race conditions, slow tests down and are a major cause of test flakiness.
Some options to avoid sleeps in tests include:
A test should test one and only one behavior. Having assertions and/or verifications that validate different behaviors makes it difficult to know why a test has failed and doesn't provide a good insight into what is being tested.
Repeated Given, When or Then sections in a BDD test, or difficult to name tests are usually two good indicators that a test is doing too much.