ひ孫

犬のこととか書いていきたい

Junit4でテストの順序を制御する

例えばinsertTestをしたあとにdeleteTestをするようなことをしたいなーというようなとき、JUnit3ではTestSuiteで行えた*1

junit3なら

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

public class TestOrderForJunit3 extends TestCase {

    public static Test suite() {
        TestSuite suite = new TestSuite();

        suite.addTest(new TestOrderForJunit3().new DbTest("testInsert"));
        suite.addTest(new TestOrderForJunit3().new DbTest("testDelete"));

        return suite;
    }

    public class DbTest extends TestCase {

        public DbTest(String name) {
            setName(name);
        }

        public void testInsert() {
            System.out.print("insert");
        }

        public void testDelete() {
            System.out.print("delete");
        }
    }
}

こんな感じ。
しかしこれをJUnit4のTestSuiteに置き換えようとしたとき、クラス単位で順序制御をするようなやり方はあったが
メソッド単位での制御を制御するやり方は見つからなかった。
なので最初に考えたのは1クラス1テストのクラスを作るやり方。

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

@RunWith(Suite.class)
@SuiteClasses({
    TestOrderForJunit4.InsertTest.class,
    TestOrderForJunit4.DeleteTest.class,})
public class TestOrderForJunit4 {

    public static class DeleteTest {

        @Test
        public void testDelete() {
            System.out.println("delete");
        }
    }

    public static class InsertTest {

        @Test
        public void test2() {
            System.out.println("insert");
        }
    }
}

こんな感じ。
内部的にクラスを持たせてそれをSuiteClassesで呼び出すようなことをしたら出来た。
どうも内部のclassはstaticでないといけない模様。
なんだかスマートじゃないのでもうちょっと調べてみたらStackoverflowにそのものずばりなやり方があった

http://stackoverflow.com/questions/3089151/specifying-an-order-to-junit-4-tests-at-the-method-level-not-class-level

上のよりも下の@Orderのアノテーションを自作するのがとても良さ気
以下コード抜粋。

まずアノテーションになるクラスを作る
Order.java

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
    public int order();
}

*2

で、それの順序制御をするクラス
OrderedRunner.java

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

public class OrderedRunner extends BlockJUnit4ClassRunner {
    public OrderedRunner(Class<?> klass) throws InitializationError  {
        super(klass);
    }

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> list = super.computeTestMethods();
        Collections.sort(list, new Comparator<FrameworkMethod>() {
            @Override
            public int compare(FrameworkMethod f1, FrameworkMethod f2) {
                Order o1 = f1.getAnnotation(Order.class);
                Order o2 = f2.getAnnotation(Order.class);
                
                if (o1 == null || o2 == null){
                    return -1;   
                }
                return o1.order() - o2.order();
            }
        });
        return list;
    }
}

*3

とこんなふうに準備をして

@RunWith(OrderedRunner.class)
public class TestUseOdered {
    
    @Test
    @Order(order=2)
    public void deleteTest(){
        System.out.println("delete");
    }
    
    @Test
    @Order(order=1)
    public void insertTest(){
        System.out.println("insert");
    }
}

とすると@Orderで指定した順序が若い方から実行される。素敵!

*1:そもそも順序を気にするようなテスト書くなという話はあるけども。今回はまあひとつ方法をやってみるという話

*2:アノテーションってこんなふうに自作するんですね

*3:InitializationErrorはなんか2つぐらいクラスがあるみたいなので最初ハマった。