Designing for testability

We learned about testing impediments and how to refactor them. We cannot unit test code when testing impediments are present; we refactor the code and move the impediments out (to another class or methods), and during testing, the impediments are replaced with mock objects.

However, sometimes we cannot mock out the external dependencies because of testing an unfriendly design. This section covers the design for testability, or rather matters to avoid in code. The following Java constructs go up against mocking the testing impediments:

  • Constructors initialize testing impediments
  • Class-level variable declaration and initialization
  • The private methods
  • The final methods
  • The static methods
  • The final classes
  • Use of new
  • Static variable declaration and initialization
  • Static initialization blocks

You cannot unit test the legacy code because either it is tightly coupled or testing unfavorable language constructs hide the testing impediments. The following section explains the testing unfavorable constructs.

Note

To show a testing impediment, we'll throw a special runtime exception TestingImpedimentException. If your test fails with a TestingImpedimentException, then that means you cannot automate the test as your code has unfavorable features for testing.

Identifying constructor issues

To build a test, we need to instantiate the class in the test harness, but the problem with legacy code is that it is difficult to break dependency and instantiate a class in a test harness. One such example is in a constructor, where the class instantiates many objects, reads from the properties file, or even creates a database connection. There can be many callers of the class, so you cannot change the constructor to pass dependencies; otherwise, it will cause a series of compilation errors.

We will take a look at a sample legacy code and try to write a test for the class.

Suppose we have a TestingUnfavorableConstructor class with two external dependencies DatabaseDependency and FileReadDependency. Both the dependencies are slow in nature and are testing impediments. TestingUnfavorableConstructor creates dependencies in the constructor. Ideally, the dependencies represent the database access and the file reads from the TestingUnfavorableConstructor constructor. The following is the TestingUnfavorableConstructor class:

public class TestingUnfavorableConstructor {
  private DatabaseDependency dependency1;
  private FileReadDependency dependency2;
  
  public TestingUnfavorableConstructor() {
    this.dependency1 = new DatabaseDependency();
    this.dependency2 = new FileReadDependency();
  }

  
  public Object testMe(Object arg) {
    return arg;
  }

}

If we want to unit test the testMe() behavior of the class, then we need to create an object of the TestingUnfavorableConstructor class. However, when we try to create an instance in a unit test, the class fails to indicate that the class cannot be instantiated from an automated test suite. The following is the output:

Identifying constructor issues

To overcome this, you should inject the dependencies through a constructor instead of creating them in a constructor.

We cannot modify the default constructor because the class is invoked from many other clients. We cannot break the clients. The other two options are as follows:

  • Keep the default constructor as it is. Create another constructor and inject dependencies through this new constructor; from test, we can call this new constructor.
  • Create a protected method, move the dependency instantiation to that method, create two setter methods, and initialize the dependencies through the setter injection. In the test, create a fake object of the main class and override the protected method to do nothing, and pass the dependencies through the setter methods.

The first option is relatively straight forward. We'll apply the second approach.

The following is the modified code:

public class TestingUnfavorableConstructor {
  private DatabaseDependency dependency1;
  private FileReadDependency dependency2;
  
  public TestingUnfavorableConstructor() {
    createDependencies();
  }

  protected void createDependencies() {
    this.dependency1 = new DatabaseDependency();
    this.dependency2 = new FileReadDependency();
  }
  
  public void setDependency1(DatabaseDependency dependency1) {
    this.dependency1 = dependency1;
  }

  public void setDependency2(FileReadDependency dependency2) {
    this.dependency2 = dependency2;
  }

  public Object testMe(Object arg) {
    return arg;
  }
}

The following unit test overrides the TestingUnfavorableConstructor and provides an empty implementation of the createDependencies() method, creates mock dependencies, and calls setter methods to set the mock dependencies:

@RunWith(MockitoJUnitRunner.class)
public class TestingUnfavorableConstructorTest {
  @Mock DatabaseDependency dep1;
  @Mock FileReadDependency dep2;
  TestingUnfavorableConstructor unfavorableConstructor;
  @Before  public void setUp() {
    unfavorableConstructor= new TestingUnfavorableConstructor() {
      protected void createDependencies() {
      }
    };

    unfavorableConstructor.setDependency1(dep1);
    unfavorableConstructor.setDependency2(dep2);
  }
  
  @Test   public void sanity() throws Exception {
  }
}

Tip

Do not instantiate dependencies in the constructor; the dependencies may exhibit testing impediments and make the class nontestable. Instead of instantiating the dependencies in the constructor, you can pass the real implementations (real dependencies) to the constructor or the setter method of the code under the test.

Realizing initialization issues

Class-level variable declaration and object instantiation at the same time creates problems. You don't get the chance to mock out the variable. The following example explains the problem:

The VariableInitialization class has a database dependency, and the dependency is instantiated where it is declared, as follows:

Public class VariableInitialization {
  DatabaseDependency dependency1 = new DatabaseDependency();
  public void testMe(Object obj) {
    
  }
}

When you instantiate the VariableInitialization class in test, the test fails. The following is the output:

Realizing initialization issues

The following is the test class:

public class VariableInitializationTest {
  VariableInitialization initialization;

  @Before public void setUp() throws Exception {
    initialization = new VariableInitialization();
  }
  @Test   public void sanity() throws Exception {
  }
}

To overcome the class-level variable initialization, you can try out the following options:

  • Add a default constructor and move the dependency instantiation to the default constructor. Create another constructor and inject the dependencies through this new constructor; from test, we can call this the new constructor.
  • Add a default constructor, and move the dependency instantiation to a protected method and call the method from the default constructor. Create a setter method and initialize the dependency through a setter injection. In the test, create a fake object of the main class and override the protected method to do nothing, and pass the dependencies through the setter methods.

    Tip

    Do not instantiate variables at the class level.

Working with private methods

The private methods are useful for hiding the internal state and encapsulation, but they can also hide the testing impediments. The following example explains the details:

The PrivateMethod class has a private method named showError(). This private method hides a test impediment. When we unit test the validate() method with a null object, the validate() method calls the showError message, as follows:

public class PrivateMethod {
  public Object validate(Object arg) {
    if(arg == null) {
      showError("Null input");
    }
    return arg;
  }

  private void showError(String msg) {
    GraphicalInterface.showMessage(msg);
  }
}

The following is the test output:

Working with private methods

You can extract the testing impediments to a protected method, or you can separate the concern. Create a new class, move the testing impediment to that class, and inject the new class as a dependency.

Tip

Do not hide testing impediments in private methods.

The following code refactors the testing impediments and makes the class unit testable:

public class PrivateMethodRefactored {
  public Object validate(Object arg) {
    if(arg == null) {
      showError("Null input");
    }
    
    return arg;
  }

  protected void showError(String msg) {
    GraphicalInterface.showMessage(msg);
  }
}

The showError method's access specifier is changed to protected.

The following test code extends the class with an anonymous implementation, and it overrides the protected method with an empty implementation. The test code invokes the validate() method on the new anonymous implementation of the PrivateMethodRefactored class. In turn, the polymorphic behavior will call the empty implementation. Hence, the test will always bypass the testing impediments by calling the overridden empty implementation of the testing impediment, but the real production code will always invoke the protected method:

public class PrivateMethodRefactoredTest {

  PrivateMethodRefactored privateMethod;
  
  @Before
  public void setUp() {
    privateMethod = new PrivateMethodRefactored() {
      protected void showError(String msg) {
        
      }
    };
  }
  
  @Test
  public void validate() throws Exception {
    privateMethod.validate(null);
  }
}

Tip

This approach of bypassing the testing impediments with overridden versions of the testing impediments is known as faking or fake object. If the code under test contains many testing impediments, then it is not possible to override all of them in an anonymous class. Instead, we can create an inner class, and extend the code under test and override all the testing unfriendly methods.

Working with final methods

When a method is final, you cannot override it. If the final method hides any testing impediment, you cannot unit test the class. The following example explains the issue:

The FinalDependency class has a final method named doSomething. This method hides a testing unfriendly feature. The following is the class definition:

public class FinalDependency {

  public final void doSomething() {
    throw new TestingImpedimentException("Final methods cannot be overriden");
  }
}

The FinalMethodDependency class has a dependency on FinalDependency, and in the testMe method, it calls the doSomething method as follows:

public class FinalMethodDependency {

  private final FinalDependency dependency;

  public FinalMethodDependency(FinalDependency dependency) {
    this.dependency = dependency;
  }  
  public void testMe() {
    dependency.doSomething();
  }
}

In the test, we'll mock the dependency and unit test the code as follows:

@RunWith(MockitoJUnitRunner.class)
public class FinalMethodDependencyTest {
  @Mock
  FinalDependency finalDependency;
  FinalMethodDependency methodDependency;

  @Before
  public void setUp() {
    methodDependency = new FinalMethodDependency(finalDependency);
  }

  @Test
  public void testSomething() throws Exception {
    methodDependency.testMe();
  }
}

When we run the test, the test still accesses the testing impediment, as the mock object cannot stub a final method. When we try to stub the method, we get an error. The following test stubs the final method call:

  @Test
  public void testSomething() throws Exception {
    doNothing().when(finalDependency).doSomething();
    methodDependency.testMe();
  }

When we run the test, we get the following error message thrown by the Mockito framework:

Working with final methods

Tip

Do not hide the testing impediments in final methods. You cannot override or stub a final method.

The only possible way to overcome this is extracting the content of the final method to a protected method; call the protected method from the final method, and override the protected method in test. Otherwise, you can use the PowerMock or PowerMockito framework if you cannot touch the class at all; for example, when you only have a JAR file.

Exploring static method issues

The static methods are good for utility classes, but unnecessary use of static can hide the testing impediments and create problems in unit testing. The following example sheds light on the issue:

The SingletonDependency class is an implementation of the Gang of Four (GoF) singleton design pattern. It has a private constructor and a static getInstance() method to create only a single instance of the class. The static callMe() method hides a testing impediment. Note that the GoF singleton pattern doesn't define methods as static, but in this example, we are defining the callMe() method as static to display a drawback of the static methods. The following is the singleton implementation:

public class SingletonDependency {
  private static SingletonDependency singletonDependency;

  private SingletonDependency() {
  }

  public synchronized static SingletonDependency getInstance() {
    if (singletonDependency == null) {
      singletonDependency = new SingletonDependency();
    }

    return singletonDependency;
  }

  Public static void callMe() {
    throw new TestingImpedimentException("we dont need singleton");
  }
}

The VictimOfAPatternLover class has a dependency on SingletonDependency. The following are the class details:

public class VictimOfAPatternLover {
  private final SingletonDependency dependency;

  public VictimOfAPatternLover(SingletonDependency dependency) {
    this.dependency = dependency;
  }

  public void testMe() {
    dependency.callMe();
  }
}

Mockito cannot stub a static method. When we try to stub the static callMe() method, it still calls the original method and fails for the testing impediment. You cannot stub a static method.

Tip

Do not hide testing impediments in static methods. You cannot stub static methods.

The only way to overcome this issue is to create a protected method and wrap the static call. From the code, call the wrapped method and from the test, override the protected method.

Add a static wrapper method in the dependency class and call the static method from it, as shown in the following code:

  public static void callMe() {
    throw new TestingImpedimentException("Common we dont need singleton");
  }
  
  protected void wrapper() {
    callMe();
  }

In the code, call the wrapper method as follows:

  public void testMe() {
    dependency.wrapper();
  }

Stub the wrapper method in the test as follows:

@Test
  public void testMe() throws Exception {
    Mockito.doNothing().when(dependency).wrapper();
    aPatternLover.testMe();
  }

Working with final classes

You cannot override a final class, so you can hide testing unfavorable features in a final class. The following example explains the problem:

The final class hides a testing impediment as follows:

public final class FinalDepencyClass {

  public void poison() {
    throw new TestingImpedimentException("Finals cannot be mocked");
  }
}

The code under test has a dependency on the final class as follows:

public class FinalClassDependency {
  private final FinalDepencyClass finalDepencyClass;

  public FinalClassDependency(FinalDepencyClass     finalDepencyClass) {
    this.finalDepencyClass = finalDepencyClass;
  }
  
  public void testMe() {
    finalDepencyClass.poison();
  }
}

In test, we'll try to stub the poison method as follows:

@RunWith(MockitoJUnitRunner.class)
public class FinalClassDependencyTest {
  @Mock
  FinalDepencyClass finalDependency;
  
  FinalClassDependency test;

  @Before
  public void setUp() {
    test = new FinalClassDependency(finalDependency);
  }
  @Test
  public void testMe() throws Exception {
    Mockito.doNothing().when(finalDependency).poison();
    test.testMe();
  }
}

The test fails with a MockitoException as Mockito cannot mock a final class. The following is the JUnit output:

Working with final classes

Tip

Do not hide testing impediments in final classes. You cannot mock a final class.

Final classes are important for framework or architecture design so that no one can hack the behavior, but it can create a serious problem for unit testing. Consider it before you choose to make a class final.

Learning the new attribute

Java instantiates classes using the new operator, but a new operator can create problems for unit testing.

The following example explains the issue. The PoisonIvy constructor has a testing impediment such as calls fetch data from a database table or reads from a filesystem; we represented the testing impediment with the TestingImpedimentException:

public class PoisonIvy {

  public PoisonIvy() {
    throw new TestingImpedimentException(
      "Do not instantiate concrete class, use interfaces");
  }

  public void poison() {

  }
}

The following is the code that calls the PoisonIvy constructor:

public class NewExpressionDependency {

  public void testMe() {
    PoisonIvy ivy = new PoisonIvy();
    ivy.poison();
  }
}

When we unit test the testMe() code, it fails. The testMe() method directly creates an instance of dependency and calls the poison() method. You cannot override this new expression. If we want to unit test the testMe() method, first we need to move the new operator outside of testMe() as we cannot instantiate the PoisonIvy class. The constructor of PoisonIvy throws an exception. Hence, we cannot unit test the testMe behavior unless we move the object creation out of testMe. Instead of creating a new instance of PoisonIvy inside testMe(), we can pass an instance of PoisonIvy as a method argument, or create a class-level dependency and pass PoisonIvy as the constructor or setter dependency argument.

Tip

Program to an interface, not to an implementation. Rather than hardcoding the instantiation of the subtype into the code, assign the concrete implementation object at runtime.

What is "program to an interface, not to an implementation"?

This means program to a supertype rather than a subtype. You can interchange the implementation at runtime. In the collection framework, we have the List interface and its many implantations. In your class, always define a variable of the List type and not ArrayList; at runtime, you can assign any implementation you want.

In this example, you can pass PoisonIvy as a constructor or setter dependency, and at runtime (during testing), you can pass a mock or a fake implementation to suppress the testing impediments.

Exploring static variables and blocks

Static initializations and static blocks are executed during class loading. You cannot override them. If you initialize a testing impediment in a static block, then you cannot unit test the class. The following example explains the issue:

The StaticBlockOwner class has a static variable named StaticBlockDependency, and it initializes the variable in a static block. The following is the class:

public class StaticBlockOwner {
  private static StaticBlockDependency blockDependency;
  static {
    blockDependency = new StaticBlockDependency();
    blockDependency.loadTime = new Date();
  }
  public void testMe() {
  }
}

When we unit test the class, it fails. The following is the unit test:

public class StaticBlockOwnerTest {
  StaticBlockOwner owner;
  @Before public void setUp()  {
    owner = new StaticBlockOwner();
  }
  @Test   public void clean() throws Exception {
    owner.testMe();
  }
}

The test fails with a java.lang.ExceptionInInitializationError, as it tries to instantiate the dependency in a static block and the dependency throws an exception.

Tip

Do not instantiate dependencies in the static block. You cannot override the testing impediments.

The book Working Effectively with Legacy Code, Pearson Education, by Michael Feathers explains the legacy code and how effectively you can refactor the legacy code. You can read the e-book at http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset