サーバのバッチ処理等で定期的に自動でJUnitを実行するためのメモです。JUnitのテストはGUIで実行すれば視覚的にわかりやすく、コマンドベースでも実行可能です。ただしこれらのテスト結果は、対人間のためのものであり、対プログラムというわけではありません。この記事では、JUnitのテスト結果をプログラムに対して伝える方法を説明します。

なお、本記事ではJUnit 3.8(junit-3.8.2.jar)の利用を想定しています。JUnit 4以降は異なる部分もありますので、ご注意ください。


例えば以下のようなJUnitのテストコードがあったとします。

//========== JUnit 3.8.2 ======================================
public void testA () {
    assertEquals("testA", "0", "0");
}
public void testB () {
    assertEquals("testB", "a", "b");
}
public void testC () {
    throw new RuntimeException("Exception");
}

 
このテストコードをコマンドベースでJUnitで実行してみます。

//========== JUnit 3.8.2 ======================================
java -classpath .;junit-3.8.1.jar junit.textui.TestRunner
junit.JUnitSampleTest
.F.E.
Time: 0.002
There was 1 error:
1) testC(junit.JUnitSampleTest)java.lang.RuntimeException: Exception
        at junit.JUnitSampleTest.testC(JUnitSampleTest.java:31)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
There was 1 failure:
1) testB(junit.JUnitSampleTest)junit.framework.ComparisonFailure: testB expected
:<a> but was:<b>
        at junit.JUnitSampleTest.testB(JUnitSampleTest.java:28)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

FAILURES!!!
Tests run: 3,  Failures: 1,  Errors: 1

 
このようなテスト結果は人間が目で見ればわかりますが、プログラムがこの結果を解析し、成否を判断するのは非常に難しいです。

これを解決する方法は単純です。JUnitはJavaで実装されており、そのjarファイルの中にはテストの判定するためのクラスが含まれます。つまり、その判定クラスを使うプログラムを組んでやれば、30分毎にテストを実行し、テスト結果が不正な場合はメールを送信することなども可能となります。

//========== JUnit 3.8.2 ======================================
//テストクラスのインスタンスを生成
TestCase test = new JUnitテストクラス();
//テストメソッドの指定
test.setName(method);

//テストの実行
TestResult result = test.run();
//テストの成否
System.out.println(result.wasSuccessful());

//Assertメソッドで失敗した内容を取得
Enumeration failures = result.failures();
while(failures.hasMoreElements()){
    TestFailure failure = (TestFailure)failures.nextElement();
    System.out.println("fail: " + failure.exceptionMessage());
}

//発生したExceptionの内容を取得
Enumeration errors = result.errors();
while(errors.hasMoreElements()){
    TestFailure error = (TestFailure)errors.nextElement();
    System.out.println("error: " + error.exceptionMessage());
}

 
実行結果

true
false
fail: testB expected:<a> but was:<b>
false
error: Exception

 
面倒ですがテストクラスの他にテストメソッドも指定してテストを実行する必要があります。この辺は少し作り込めばTestCaseを継承するtestから始まるメソッドを実行するプログラムを作ることも可能です。

このままではテストメソッド毎に実装しなければならないため、もう少し汎用的に使えるようにしてみます。

/**
 * JUnitのテストを実行します。
 * @param classPath テストクラスのパス
 * @param method テストメソッド
 * @throws Exception
 */
public void run(String classPath, String method) throws Exception {
    Class c = Class.forName(classPath);
    Constructor constructor = c.getConstructor(new Class[]{});
    TestCase test = (TestCase)constructor.newInstance(new Object[]{});
    test.setName(method);

    TestResult result = test.run();
    System.out.println(result.wasSuccessful());
    Enumeration failures = result.failures();

    while(failures.hasMoreElements()){
        TestFailure failure = (TestFailure)failures.nextElement();
        System.out.println("fail: " + failure.exceptionMessage());
    }

    Enumeration errors = result.errors();
    while(errors.hasMoreElements()){
        TestFailure error = (TestFailure)errors.nextElement();
        System.out.println("error: " + error.exceptionMessage());
    }
}

/**
 * JUnitのテストを実行します。
 * @param classPath テストクラスのパス
 * @throws Exception
 */
public void run(String classPath) throws Exception {
    Class c = Class.forName(classPath);
    Method[] methods = c.getMethods();
    for (int i = 0; i < methods.length; i++) {
        // "test"から始まるメソッドのみを対象
        if (methods[i].getName().startsWith("test")) {
            // 引数無しのメソッド、指定されたクラスで宣言されたメソッドのみを対象
            if (methods[i].getParameterTypes().length == 0 &&
                    methods[i].getDeclaringClass() == c) {
                run(classPath, methods[i].getName());
            }
        }
    }
}

public void junit() {
    run("xxx.xxx.Xxx");
    run("xxx.xxx.Xxx", "testXxxx");
}

 
このように実装すると、指定したJUnitのTestCaseを継承したクラスの "test" から始まるメソッドをテストを実行することができます。

TestCase
void setName (String name)
          実行するメソッドを設定します。
TestResult run ()
          テストを実行します。
TestResult
boolean wasSuccessful ()
          テストを実行した結果を取得します。
java.util.Enumeration failures ()
          Assert で失敗した内容(TestFailure)を取得します。
java.util.Enumeration errors ()
          発生した Exception の内容(TestFailure)を取得します。
TestFailure
String exceptionMessage ()
          失敗した内容のメッセージを取得します。


Leave a Reply

preload preload preload