However it is easy to write a readable and maintainable unit test. You just have to follow several tips.
Tip #1 : give a comprehensive name to each test
Your unit test name should be comprehensive enough to understand which method you are testing and which case you are testing. To achieve that, three information are mandatory in your method name :- the name of the tested method
- the input parameters of the tested method
- what is the expected behavior of the tested method
@Test
public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() {
}
Ok I understand you can be chocked to see an underscore in the method name. But it is a little trick to improve the visibility of what your test is doing. Moreover you can easily identify if you have forgotten a case using the method listing of your favorite IDE :
Tip #2 : use Given / When / Then template
Every unit test has three sections of instructions :- the test preparation
- the call to the tested method
- the assertions
@Test public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() { // Given User user = new User(); user.setName("userName"); user.setBirthDate(new Date()); user.setStatus(-1); // When List<User> users = userService.filterInvalidUsers(
Arrays.asList(user)
); // Then assertNotNull(users); assertTrue(users.isEmpty()); }
Tip #3 : use reusable methods for the Given and Then sections
Even if it is logical to factorize your code, this basic coding principle is not always applied to the unit tests. Some developers prefer to copy & paste an entire unit test, just change the test name and one of the assertions, whereas they could create an utility method used by several tests.An exemple of factorization of our test method could be :
@Test public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() { // Given User user = createUserWithCorrectParameters(); user.setStatus(-1); // When List<User> users = userService.filterInvalidUsers(
Arrays.asList(user)
); // Then assertThatThereIsNoUser(users); } private User createUserWithCorrectParameters() { User user = new User(); user.setName("userName"); user.setBirthDate(new Date()); user.setStatus(10); return user; } private void assertThatThereIsNoUser(List<User> users) { assertNotNull(users); assertTrue(users.isEmpty()); }
Tip #4 : mock all interactions to other objects
An important principle of the unit tests is to test only the behavior of the tested class, and not the behavior of all the objects used by this class. If you don't respect this principle, you could have impacts on several test classes each time you change the behavior of a single class, which is a waste of time.To do that you can mock every other objects used by the tested class. Mock an object means simulate his behavior and not do a real call to the object. Great Java librairies allow to do this job, for example Easymock or Mockito. I will do a comparative of these two solutions in another thread.
Tip #5 : use partial mocking to have smaller tests
Imagine you want to add a createUsers method in the same class of the filterInvalidUsers method :public void createUsers(List<User> users) {
List validUsers = filterInvalidUsers(users);
if (! validUsers.isEmpty()) {
userDao.createUsers(validUsers);
}
}
You have already written your unit tests on filterInvalidUsers and you don't want to write them again. How can you avoid that? A solution is to use partial mocking. Whereas classic mocking allows you to mock the objects used in your tested class, the partial mocking allows you to mock a method in the class you are testing. So you can simulate the call to the method then verify that the call has been performed. For example, in the createUsers unit tests, you can mock the call to the filterInvalidUsers. In Java, EasyMock and Mockito both allow to do partial mocking.
Aucun commentaire:
Enregistrer un commentaire