1. 概要

JUnit 5より前は、クールな新機能を導入するために、JUnitチームはコアAPIに対してそれを行う必要がありました。 JUnit 5を使用して、チームは、コアJUnitAPIをJUnit自体の外部に拡張する機能をプッシュする時期が来たと判断しました。これは「機能よりも拡張ポイントを優先する」と呼ばれるJUnit5のコア哲学です。

この記事では、これらの拡張ポイントインターフェイスの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();
  }
}

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

  • supportedsParameter() –パラメーターのタイプがサポートされている場合(この例ではFoo)、trueを返します。
  • resolveParamater() –正しいタイプのオブジェクト(この例では新しいFooインスタンス)を提供し、テストメソッドに挿入されます

2.2. FooTest

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

次に、拡張機能を使用するには、 @ExtendWith アノテーションを介して拡張機能を宣言する必要があります(つまり、JUnitプラットフォームに拡張機能について通知します)(1行目)。

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

拡張機能には影響範囲があり、宣言されている場所に応じて拡張機能がアクティブになります。

拡張機能は、次のいずれかでアクティブになっている可能性があります。

  • メソッドレベル。そのメソッドに対してのみアクティブであるか、または
  • クラスレベル。テストクラス全体でアクティブになります。または、@Nestedテストクラスですぐに確認できます。

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

この記事では、 Person オブジェクトを挿入するための2つの拡張機能を記述して使用する方法を説明します。1つは「良い」データ( ValidPersonParameterResolver と呼ばれる)を挿入し、もう1つは「悪い」データを挿入します。データ( InvalidPersonParameterResolver )。 このデータを使用して、 PersonValidator というクラスを単体テストします。このクラスは、Personオブジェクトの状態を検証します。

3. 拡張機能を書く

ParameterResolver 拡張機能が何であるかを理解したので、次のように記述します。

  • valid Person オブジェクト( ValidPersonParameterResolver )を提供するもの、および
  • invalid 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配列に注意してください。 これは、有効な Person オブジェクトのリポジトリであり、JUnitプラットフォームによって resolveParameter()メソッドが呼び出されるたびにランダムに選択されます。

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

  1. 単体テストとそれを駆動するデータ間の関心の分離
  2. 他の単体テストでそれらを駆動するために有効な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配列から返されます。 resolveParameter()は、 supportsParameter()trueを返す場合にのみ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ができたので、次にそれらを使用します。 PersonValidatorTestというPersonValidatorのJUnitテストクラスを作成しましょう。

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

  • @ 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実装を単体テストで使用する方法を確認しました。

いつものように、コードはGithubから入手できます。

また、JUnit Jupiter拡張モデルについて詳しく知りたい場合は、 JUnit 5ユーザーズガイド、またはdeveloperWorksに関するチュートリアルのパート2を確認してください。