About Petri Kainulainen

Petri is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

Three Reasons Why We Should Not Use Inheritance In Our Tests

When we write automated tests (either unit or integration tests) for our application, we should notice pretty soon that

  1. Many test cases use the same configuration which creates duplicate code.
  2. Building objects used in our tests creates duplicates code.
  3. Writing assertions creates duplicate code.

The first thing that comes to mind is to eliminate the duplicate code. As we know, the Don’t repeat yourself (DRY) principle states that:

 

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

So we get to work and remove the duplicate code by creating a base class (or classes) which configures our tests and provides useful testing utility methods to its subclasses.

Unfortunately, this is a very naive solution. Keep on reading and I will present three reasons why we should not use inheritance in our tests.

  1. Inheritance Is Not the Right Tool for Reusing Code
  2. DZone published a very good interview of Misko Hevery where he explains why inheritance is not the right tool for reusing code:

    The point of inheritance is to take advantage of polymorphic behavior NOT to reuse code, and people miss that, they see inheritance as a cheap way to add behavior to a class. When I design code I like to think about options. When I inherit, I reduce my options. I am now sub-class of that class and cannot be a sub-class of something else. I have permanently fixed my construction to that of the superclass, and I am at a mercy of the super-class changing APIs. My freedom to change is fixed at compile time.

    Although Misko Hevery was talking about writing testable code, I think that this rule applies to tests as well. But before I explain why I think this way, let’s take a closer look at the the definition of polymorphism:

    Polymorphism is the provision of a single interface to entities of different types.

    This is not why we use inheritance in our tests. We use inheritance because it is an easy way to reuse code or configuration. If we use inheritance in our tests, it means that

    • If we want to ensure that only the relevant code is visible to our test classes, we probably have to create a “complex” class hierarchy because putting everything in one superclass isn’t very “clean”. This makes our tests very hard to read.
    • Our test classes are in the mercy of their superclass(es), and any change which we make to a such superclass can effect its every subclass. This makes our tests “hard” to write and maintain.

    So, why does this matter? It matters because tests are code too! That is why this rule applies to test code as well.

    By the way, did you know that the decision to use inheritance in our tests has practical consequences as well?

  3. Inheritance Can Have a Negative Effect to the Performance of Our Test Suite
  4. If we use inheritance in our tests, it can have a negative effect to the performance of our test suite. In order to understand the reason for this, we must understand how JUnit deals with class hierarchies:

    1. Before JUnit invokes the tests of a test class, it looks for methods which are annotated with the @BeforeClass annotation. It traverses the whole class hierarchy by using reflection. After it has reached to java.lang.Object, it invokes all methods annotated with the @BeforeClass annotation (parents first).
    2. Before JUnit invokes a method which annotated with the @Test annotation, it does the same thing for methods which are annotated with the @Before annotation.
    3. After JUnit has executed the test, it looks for method which are annotated with the @After annotation, and invokes all found methods.
    4. After all tests of a test class are executed, JUnit traverses the class hierarchy again and looks for methods annotated with the @AfterClass annotation (and invokes those methods).

    In other words, we are wasting CPU time in two ways:

    1. The traversal of the test class hierarchy is wasted CPU time.
    2. Invoking the setup and teardown methods is wasted CPU time if our tests don’t need them.

    I learned this from a book titled Effective Unit Testing.

    You might of course argue that this isn’t a big problem because it takes only a few milliseconds per test case. However, the odds are that you haven’t measured how long it really takes.

    Or have you?

    For example, if this takes only 2 milliseconds per test case, and our test suite has 3000 tests, our test suite is 6 seconds slower than it could be. That might not sound like a long time but it feels like eternity when we run our tests in our own computer.

    It is in our best interest to keep our feedback loop as fast as possible, and wasting CPU time doesn’t help us to achieve that goal.

    Also, the wasted CPU time is not the only thing that slows down our feedback loop. If we use inheritance in our test classes, we must pay a mental price as well.

  5. Using Inheritance Makes Tests Harder to Read
  6. The biggest benefits of automated tests are:

    • Tests document the way our code is working right now.
    • Tests ensure that our code is working correctly.

    We want to make our tests easy to read because

    • If our tests are easy to read, it is easy to understand how our code works.
    • If our tests are easy to read, it is easy to find the problem if a test fails. If we cannot figure out what is wrong without using debugger, our test isn’t clear enough.

That is nice but it doesn’t really explain why using inheritance makes our tests harder to read. I will demonstrate what I meant by using a simple example.

Let’s assume that we have to write unit tests for the create() method of the TodoCrudServiceImpl class. The relevant part of the TodoCrudServiceImpl class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TodoCrudServiceImpl implements TodoCrudService {

    private TodoRepository repository;
   
    @Autowired
    public TodoCrudService(TodoRepository repository) {
        this.repository = repository;
    }
       
    @Transactional
    @Overrides
    public Todo create(TodoDTO todo) {
        Todo added = Todo.getBuilder(todo.getTitle())
                .description(todo.getDescription())
                .build();
        return repository.save(added);
    }
   
    //Other methods are omitted.
}

When we start writing this test, we remember the DRY principle, and we decide to create a two abstract classes which ensure that we will not violate this principle. After all, we have to write other tests after we have finished this one, and it makes sense to reuse as much code as possible.

First, we create the AbstractMockitoTest class. This class ensures that all test methods found from its subclasses are invoked by the MockitoJUnitRunner. Its source code looks as follows:

import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public abstract class AbstractMockitoTest {
}

Second, we create the AbstractTodoTest class. This class provides useful utility methods and constants for other test classes which tests methods related to todo entries. Its source code looks as follows:

import static org.junit.Assert.assertEquals;

public abstract class AbstractTodoTest extends AbstractMockitoTest {

    protected static final Long ID = 1L;
    protected static final String DESCRIPTION = "description";
    protected static final String TITLE = "title";

    protected TodoDTO createDTO(String title, String description) {
        retun createDTO(null, title, description);
    }

    protected TodoDTO createDTO(Long id,
                                String title,
                                String description) {
        TodoDTO dto = new DTO();
       
        dto.setId(id);
        dto.setTitle(title);
        dto.setDescrption(description);
   
        return dto;
    }
   
    protected void assertTodo(Todo actual,
                            Long expectedId,
                            String expectedTitle,
                            String expectedDescription) {
        assertEquals(expectedId, actual.getId());
        assertEquals(expectedTitle, actual.getTitle());
        assertEquals(expectedDescription, actual.getDescription());
    }
}

Now we can write a unit test for the create() method of the TodoCrudServiceImpl class. The source code of our test class looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public TodoCrudServiceImplTest extends AbstractTodoTest {

    @Mock
    private TodoRepository repositoryMock;
   
    private TodoCrudServiceImpl service;
   
    @Before
    public void setUp() {
        service = new TodoCrudServiceImpl(repository);
    }
   
    @Test
    public void create_ShouldCreateNewTodoEntryAndReturnCreatedEntry() {
        TodoDTO dto = createDTO(TITLE, DESCRIPTION);
       
        when(repositoryMock.save(isA(Todo.class))).thenAnswer(new Answer<Todo>() {
            @Override
            public Todo answer(InvocationOnMock invocationOnMock) throws Throwable {
                Todo todo = (Todo) invocationOnMock.getArguments()[0];
                todo.setId(ID);
                return site;
            }
        });
               
        Todo created = service.create(dto);
       
        verify(repositoryMock, times(1)).save(isA(Todo.class));
        verifyNoMoreInteractions(repositoryMock);
               
        assertTodo(created, ID, TITLE, DESCRIPTION);
    }
}

Is our unit test REALLY easy read? The weirdest thing is that if we take only a quick look at it, it looks pretty clean. However, when we take a closer look at it, we start asking the following questions:

  • It seems that the TodoRepository is a mock object. This test must use the MockitoJUnitRunner. Where the test runner is configured?
  • The unit test creates new TodoDTO objects by calling the createDTO() method. Where can we find this method?
  • The unit test found from this class uses constants. Where these constants are declared?
  • The unit test asserts the information of the returned Todo object by calling the assertTodo() method. Where can we find this method?

These might seem like “small” problems. Nevertheless, finding out the answers to these questions takes time because we have to read the source code of the AbstractTodoTest and AbstractMockitoTest classes.

If we cannot understand a simple unit like this one by reading its source code, it is pretty clear that trying to understand more complex test cases is going to be very painful.

A bigger problem is that code like this makes our feedback loop a lot longer than necessary.

What Should We Do?

We just learned three reasons why we should not use inheritance in our tests. The obvious question is:

If we shouldn’t use inheritance for reusing code and configuration, what should we do?

That is a very good question, and I will answer to it in a different blog post.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


4 × four =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close