技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

JUnit:テストケース毎の初期化

JUnitで複数のテストを行う際、あるテストが以降のテストに影響を与えてしまうことがあります。
あるテストが以降のテストに影響を与えると、テストの実行順を変えたりテストケースを変えたりするだけで今まで通っていたテストが通らなくなるので、望ましい状況ではありません。
今回は簡単な例を挙げて、対処法を紹介します。
 
【テスト対象のプログラム】
テスト対象は以下のプログラムである。
"HelloWorld"を返すのが正しい仕様だが、以前の実行で内部変数が変化すると"HelloKitty"を返すことがある、というのを表現している。
(なお、以前のテストが影響を与える例としては、内部変数の変化の他に、インプットのファイル・テーブルの変更が考えられる)
 
package jp.co.hello;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class HelloWorld
 * WebServletとしたことに特に意味は無い。
 */
@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {
    static int flg = 0;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWorld() {
        super();
    }

    public String Hello() {
        if(flg==0) {
            flg=1;
            return "HelloWorld";
        } else {
            return "HelloKitty";
        }
    }
}
 
【テストクラス】
HelloTest.class→HelloTest2.classの順番に実行する。
HelloTest.classのテストメソッドは2つ、HelloTest2.classのテストメソッドは1つであり、何れのテストメソッドも"HelloWorld"が返却されることを期待する。
 
・HelloTestAll.java
package jp.co.hello;

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

@RunWith(Suite.class)
@SuiteClasses({
    HelloTest.class,
    HelloTest2.class
})

public class HelloTestAll {

}
 
・HelloTest.java
package jp.co.hello;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Test;

public class HelloTest {

    @Test
    public void testHello() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
    @Test
    public void testHello2() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
}
 
・HelloTest2.java
package jp.co.hello;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Test;

public class HelloTest2 {

    @Test
    public void testHello() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
}
 
【対応前のテスト結果】
HelloTest.classの1つ目のテストメソッドで内部変数が変化することで、以降のテストメソッドでは"HelloKitty"を返却するようになる。
その結果、HelloTest.classの2つ目のテストメソッド、及びHelloTest2.classのテストメソッドが失敗する。
 
【対応】
一つのテストメソッドが完了する度に、@Afterアノテーションを付与したメソッドで初期化処理を行う。
(なお、一つのテストメソッドが開始する前に初期化処理を行う場合は@Beforeアノテーション、一つのテストクラスが開始/終了する度に初期化処理を行う場合は@BeforeClassアノテーション/@AfterClassアノテーションを使用する)
 
以下のコードをHelloTest.class、HelloTest2.classに追加する。
 
    @After
    public void doAfter() {
     HelloWorld.flg=0;
    }
 
【対応後のテスト結果】
テストメソッドが完了する度に内部変数が初期化されるため、何れのテストも成功するようになる。