web-dev-qa-db-ja.com

JUnit4.11で@ClassRuleと@Ruleを組み合わせる

JUnit 4.10以下では、ルールに@Ruleと@ClassRuleの両方のアノテーションを付けることができます。これは、ルールがクラスの前後、および各テストの前後に呼び出されることを意味します。これを行う理由の1つは、高価な外部リソースを(@ClassRule呼び出しを介して)セットアップしてから、(@ Rule呼び出しを介して)安価にリセットすることです。

JUnit 4.11以降、@ Ruleフィールドは非静的である必要があり、@ ClassRuleフィールドは静的である必要があるため、上記は不可能になります。

明らかに回避策があります(たとえば、@ ClassRuleと@Ruleの責任を別々のルールに明示的に分離する)が、2つのルールの使用を義務付ける必要があるのは残念なことのようです。 @Ruleを使用して、最初/最後のテストかどうかを簡単に調べましたが、情報が利用できるとは思いません(少なくとも、Descriptionで直接利用することはできません)。

JUnit 4.11の単一のルールで@ClassRuleと@Ruleの機能を組み合わせるためのきちんとした方法はありますか?

ありがとう、ローワン

21
Rowan

JUnit 4.12(執筆時点ではリリースされていません)以降、単一の静的ルールに@Rule@ClassRuleの両方で注釈を付けることが可能になります。

静的である必要があることに注意してください。@Ruleおよび@ClassRuleで注釈が付けられた非静的ルールは、引き続き無効と見なされます(@ClassRuleで注釈が付けられたものはすべてクラスレベルで機能するため、実際には次のようにのみ意味があります。静的メンバー)。

詳細については、 リリースノート および プルリクエスト を参照してください。

13
Rowan

別の可能な回避策は、静的@ClassRuleを宣言し、非静的@Ruleを宣言するときにもその値を使用することです。

@ClassRule
public static BeforeBetweenAndAfterTestsRule staticRule = new BeforeBetweenAndAfterTestsRule();
@Rule
public BeforeBetweenAndAfterTestsRule rule = staticRule;

つまり、既存のルールクラスをリファクタリングする必要はありませんが、2つのルールを宣言する必要があるため、元の質問に特にうまく答えることはできません。

6
Rowan

さらに別の可能な回避策は、非静的@Ruleを宣言し、静的コラボレーターに作用させることです。コラボレーターがまだ初期化されていない場合、@ Ruleは、それが初めて実行されていることを認識します(したがって、コラボレーターをセットアップできます。例:外部リソースの開始);それらが初期化されている場合、@ Ruleはテストごとの作業(外部リソースのリセットなど)を実行できます。

これには、@ Ruleが最後のテストをいつ処理したかがわからないため、クラス後のアクション(外部​​リソースの整理など)を実行できないという欠点があります。ただし、代わりに@AfterClassメソッドを使用してこれを行うことができます。

2
Rowan

あなたの質問に対する答えは次のとおりです:それを行うためのクリーンな方法はありません(2つのルールを同時に設定するだけです)。自動テスト再試行のために同様のタスクを実装しようとしましたが、2つのルールが組み合わされ(このタスクのために)、そのような醜いアプローチが実装されました: テストは2つのルールで再試行します

しかし、必要なタスク(実装する必要がある)についてより正確に考える場合は、jUnitカスタムランナーを使用してより良いアプローチを使用できます: ランナーの再試行

したがって、より良いアプローチをとるためには、特定のユースケースを知っておくとよいでしょう。

2
user1459144

私は同様の問題を経験しましたが、2つの回避策があります。どちらも好きではありませんが、トレードオフが異なります。

1)ルールでクリーンアップメソッドが公開されている場合は、@Beforeメソッド内で手動でクリーンアップを呼び出すことができます。

@ClassRule
public static MyService service = ... ;

@Before
public void cleanupBetweenTests(){
     service.cleanUp();
}

これの欠点は、常にその@Beforeメソッドを追加するか、クリーンアップを行うためにテストが継承する抽象クラスを作成することを覚えておく(そしてチームの他の人に伝える)必要があることです。

2)2つのフィールドがあります。1つは静的、もう1つは同じオブジェクトを指す非静的で、各フィールドにはそれぞれ@ClassRuleまたは@Ruleの注釈が付けられています。これは、クリーンアップが公開されていない場合に必要です。もちろん、欠点は、@ ClassRuleと@Ruleの両方が奇妙に見える同じものを指すようにすることも忘れないでください。

 @ClassRule
 public static MyService service = ... ;

@Rule
public MyService tmp = service ;

次に、実装では、テストスイートと単一のテストを区別する必要があります。これは、Descriptionに子があるかどうかを確認することで実行できます。どちらに応じて、クリーンアップを処理するかどうかに応じて、さまざまなStatementアダプタを作成します。

@Override
protected void after() {

    //class-level shut-down service
    shutdownService();
}


@Override
protected void before() {

    //class-level init service
    initService();
}

@Override
public Statement apply(Statement base, Description description) {

        if(description.getChildren().isEmpty()){
            //test level perform cleanup

            return new CleanUpStatement(this,base);
        }
        //suite level no-change
        return super.apply(base, description);

    }

各テストの前にクリーンアップするカスタムStatementクラスは次のとおりです。

private static final class CleanUpStatement extends Statement{
        private final MyService service;

        private final Statement statement;



        CleanUpStatement(MyService service, Statement statement) {
            this.service = service;
            this.statement = statement;
        }



        @Override
        public void evaluate() throws Throwable {
            //clear messages first
            myService.cleanUp();
            //now evaluate wrapped statement
            statement.evaluate();
        }

    }

それがすべて終わった後、私はオプション1にもっと傾倒します。なぜなら、それはより意図的に明らかにし、維持するコードが少ないからです。同じフィールドが2回ポイントされているため、他の人がオプション2のコードを変更しようとしてバグがあったと思っていることも心配です。余分な努力、コードコメントなどはそれだけの価値はありません。

いずれにせよ、テンプレートメソッドを使用してクラスをどこにでもコピーして貼り付けるか、クラスを抽象化するボイラープレートがまだあります。

1
dkatzel