Changing the Order of your UnitTests

A few months ago we had a problem where Eclipse could not automatically run all jUnit unit tests in a package if that package references a class called “enum”, which is a reserved word in Java 1.6. I’ll spare you the details, but we were forced to create a TestSuite. Normally we avoid this construction because it’s easy to create a new unit test and forget to add it to the correct TestSuit. So as a workaround we wrote some code which could build and return a TestSuite dynamically. Right-click in eclipse, select “Run as Unittest”, sit back and enjoy.

Lately this piece of code came in handy while testing another application, which required the removal of data from a database. Yes I know, Unittests should maybe not depend on databases because it leans towards integration testing, but here we are, and I need to solve it. I used the old TestSuite code and changed it so that the TestCase I needed to run first was singled out, while still maintaining the functionality of auto-detecting testcases in the source folder.

I’ve cleaned up the code and made an example implementation which sorts the test cases in alpabetical order. It’s a simple starting point and definetely not the prettiest code I’ve ever written but it works, it helped me and it might help you. Copy/paste, and adjust to your own needs.

Have fun!

package com.rolfje.example;

import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Constructs a testsuite by dynamically scanning for
 * classes ending in "*Test", and allows for re-ordering of
 * the Classes in the Suite.
 */
public class OrderedTestSuite {

  public static Test suite() throws Exception {

    // Find all test classes
    List testClasses = findTestClasses();

    // Custom test ordering example: Sort test classes in
    // natural alphabetic order based on simple classname
    Collections.sort(testClasses, new Comparator() {
      @Override
      public int compare(Class o1, Class o2) {
        return o1.getSimpleName().compareTo(
            o2.getSimpleName());
      }
    });

    // Convert the Set to a TestSuite
    TestSuite suite = new TestSuite(
        "Custom ordered TestSuite");
    for (Class testClass : testClasses) {
      suite.addTestSuite(testClass);
    }
    return suite;
  }

  private static List findTestClasses()
      throws Exception {
    List testClasses = new ArrayList();

    File testDir = getRootOfTestTree();

    List files = new ArrayList();
    getTestFiles(files, testDir);

    int nameIdx = testDir.getAbsolutePath().length() + 1;
    for (Iterator iterator = files.iterator(); iterator
        .hasNext();) {
      File file = (File) iterator.next();
      String className = file.getAbsolutePath().substring(
          nameIdx);

      className = className
          .replace(File.separatorChar, '.');
      className = className.replaceAll(".class", "");
      Class testClass = Class.forName(className);

      // Prevent recursion
      if (OrderedTestSuite.class.equals(testClass)) {
        continue;
      }

      if (isTestClass(testClass)) {
        testClasses.add(testClass);
      }
    }
    return testClasses;
  }

  /**
   *
   * @param clazz Class to check
   * @return <code>true</code> if the given Class is a
   *         usable implementation of {@link Test}
   */
  private static boolean isTestClass(Class clazz)
      throws Exception {
    int modifiers = clazz.getModifiers();
    if (Modifier.isAbstract(modifiers)
        || Modifier.isInterface(modifiers)
        || Modifier.isPrivate(modifiers)) {
      return false;
    }

    return Test.class.isInstance(clazz.newInstance());
  }

  /**
   * @return The root directory of the Java Test sources.
   */
  private static File getRootOfTestTree() {
    String meAsClasspathResource = OrderedTestSuite.class
        .getResource(
            OrderedTestSuite.class.getSimpleName()
                + ".class").getFile()
        .replace('/', File.separatorChar);
    String myLocation = OrderedTestSuite.class
        .getCanonicalName();
    myLocation = myLocation
        .replace('.', File.separatorChar);

    if (meAsClasspathResource == null
        || !meAsClasspathResource.contains(myLocation)) {
      throw new RuntimeException(
          "Can not find the class resource for "
              + OrderedTestSuite.class.getCanonicalName());
    }

    meAsClasspathResource = meAsClasspathResource
        .substring(0,
            meAsClasspathResource.indexOf(myLocation));
    File dir = new File(meAsClasspathResource);

    if (!dir.exists()) {
      throw new RuntimeException("The directory "
          + dir.getAbsolutePath() + " does not exist.");
    }

    if (!dir.isDirectory()) {
      throw new RuntimeException(dir.getAbsolutePath()
          + " is not a directory.");
    }
    return dir;
  }

  /**
   * Recursively iterates through the nextFiles array to
   * find all test files. which it subsequently returns.
   *
   * @param allTestFilesSoFar
   *          new Files will be added to this List
   * @param nextDir
   *          The directory to scan for java Test files
   */
  private static void getTestFiles(
      List allTestFilesSoFar, File nextDir) {
    File[] files = nextDir.listFiles();
    for (int t = 0; t < files.length; t++) {
      File nextFile = files[t];
      if (nextFile.isDirectory()) {
        getTestFiles(allTestFilesSoFar, nextFile);
      } else if (nextFile.getName().endsWith("Test.class")
          || nextFile.getName().endsWith("Suite.class")) {
        allTestFilesSoFar.add(nextFile);
      }
    }
  }
}
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 29 other followers