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

JUnit Jupiterユニットテストへのパラメーターの注入

1. Overview

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

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

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)、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行目)。

拡張機能にはscope of influenceがあり、宣言されているwhereに応じて、拡張機能をアクティブにします。

拡張機能は次のいずれかでアクティブになります。

  • メソッドレベル、そのメソッドに対してのみアクティブな場合、または

  • クラスレベル。テストクラス全体でアクティブになります。または、すぐにわかるように、@Nestedテストクラスです。

Note: you should not declare a ParameterResolverat both scopes for the same parameter type, or the JUnit Platform will complain about this ambiguity

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

3. Write the Extensions

ParameterResolver拡張子が何であるかを理解したので、次のように書く準備ができています。

  • validPersonオブジェクト(ValidPersonParameterResolver)を提供するもの、および

  • invalidPersonオブジェクト(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プラットフォームは、パラメーターwithin the current scope of influenceを解決するときに、登録されているすべてのParameterResolversをチェックする必要があります。

@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つのParameterResolversができたので、次にそれらを使用します。 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));
        }
    }
}

@Nestedクラスレベルでのみ宣言することにより、同じメインテストクラス内でValidPersonParameterResolverおよびInvalidPersonParameterResolver拡張機能をどのように使用できるかに注目してください。 JUnit 4で試してみてください! (ネタバレ注意:できません!)

5. 結論

この記事では、有効なオブジェクトと無効なオブジェクトを提供するために、2つのParameterResolver拡張機能を作成する方法について説明しました。 次に、これら2つのParameterResolverの実装を単体テストで使用する方法を確認しました。

いつものように、コードはover on Githubで利用できます。

また、JUnit Jupiter拡張モデルについて詳しく知りたい場合は、JUnit 5 User’s Guideまたはpart 2 of my tutorial on developerWorksを確認してください。