パラメーター化されたテストの名前の変更


204

JUnit4でパラメーター化されたテストを使用するときに、独自のカスタムテストケース名を設定する方法はありますか?

デフォルトを— [Test class].runTest[n]意味のあるものに変更したいと思います。

回答:


299

この機能により、JUnit 4.11に組み込まれました。

パラメータ化されたテストの名前を変更するには、次のように言います。

@Parameters(name="namestring")

namestring 文字列であり、次の特別なプレースホルダーを持つことができます。

  • {index}-この引数セットのインデックス。デフォルトnamestring{index}です。
  • {0} -このテストの呼び出しからの最初のパラメーター値。
  • {1} -2番目のパラメーター値
  • 等々

namestring以下に示すように、テストの最終的な名前は、テストメソッドの名前の後にかっこで囲まれたものになります。

例(Parameterized注釈の単体テストから適応):

@RunWith(Parameterized.class)
static public class FibonacciTest {

    @Parameters( name = "{index}: fib({0})={1}" )
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
                { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
    }

    private final int fInput;
    private final int fExpected;

    public FibonacciTest(int input, int expected) {
        fInput= input;
        fExpected= expected;
    }

    @Test
    public void testFib() {
        assertEquals(fExpected, fib(fInput));
    }

    private int fib(int x) {
        // TODO: actually calculate Fibonacci numbers
        return 0;
    }
}

testFib[1: fib(1)=1]およびのような名前を付けますtestFib[4: fib(4)=3]。(名前のtestFib一部はのメソッド名です@Test)。


4
それが4.11にない理由はありません、それはマスターにあります。4.11が利用できるようになったとき、それは良い質問です:-)
Matthew Farwell 2012年

1
4.11は現在ベータ版で、上記と同じリンクからダウンロードできます:-)
rescdsk

2
はい、しかしバグがあります。この投稿のようにパラメーターの「name」値に括弧を入れると、Eclipseでの単体テスト名の表示が壊れます。
djangofan

7
素晴らしいが、何場合{0}{1}配列ですか?JUnitは、理想的にはを呼び出す必要がArrays.toString({0})あり{0}.toString()ます。たとえば、私のdata()メソッドはを返しますArrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});
dogbane 2013年

1
@djangofanこれは8年前のEclipseバグです:bugs.eclipse.org/bugs/show_bug.cgi?id
プール

37

JUnit 4.5を見ると、そのロジックはParameterizedクラス内のプライベートクラス内に埋め込まれているため、そのランナーは明らかにそれをサポートしていません。JUnit Parameterizedランナーを使用することはできません。代わりに、名前の概念を理解する独自のランナーを作成することができます(これにより、名前をどのように設定するかという疑問が生じます...)。

JUnitの観点からは、増分を渡すだけではなく(またはそれに加えて)、コンマ区切りの引数を渡すとよいでしょう。TestNGはこれを行います。この機能が重要な場合は、www.junit.orgで参照されているyahooメーリングリストにコメントできます。


3
JUnitでこれに改善点があれば、私は非常に感謝します!
ゲルダ

17
確認したところ、github.com / KentBeck / junit / issues#issue / 44に未解決の機能リクエストがあります。投票してください。
14:47

8
@フランク、この問題に対処するリリースはまだリリースされていないと思います。JUnit 4.11に含まれる予定です。その時点で(設計が同じであると仮定して)、パラメーターを名前として取るなど、テストに名前を付ける方法をテキストで指定する方法についてです。実際、かなりいいです。
Yishai、2012年

5
JUnit 4.11がリリースされました:-)
rescdsk

7
ここでは、元の発行に更新されたリンクですgithub.com/junit-team/junit/issues/44将来の参照のために
kldavis4

20

最近、JUnit 4.3.1を使用しているときに同じ問題に遭遇しました。LabelledParameterizedというParameterizedを拡張する新しいクラスを実装しました。JUnit 4.3.1、4.4、4.5を使用してテストされています。@Parametersメソッドからの各パラメーター配列の最初の引数の文字列表現を使用してDescriptionインスタンスを再構築します。このコードは次の場所にあります。

http://code.google.com/p/migen/source/browse/trunk/java/src/.../LabelledParameterized.java?r=3789

とその使用例:

http://code.google.com/p/migen/source/browse/trunk/java/src/.../ServerBuilderTest.java?r=3789

テストの説明はEclipseで適切にフォーマットされます。これにより、失敗したテストを簡単に見つけられるようになりました。今後数日/数週間にわたって、クラスをさらに洗練して文書化する予定です。落とす '?' 最先端の機能が必要な場合は、URLの一部。:-)

それを使用するには、そのクラス(GPL v3)をコピーし、@ RunWith(Parameterized.class)を@RunWith(LabelledParameterized.class)に変更するだけで、パラメーターリストの最初の要素が適切なラベルであると想定できます。

JUnitの今後のリリースでこの問題に対処するかどうかはわかりませんが、それらの問題が解決しても、共同開発者全員が更新する必要があり、再ツールよりも優先度が高いため、JUnitを更新できません。したがって、クラスの作業は、JUnitの複数のバージョンでコンパイル可能です。


注:上記のようにJUnitのさまざまなバージョンにまたがって実行されるように、いくつかのリフレクションジッケリーポケリーがあります。JUnit 4.3.1専用のバージョンはこちら、JUnit 4.4および4.5 のバージョンはこちらにあります


:-)上記のメッセージで指摘したバージョンがJUnit 4.3.1(最初に言った4.4ではない)を使用しているため、今日の私の共同開発者の1人が問題を抱えていました。彼はJUnit 4.5.0を使用しており、問題が発生しました。本日はこれらについて説明します。
ダレンプ

コンストラクタでテスト名を渡す必要があることを理解するのに少し時間がかかりましたが、覚えておく必要はありません。コードをありがとう!
キリン

私がEclipseからテストを実行する限り、うまく機能します。JUnit Antタスクで動作させる経験はありますか?テストレポートはexecute[0], execute[1] ... execute[n]、生成されたテストレポートで名前が付けられます。
Henrik AastedSørensen2012

非常に素晴らしい。魅力のように機能します。情報を追加できたら、呼び出された@ Test-methodの最初のパラメータとして「文字列ラベル、...」を追加する必要があると便利です。
ギア2013

13

Parameterizedモデルとして、私は私自身のカスタムテストランナー/スイートを書いた-だけで約30分かかりました。LabelledParameterized最初のパラメーターに依存するのではなく、明示的に名前を指定できるという点で、darrenpとは少し異なりますtoString()

また、配列が嫌いなため、配列も使用しません。:)

public class PolySuite extends Suite {

  // //////////////////////////////
  // Public helper interfaces

  /**
   * Annotation for a method which returns a {@link Configuration}
   * to be injected into the test class constructor
   */
  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.METHOD)
  public static @interface Config {
  }

  public static interface Configuration {
    int size();
    Object getTestValue(int index);
    String getTestName(int index);
  }

  // //////////////////////////////
  // Fields

  private final List<Runner> runners;

  // //////////////////////////////
  // Constructor

  /**
   * Only called reflectively. Do not use programmatically.
   * @param c the test class
   * @throws Throwable if something bad happens
   */
  public PolySuite(Class<?> c) throws Throwable {
    super(c, Collections.<Runner>emptyList());
    TestClass testClass = getTestClass();
    Class<?> jTestClass = testClass.getJavaClass();
    Configuration configuration = getConfiguration(testClass);
    List<Runner> runners = new ArrayList<Runner>();
    for (int i = 0, size = configuration.size(); i < size; i++) {
      SingleRunner runner = new SingleRunner(jTestClass, configuration.getTestValue(i), configuration.getTestName(i));
      runners.add(runner);
    }
    this.runners = runners;
  }

  // //////////////////////////////
  // Overrides

  @Override
  protected List<Runner> getChildren() {
    return runners;
  }

  // //////////////////////////////
  // Private

  private Configuration getConfiguration(TestClass testClass) throws Throwable {
    return (Configuration) getConfigMethod(testClass).invokeExplosively(null);
  }

  private FrameworkMethod getConfigMethod(TestClass testClass) {
    List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Config.class);
    if (methods.isEmpty()) {
      throw new IllegalStateException("@" + Config.class.getSimpleName() + " method not found");
    }
    if (methods.size() > 1) {
      throw new IllegalStateException("Too many @" + Config.class.getSimpleName() + " methods");
    }
    FrameworkMethod method = methods.get(0);
    int modifiers = method.getMethod().getModifiers();
    if (!(Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
      throw new IllegalStateException("@" + Config.class.getSimpleName() + " method \"" + method.getName() + "\" must be public static");
    }
    return method;
  }

  // //////////////////////////////
  // Helper classes

  private static class SingleRunner extends BlockJUnit4ClassRunner {

    private final Object testVal;
    private final String testName;

    SingleRunner(Class<?> testClass, Object testVal, String testName) throws InitializationError {
      super(testClass);
      this.testVal = testVal;
      this.testName = testName;
    }

    @Override
    protected Object createTest() throws Exception {
      return getTestClass().getOnlyConstructor().newInstance(testVal);
    }

    @Override
    protected String getName() {
      return testName;
    }

    @Override
    protected String testName(FrameworkMethod method) {
      return testName + ": " + method.getName();
    }

    @Override
    protected void validateConstructor(List<Throwable> errors) {
      validateOnlyOneConstructor(errors);
    }

    @Override
    protected Statement classBlock(RunNotifier notifier) {
      return childrenInvoker(notifier);
    }
  }
}

そして例:

@RunWith(PolySuite.class)
public class PolySuiteExample {

  // //////////////////////////////
  // Fixture

  @Config
  public static Configuration getConfig() {
    return new Configuration() {
      @Override
      public int size() {
        return 10;
      }

      @Override
      public Integer getTestValue(int index) {
        return index * 2;
      }

      @Override
      public String getTestName(int index) {
        return "test" + index;
      }
    };
  }

  // //////////////////////////////
  // Fields

  private final int testVal;

  // //////////////////////////////
  // Constructor

  public PolySuiteExample(int testVal) {
    this.testVal = testVal;
  }

  // //////////////////////////////
  // Test

  @Ignore
  @Test
  public void odd() {
    assertFalse(testVal % 2 == 0);
  }

  @Test
  public void even() {
    assertTrue(testVal % 2 == 0);
  }

}

6

junit4.8.2以降では、Parameterizedクラスをコピーするだけで、独自のMyParameterizedクラスを作成できます。TestClassRunnerForParametersのgetName()およびtestName()メソッドを変更します。


私はこれを試しましたが、助けにはなりません。新しいクラスの作成中にgetParametersMethodが失敗します。
java_enthu


2

次のようなメソッドを作成できます

@Test
public void name() {
    Assert.assertEquals("", inboundFileName);
}

私はいつもそれを使用するわけではありませんが、143というテスト番号がどれであるかを正確に理解することは役に立ちます。


2

私はアサートや友人のために静的インポートを広範囲に使用しているので、アサーションを再定義するのは簡単です:

private <T> void assertThat(final T actual, final Matcher<T> expected) {
    Assert.assertThat(editThisToDisplaySomethingForYourDatum, actual, expected);
}

たとえば、「名前」フィールドをテストクラスに追加し、コンストラクターで初期化して、テストが失敗したときに表示することができます。各テストのパラメーター配列の最初の要素として渡すだけです。これは、データのラベル付けにも役立ちます。

public ExampleTest(final String testLabel, final int one, final int two) {
    this.testLabel = testLabel;
    // ...
}

@Parameters
public static Collection<Object[]> data() {
    return asList(new Object[][]{
        {"first test", 3, 4},
        {"second test", 5, 6}
    });
}

これは、テストがアサートに失敗した場合は問題ありませんが、例外がスローされてテストが失敗した場合や、テストが例外のスローを予期している場合など、名前のオーバーヘッドを考慮する必要があります。フレームワークによって処理されます。
Yishai、2014年

2

どれも私にとってはうまくいかなかったので、Parameterizedのソースを入手して変更し、新しいテストランナーを作成しました。それほど変更する必要はありませんでしたが、ITは機能しました!!!

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.internal.runners.ClassRoadie;
import org.junit.internal.runners.CompositeRunner;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.MethodValidator;
import org.junit.internal.runners.TestClass;
import org.junit.runner.notification.RunNotifier;

public class LabelledParameterized extends CompositeRunner {
static class TestClassRunnerForParameters extends JUnit4ClassRunner {
    private final Object[] fParameters;

    private final String fParameterFirstValue;

    private final Constructor<?> fConstructor;

    TestClassRunnerForParameters(TestClass testClass, Object[] parameters, int i) throws InitializationError {
        super(testClass.getJavaClass()); // todo
        fParameters = parameters;
        if (parameters != null) {
            fParameterFirstValue = Arrays.asList(parameters).toString();
        } else {
            fParameterFirstValue = String.valueOf(i);
        }
        fConstructor = getOnlyConstructor();
    }

    @Override
    protected Object createTest() throws Exception {
        return fConstructor.newInstance(fParameters);
    }

    @Override
    protected String getName() {
        return String.format("%s", fParameterFirstValue);
    }

    @Override
    protected String testName(final Method method) {
        return String.format("%s%s", method.getName(), fParameterFirstValue);
    }

    private Constructor<?> getOnlyConstructor() {
        Constructor<?>[] constructors = getTestClass().getJavaClass().getConstructors();
        Assert.assertEquals(1, constructors.length);
        return constructors[0];
    }

    @Override
    protected void validate() throws InitializationError {
        // do nothing: validated before.
    }

    @Override
    public void run(RunNotifier notifier) {
        runMethods(notifier);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Parameters {
}

private final TestClass fTestClass;

public LabelledParameterized(Class<?> klass) throws Exception {
    super(klass.getName());
    fTestClass = new TestClass(klass);

    MethodValidator methodValidator = new MethodValidator(fTestClass);
    methodValidator.validateStaticMethods();
    methodValidator.validateInstanceMethods();
    methodValidator.assertValid();

    int i = 0;
    for (final Object each : getParametersList()) {
        if (each instanceof Object[])
            add(new TestClassRunnerForParameters(fTestClass, (Object[]) each, i++));
        else
            throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fTestClass.getName(), getParametersMethod().getName()));
    }
}

@Override
public void run(final RunNotifier notifier) {
    new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() {
        public void run() {
            runChildren(notifier);
        }
    }).runProtected();
}

private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
    return (Collection<?>) getParametersMethod().invoke(null);
}

private Method getParametersMethod() throws Exception {
    List<Method> methods = fTestClass.getAnnotatedMethods(Parameters.class);
    for (Method each : methods) {
        int modifiers = each.getModifiers();
        if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
            return each;
    }

    throw new Exception("No public static parameters method on class " + getName());
}

public static Collection<Object[]> eachOne(Object... params) {
    List<Object[]> results = new ArrayList<Object[]>();
    for (Object param : params)
        results.add(new Object[] { param });
    return results;
}
}

2

回避策は、パラメーターに関するすべての情報を含むカスタムメッセージを使用して、すべてのThrowableをキャッチし、新しいThrowableにネストすることです。メッセージはスタックトレースに表示されます。 これらはすべてThrowableのサブクラスであるため、すべてのアサーション、エラー、および例外のテストが失敗した場合に機能します。

私のコードは次のようになります:

@RunWith(Parameterized.class)
public class ParameterizedTest {

    int parameter;

    public ParameterizedTest(int parameter) {
        super();
        this.parameter = parameter;
    }

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] { {1}, {2} });
    }

    @Test
    public void test() throws Throwable {
        try {
            assertTrue(parameter%2==0);
        }
        catch(Throwable thrown) {
            throw new Throwable("parameter="+parameter, thrown);
        }
    }

}

失敗したテストのスタックトレースは次のとおりです。

java.lang.Throwable: parameter=1
    at sample.ParameterizedTest.test(ParameterizedTest.java:34)
Caused by: java.lang.AssertionError
    at org.junit.Assert.fail(Assert.java:92)
    at org.junit.Assert.assertTrue(Assert.java:43)
    at org.junit.Assert.assertTrue(Assert.java:54)
    at sample.ParameterizedTest.test(ParameterizedTest.java:31)
    ... 31 more

0

dsaffが述べたようにJUnitParamsをチェックしてください。antを使用して、htmlレポートでパラメーター化されたテストメソッドの説明を作成します。

これは、LabelledParameterizedを試してみて、Eclipseで動作するものの、htmlレポートに関する限り、antでは動作しないことを発見した後です。

乾杯、


0

アクセスされるパラメーター(たとえば、"{0}"常にtoString()表現が返されるため)の1つの回避策は、匿名の実装を作成し、toString()それぞれのケースでオーバーライドすることです。次に例を示します。

public static Iterable<? extends Object> data() {
    return Arrays.asList(
        new MyObject(myParams...) {public String toString(){return "my custom test name";}},
        new MyObject(myParams...) {public String toString(){return "my other custom test name";}},
        //etc...
    );
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.