JUnit Jupiter単体テストにパラメータを挿入する

1. 概要

JUnit 5より前は、クールな新機能を導入するために、JUnitチームはコアAPIに対してそれを行う必要がありました。 JUnit 5では、コアJUnit APIをJUnit自体の外部に拡張する機能をプッシュする時期が来たと判断しました。これは、「https://github.com/junit-team/junit5/wiki/Core-Principles」と呼ばれるコアJUnit 5の理念です。[機能よりも拡張ポイントを好む]。

この記事では、テストメソッドにパラメータを挿入するために使用できる拡張ポイントインターフェイスの1つ、 ParameterResolver に焦点を当てます。 JUnitプラットフォームに自分の拡張機能を認識させる方法(「登録」と呼ばれるプロセス)はいくつかありますが、この記事では宣言型の登録(つまり、ソースコードによる登録)に焦点を当てます。

2. ParameterResolver

テストメソッドへのパラメータの注入はJUnit 4 APIを使用して実行できますが、かなり制限されていました。 JUnit 5では、 ParameterResolver を実装することでJupiter APIを拡張し、あらゆる種類のオブジェクトをテストメソッドに提供することができます。みてみましょう。

2.1. FooParameterResolver

public class FooParameterResolver implements ParameterResolver {
  @Override
  public boolean supportsParameter(ParameterContext parameterContext,
    ExtensionContext extensionContext) throws ParameterResolutionException {
      return parameterContext.getParameter().getType() == Foo.class;
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext,
    ExtensionContext extensionContext) throws ParameterResolutionException {
      return new Foo();
  }
}

まず、2つのメソッドを持つ ParameterResolver – を実装する必要があります。

  • supportsParameter() - パラメータの型が

サポートされている(この例ではFoo) ** resolveParamater() - 正しい型のオブジェクトを提供します(新しい

この例のFooインスタンス)はテストメソッドに注入されます。

2.2. FooTest

@ExtendWith(FooParameterResolver.class)
public class FooTest {
    @Test
    public void testIt(Foo fooInstance) {
       //TEST CODE GOES HERE
    }
}

それからエクステンションを使うためには、 @ ExtendWith アノテーション(1行目)でそれを宣言する必要があります。

JUnitプラットフォームが単体テストを実行すると、 FooParameterResolver から Foo インスタンスが取得され、それが__testIt()メソッドに渡されます(4行目)。

エクステンションには 影響範囲 があり、宣言されている場所に応じてエクステンションを有効にします。

内線番号は次のいずれかでアクティブになります。

  • メソッドレベル。そのメソッドに対してのみ有効です。

  • クラスレベル。テストクラス全体で有効です。

まもなく表示されるように @ Nested テストクラス

注:両方のスコープで 同じパラメータータイプ に対してParameterResolverを宣言しないでください。そうしないと、JUnitプラットフォームはこのあいまいさについて不平を言います。

この記事では、 Person オブジェクトをインジェクトするための2つの拡張機能を作成して使用する方法について説明します。このデータを使用して、 PersonValidator というクラスを単体テストし、 Person オブジェクトの状態を検証します。

3. 拡張機能を書く

ParameterResolver エクステンションとは何かを理解したので、次のように書きます。

  • 有効** Person オブジェクトを提供するもの

ValidPersonParameterResolver )、および 無効 Person オブジェクトを提供するもの

InvalidPersonParameterResolver

3.1. ValidPersonParameterResolver

public class ValidPersonParameterResolver implements ParameterResolver {

  public static Person[]VALID__PERSONS = {
      new Person().setId(1L).setLastName("Adams").setFirstName("Jill"),
      new Person().setId(2L).setLastName("Baker").setFirstName("James"),
      new Person().setId(3L).setLastName("Carter").setFirstName("Samanta"),
      new Person().setId(4L).setLastName("Daniels").setFirstName("Joseph"),
      new Person().setId(5L).setLastName("English").setFirstName("Jane"),
      new Person().setId(6L).setLastName("Fontana").setFirstName("Enrique"),
  };

Person オブジェクトの VALID PERSONS 配列に注目してください。これは、 resolveParameter()メソッドがJUnitプラットフォームによって呼び出されるたびにランダムに選択される有効な Person オブジェクトのリポジトリです。

ここに有効なPersonオブジェクトを持つと、2つのことが達成されます。

  1. 単体テストと駆動されるデータとの懸案事項の分離

それ 。他のユニットテストで有効な Person オブジェクトを駆動する必要がある場合は再利用

それら

@Override
public boolean supportsParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    boolean ret = false;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = true;
    }
    return ret;
}

パラメータの型が Person の場合、拡張機能はそのパラメータ型をサポートすることをJUnitプラットフォームに通知します。それ以外の場合は、falseを返し、サポートはしません。

なぜこれが重要なのでしょうか。この記事の例は単純ですが、実際のアプリケーションでは、単体テストクラスは非常に大きく複雑になる可能性があり、さまざまなパラメーター型をとる多くのテストメソッドがあります。 JUnitプラットフォームは、現在の影響範囲内でパラメータを解決するときに、登録されているすべての____ParameterResolverを確認する必要があります。

@Override
public Object resolveParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    Object ret = null;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = VALID__PERSONS[new Random().nextInt(VALID__PERSONS.length)];
    }
    return ret;
}

ランダムな Person オブジェクトが VALID PERSONS__配列から返されます。

supportsParameter() true を返す場合に、 resolveParameter() がJUnitプラットフォームによってのみ呼び出される方法に注意してください。

3.2. InvalidPersonParameterResolver

public class InvalidPersonParameterResolver implements ParameterResolver {
  public static Person[]INVALID__PERSONS = {
      new Person().setId(1L).setLastName("Ad__ams").setFirstName("Jill,"),
      new Person().setId(2L).setLastName(",Baker").setFirstName(""),
      new Person().setId(3L).setLastName(null).setFirstName(null),
      new Person().setId(4L).setLastName("Daniel&").setFirstName("{Joseph}"),
      new Person().setId(5L).setLastName("").setFirstName("English, Jane"),
      new Person()/** .setId(6L).setLastName("Fontana").setFirstName("Enrique")** /,
  };

Person オブジェクトの INVALID PERSONS 配列に注目してください。 ValidPersonParameterResolver と同様に、このクラスには、たとえば PersonValidator.ValidationExceptions__が無効なデータが存在する場合に正しくスローされることを保証するために、単体テストで使用するための「不適切な」(無効な)データのストアが含まれます。

@Override
public Object resolveParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    Object ret = null;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = INVALID__PERSONS[new Random().nextInt(INVALID__PERSONS.length)];
    }
    return ret;
}

@Override
public boolean supportsParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    boolean ret = false;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = true;
    }
    return ret;
}

このクラスの残りの部分は、当然のことながら「良い」対応物とまったく同じように動作します。

4拡張機能を宣言して使用する

これで2つの __ParameterResolverがありましたので、それらを使用する時が来ました。 PersonValidator のための PersonValidatorTest__というJUnitテストクラスを作成しましょう。

JUnit Jupiterでのみ利用可能ないくつかの機能を使用します。

  • @ DisplayName - これはテストレポートに表示される名前です。

はるかに人間が読める ** @ Nested - ネストしたテストクラスを作成し、独自のテストを完成させる

親クラスとは別にライフサイクル ** @ RepeatedTest - テストは指定された回数繰り返されます

value属性による(各例で10)

@ Nested クラスを使用することで、有効なデータと無効なデータの両方を同じテストクラスでテストすることができます。同時に、それらを互いに完全にサンドボックス化することもできます。

@DisplayName("Testing PersonValidator")
public class PersonValidatorTest {

    @Nested
    @DisplayName("When using Valid data")
    @ExtendWith(ValidPersonParameterResolver.class)
    public class ValidData {

        @RepeatedTest(value = 10)
        @DisplayName("All first names are valid")
        public void validateFirstName(Person person) {
            try {
                assertTrue(PersonValidator.validateFirstName(person));
            } catch (PersonValidator.ValidationException e) {
                fail("Exception not expected: " + e.getLocalizedMessage());
            }
        }
    }

    @Nested
    @DisplayName("When using Invalid data")
    @ExtendWith(InvalidPersonParameterResolver.class)
    public class InvalidData {

        @RepeatedTest(value = 10)
        @DisplayName("All first names are invalid")
        public void validateFirstName(Person person) {
            assertThrows(
              PersonValidator.ValidationException.class,
              () -> PersonValidator.validateFirstName(person));
        }
    }
}

同じメインテストクラス内で ValidPersonParameterResolver および InvalidPersonParameterResolver 拡張を使用できることに注目してください - それらを@ Nested クラスレベルでのみ宣言することによって。 JUnit 4で試してみてください。 (ネタバレ注意:あなたはそれができません!)

5.まとめ

この記事では、2つの ParameterResolver 拡張を書く方法を調べました - 有効なオブジェクトと無効なオブジェクトを提供するためです。それから、単体テストでこれら2つの ParameterResolver 実装を使用する方法を調べました。

いつものように、このコードはhttps://github.com/eugenp/tutorials/tree/master/testing-modules/junit-5[over Github]で利用可能です。

そして、JUnit Jupiter拡張モデルについてもっと知りたい場合は、http://junit.org/junit5/docs/current/user-guide/[JUnit 5ユーザーズガイド]またはhttps://wwwを調べてください。 ibm.com/developerworks/library/j-introducing-junit5-part1-jupiter-api/index.html[developerWorksの私のチュートリアルのパート2]。