07 - JUnit techniques

7. JUnit techniques    

     7.1 JUnit Patterns

7.2 Junit Anti-Patterns                             

7.3 Testing Legacy Code

7.4 Refactoring

7.5 Test Suites

          7.6 JUnit Test Suites

                 7.6.1 Wizards for creating JUnit test suites

7. JUnit techniques

A single unit test should represent exactly what we want to test. Tests should not be written for everything and should be focused on what exactly matters for the developer, application and users.

7.1 JUnit Patterns

JUnit uses the following Design Patterns:

Command (Processor)

The command pattern encapsulates a function as an object. The class has a single method with a name like do, run or perform.  An instance of a subclass is created that override this method. The command can be passed as an object and executed by calling the method.

In JUnit test cases are represented as command object that implement the interface Test:

public interface Test {
    public void run();
}

The JUnit test cases are instances of a subclass of a concrete class TestCase:

public abstract class TestCase implements Test { 
    private String fName; 
    public TestCase(String name) { 
         fName= name;     
    }
    public void run(){
         …
    }
}

Template Method

In general, test cases have three phases

setting up the context

perform the test

tearing down the context

The implementations are:

protected void setup(){}

protected void runTest(){}

protected void tearDown(){}

These methods are called  implicit because these are the methods of the clients of the JUnit framework. They are analog to pre- and post- conditions.

Adapter

Adapter pattern offers the possibility tot convert the interface of a class into another interface that the clients expect. The class adapter uses subclassing to adapt the interface. For example:

public class TestPersonEquals extends PersonTest { 
    public TestPersonEquals() { 
     super("testPersonEquals"); 
    } 
    protected void runTest () { 
      testPersonEquals();
   } 
}

A subclass of PersonTest is implemented in order to adapt testPersonEquals to runTest and override runTest to invoke testPersonEquals.

This implementation will force us to create a subclass for each test case. This problem can be avoided by using anonymous inner classes which will let to create an Adapter without having to invent a class name:

TestCase test = new Persontest(“testPersonEquals”){

  protected void runTest(){testPersonEquals();}
} 

Composite

Test cases are grouped into test suites. All the actions with a test suites are the same with a test from a test suite. This pattern is represented by a composite object that shares an interface.

The Patterns used in JUnit 4.x are the same as they were in Junit 3. The difference is the inheritance restriction that all test classes must be subclasses of TestCase is no longer required.

With this feature the test classes may now extend other test classes that have nothing to do with JUnit.

Also the methods names have no longer to have the names starting with “test” which makes the method name more revealing and shorter.

JUnit 4 still uses template pattern with @Before annotation. Now, instead of using a template method void setup() which is called before each test, now all methods whatever that are named and which are annotated with @Before annotation will be called. Test specific patterns which were difficult to do in JUnit 3 now are easy to do in JUnit 4. 

7.2 Junit Anti-Patterns

Antipatterns represent the opposite of software design Patterns, the result of bad implementations, bad practices following and bad understanding of the context of the problem.

Testing should check that the code does what we expect. Sometimes during unit tests, developers spend a lot of time verifying that code works based on how they wrote it, rather than verifying that it achieves the desired result. 

Developers should know that for JUnit framework, it is not enough only to learn the JUnit API and to write a few tests in order to have a sufficient tested application. It is easy to learn Junit, but it is hard to write good tests.

There are some common JUnit antipatterns and on http://www.exubero.com/junit/antipatterns.html we can find a good analysis of this subject made by Joe Schmetzer.

  • Misuse of Assertions
    • Manual Assertions
    • Multiple Assertions
    • Redundant Assertions
    • Using the Wrong Assert
  • Superficial Test Coverage
  • Overly Complex Tests
  • External Dependencies
  • Catching Unexpected Exceptions
  • Mixing Production and Test Code
  • No Unit Tests 

7.3 Testing Legacy Code

Developers are changing often their jobs so the companies have to protect and maintain the collective knowledge of the projects. Applications built upon a code base are continually modified by adding new enhancements and by fixing bugs. Changes to legacy code should be made safely.

Code should be changed without changing the behavior.

After the changes are done some problems could arise:

  • Complexity
  • Duplication
  • Difficult to read or understand
  • Not testable

Solutions to these problems:

  • “Golden Master” testing approach ---- capturing the result of a process and then comparing future runs against the saved “gold master” version.
  • Bottom Up/Inside Out code inspection
  • Refactoring

Such structures could be prevented by:

  • Code review
  • Automated code checks
    • Code formatter
    • Clean code before save
    • Setup code checker
    • Integrate code checker with CI

Developers should start writing tests (for code with no previous test) for the parts of the applications in which most errors happened in the past and could possibly break. Thus, developers can concentrate on the critical parts of their applications. Production code should not be changed unless is covered by a test.

The tests should be added incrementally - from the shortest (less understanding of the code base) to deepest branch. When code is inspected for bug fixes or refactoring first the unit tests should be written in order to find where the problem is.

7.4 Refactoring

Refactoring is a disciplined technique for restructuring the actual code, altering its internal structure without changing its external behavior.

Refactoring is composed of a sequence of transformations, each transformation doing little for the code, but in total producing important restructuring.

Refactoring should start from deepest to shortest branch.

Refactoring test code represents the cleaning of the source code used for testing in order to improve its design without changing its external behavior.

Every change or add of functionality needs to deal with existing tests.

The goal of refactoring of unit tests is different than the goal of the common code. Unit tests code should be simple and readable meanwhile for common code the goal is to be modularized and to reduce the relationships.

Test code refactoring can be applied on:

 

Test Method

 

Change actions and assertions with equivalent ones.

Assertion explanation

Simplify test scenario

Separate action from assertion

Decompose assertion

 

 

Test Class

 

Reorganize actions and assertions among methods

Split test

Join similar tests with distinct data

Add fixture

Test Suite

 

Mirror Hierarchy to Tests

Pull Up test

Pull down test

Create Template Test

Split Tests from Composition

 

The unit test refactoring could reduce complexity and eliminate duplication.

 7.5 Test Suites

Test suites are used for the purpose of grouping and invocation and to group test classes from the same package.

The Suite is a container used to collect tests. They are useful to build, test cycles. Suites can be nested.

  • Smoke tests
  • Integration tests
  • Separate fast/ slow tests

7.6 JUnit Suites

JUnit Suites are marked with the annotations:

  • @RunWith(Suite.class)
  • @SuiteClasses({<TestClass>.class, <SuiteClass>.class}) 
package main.java.examples;

import java.util.Arrays;

public class MySuiteUtils {

       public String[] buyItems() {
        String[] shoppingbag = {"DVD", "CD", "Stick"};
        System.out.println("Shopping bag contains: "+Arrays.toString(shoppingbag));
        return shoppingbag;
       }

       public String[] addCardReader() {
        String[] shoppingbag = {"DVD", "CD", "Stick", "CardReader"};
        System.out.println("Now shopping bag contains: "+Arrays.toString(shoppingbag));
        return shoppingbag;
    }
}

package test.java;

import static org.junit.Assert.*;
import org.junit.Test;

import main.java.examples.MySuiteUtils;

public class MyTest1 {

     MySuiteUtils shop = new MySuiteUtils();
     String[] shoppingbag = {"DVD", "CD", "Stick"};

     @Test
       public void testBuy() {    
          System.out.println("Inside testBuyItems()");    
          assertArrayEquals(shoppingbag, shop.buyItems());     
       }
}
package test.java;

import org.junit.Test;

import main.java.examples.MySuiteUtils;
import static org.junit.Assert.*;

public class MyTest2 {
       MySuiteUtils shop = new MySuiteUtils();
       String[] shoppingbag = {"DVD", "CD", "Stick", "CardReader"};

       @Test
       public void testAddCardReader() {    
          System.out.println("Inside testAddCardReader()");    
          assertArrayEquals(shoppingbag, shop.addCardReader());     
       }
}
package test.java;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
   MyTest1.class,
   MyTest2.class
})
public class JUnitTestSuite {

}
package test.java;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class JunitTestSuiteRunner {
    public static void main(String[] args) {
  
       Result result = JUnitCore.runClasses(JUnitTestSuite.class);
        for (Failure fail : result.getFailures()) {
            System.out.println(fail.toString());
        }
        if (result.wasSuccessful()) {
            System.out.println("All tests finished successfully...");
        }
    }
}

The result in Eclipse console is:

The result after running the test is:

The classes can be run from command line. If we consider the C:\junit\suite the workspace folder:

C:\junit\suite>javac -classpath "C:\junit\junit-4.11.jar";"C:\junit\junit-master\lib\hamcrest-core-1.3.jar"; MySuiteUtils.java JUnitTestSuite.java  MyTest1.java MyTest2.java JunitTestSuiteRunner.java


C:\junit\suite>java -classpath "C:\junit\junit-4.11.jar";"C:\junit\junit-master\lib\hamcrest-core-1.3.jar"; JunitTestSuiteRunner

The result is:

7.6.1 Wizards for creating JUnit test suites

In Eclipse Test Suites can be created automatically by selecting the test classes that should be included in the Test Suites.

From Package Explorer view > New >Other > Java > JUnit > JUnit Test Suite

Then select the Test classes:

The new created class is:

package test.java;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ MyTest1.class, MyTest2.class })
public class AllTests {

}

Like us on Facebook