Java Unit Test with TestNG
Terminology
- CUT (Class Under Test) – a class in the production code which is being tested
- MUT (Method Under Test) – a method in the production code which is being tested
- Fixture – the set of fields defined in a test class and initialized in
setUp()for use in multiple tests
Frameworks
While you will not be arrested by the Unit Testing Police for not following the policies below, you will be helping the team by ensuring uniformity and consistency:
- Use TestNG
- This is the prevailing framework and easily supports test groups to facilitate integration and other kinds of tests
-
Although TestNG allows you to use JUnit style assertions, it is less confusing to simply stick with TestNG style everywhere
-
Use Mockito
-
Although some existing tests were written with EasyMock, you should find that Mockito can easily do everything that EasyMock does but with much less code
-
- Avoid PowerMock
- While PowerMock can coexist with TestNG and Mockito, the features it provides enable developers to work around untestable code, rather than making the code more testable
-
Use Hamcrest
-
This is primarily relevant to special assertions using
assertThat()
-
Workflow
If you follow a TDD methodology, write the tests first which express the features you wish to implement. Otherwise, make a first pass at writing the code under test, then proceed to the list below.
- You can use your IDE to generate at least one test per method in the CUT
- In IntelliJ, select the class name and press
ALT-Enter, then select "Create Test" - It is usually useful to have a
setUp()method, so check this box - Select all the methods
- In IntelliJ, select the class name and press
- Mark each test class with
groups = "unit", like so:-
@Test(groups ="unit")publicclassMyClassTest{...}
-
- Name your test "methodUnderTest_Conditions" or "methodUnderTestConditions" or "testMethodUnderTestConditions"
- Prefer the names from left to right, depending on the PMD/Checkstyle rules in effect
- This scheme makes it easy to see what test and method failed during test failures
- Sometimes it is convenient to also append the expected output value to the name, like:
equals_WrongClass_False()
- Write a test for every code path through each method
- For each method, there should be at least as many tests as the cyclomatic complexity of the MUT
- If you know that an expression inside the method under test throws, then you should write a test for that scenario
- Every nullable parameter should have a test for both the
nulland not-nullconditions- The expected behavior of the method under a null parameter should be clearly documented in the Javadoc for the method
- If the above is not true, then add the appropriate Javadoc
-
/*** Returns the list of hotel summaries for the IDs provided. Does not return null.** @param hotelIds the list of hotel IDs for the summaries to fetch--must not be null*/publicList<Summary> getHotelSummaries(List<String> hotelIds);// Or possibly:/*** Returns the list of hotel summaries for the IDs provided, or null if hotelIds is null or no data is available.** @param hotelIds the list of hotel IDs for the summaries to fetch*/publicList<Summary> getHotelSummaries(List<String> hotelIds);
- If the expected behavior of a test is for the MUT to throw an exception, then use the
expectedannotation for TestNG:-
@Test(expected = InvalidArgumenException.class)publicvoidtestGetHotelSummariesNullHotelIDsThrows(){this.retriever.getHotelSummaries(null);}
-
- If an object is constructed the same way in several tests, hoist it to the test fixture
- Move the local variable to a field of the test class
- If the value is immutable, make it
static finaland initialize it directly - Otherwise, initialize it in
@BeforeMethod public void setUp()-
@Test(groups ="unit")publicclassHotelRetrieverTest{privateLodgingContentRetriever contentRetriever;privateReviewRetriever reviewRetriever;privateHotelRetriever hotelRetriever;@BeforeMethodpublicvoidsetUp(){this.contentRetriever = mock(LodgingContentRetriever.class);when(this.contentRetriever.getContentSummary(any(), any(), any())).thenReturn(newContentSummary(1234,"Hotel Name", ...);this.reviewRetriever = mock(ReviewRetriever.class);when(this.reviewRetriever.getReviewSummarythis.hotelRetriever =newHotelRetriever(this.contentRetriever,this.reviewRetriever);}}
-
- Minimize mocking noise
- The mocks become much more readable if you use static imports
when/thenclauses are easier to read when thethenclause is wrapped:-
when(retriever.getHotelSummaries(any())).thenReturn(...);
-
-
Use good assertions
-
assertEquals()is usually the best assertion, because it is the strongest (most precise) assertSame()is appropriate for testing fluent APIs and normalizing/interning caches-
Avoid
assertNotNull(), which is often the weakest assertion–try to find a reliable property of the result which can be asserted instead -
If there are no good built-in matchers for your assertion, use Hamcrest's
assertThat()and write the appropriate custom matcher
-
- Ensure the test can fail
- Confirm this by first writing the test in a way that fails
- Try to fail the test by only modifying the test input(s) or expected output(s), not the assertions
- It is the assertion which must fail, not the whole test, per se
- If an assertion can never fail, then it is a useless NOP, and the test itself provides false coverage
-
// HotelRetriever.javapublicList<Summary> getHotelSummaries(List<String> hotelIds){finalList<Summary> result =newArrayList<>();// result is never reassigned...returnresult;}// HotelRetrieverTest.java@TestpublicvoidtestGetHotelSummaries(){// Useless assert, because it can never failassertNotNull(retriever.getHotelSummaries(HOTEL_IDS));}
-
- Confirm this by first writing the test in a way that fails
-
Do not use reflection to violate privacy
-
A common pattern uses Spring's
ReflectionTestUtilsto make a private field accessible for override in a test– DO NOT DO THIS!!! -
Instead, make the field an injected dependency using c'tor injection:
-
// BadpublicclassHotelRetriever{privatestaticfinalLogger LOGGER = LoggerFactory.getLogger(HotelRetriever.class);...}publicvoidtestGetHotelSummaries(){finalLogger logger = mock(Logger.class);ReflectionTestUtils.setField(retriever,"LOGGER", logger);...verify(logger).info(containsString("message"));}// GoodpublicclassHotelRetriever{privatefinalLogger logger;...publicHotelRetriever(){this(LoggerFactory.getLogger(HotelRetriever.class));}// Package private for testingHotelRetriever(Logger logger){this.logger = notNull(logger);}}publicvoidtestGetHotelSummaries(){finalLogger logger = mock(Logger.class);finalHotelRetriever retriever =newHotelRetriever(logger);...verify(logger).info(containsString("message"));}
-
- Or, add a [package private] getter if necessary
-
- Test methods which throw only need to declare
throws Exception- More precise
throwsclauses are useless because nobody except the test runner should be calling the test method - The thrown exceptions should be exercised in separate tests via
@Test(expected = ThrownException.class)
- More precise
- Use code coverage metrics to show you where tests are missing
- Code coverage only tells you where you are not done – it does not tell you when you are done !!!
- It does not guarantee that your tests make strong assertions – only manual inspection can verify this!
- It does not know whether you have tested boundary conditions – only careful analysis can verify this!
Guidelines
- Tests must be parallelizable with no synchronization
- This also implies that the test runner must be able to run them sequentially in any order and succeed
- Both of the above conditions means that tests may only share immutable state!
- All mutable state must be constructed at test initialization time (mostly in
setUp()) - Code which relies on 3rd-party singletons should mock the singletons to satisfy this requirement
- If the CUT is a singleton, it should be implemented in a way which allows multiple instances for testing (usually by providing a package private c'tor)
- Tests must be repeatable
- Ideally, all code executed in a test should be observationally deterministic–each step should produce the same output, as far as the caller is concerned
- This means that non-deterministic elements must be mocked, if possible (this includes the clock/calendar!)
- Each test should only verify one effect
- All method calls except the MUT should serve to initialize the test
- All assertions in the test should only verify the behavior of the single MUT
- A test may have multiple assertions, but only to determine the shape of a single output
- Verify state, not behavior
- Strongly prefer to
assertEquals()on the result rather thanverify(mock).methodWasCalled()assertwhen you can,verifywhen you must- Seeing both
assertandverifyin a test is a smell–you should usually only need one or the other
- You may need multiple assertions to fully qualify the final state
- You rarely need more than one
verifyto qualify the behavior of the MUT - By only checking the state, which is the public contract, you allow the implementation to arrive at that state by various means (behaviors)
- Don't defeat this by
verifying the exact path through the MUT–doing so makes the test brittle without adding value
- Strongly prefer to
- Prefer real objects over mocks
- Strongly prefer to
assertEquals()on the result rather thanverify(mock).methodWasCalled()assertwhen you can,verifywhen you must- Seeing both
assertandverifyin a test is a smell–you should usually only need one or the other
- You may need multiple assertions to fully qualify the final state
- You rarely need more than one
verifyto qualify the behavior of the MUT - By only checking the state, which is the public contract, you allow the implementation to arrive at that state by various means (behaviors)
- Don't defeat this by
verifying the exact path through the MUT–doing so makes the test brittle without adding value
- Strongly prefer to
- Prefer real objects over mocks
- If a class goes out of process (calls a service/database, touches the filesystem, etc.), use a mock
- If a class has non-deterministic behavior (calls
random(), uses the clock, etc.), use a mock - If a class has complex construction requirements (you need to build a dozen objects to call the c'tor), use a mock
- Otherwise, use a real instance of the class
- Tests should drive the conversion of field/setter injection to c'tor injection
- C'tor injection is superior because it forces the developer to eliminate circular dependencies
- C'tor injection allows dependencies to be
final - Setters provide more public interface which needs to be tested for full code coverage
- Field injection requires reflection to override in tests
- Tests must be fast
- The entire suite of unit tests should take no more than a few seconds to execute per library/module
- Fast tests facilitate a tight write-test-fix loop; slow tests discourage the execution of tests
- Do not let a test wait for some elapsed time to occur–the clock should be mocked in the first place, so it should be independently settable

浙公网安备 33010602011771号