Eclipse TestRunner を使用して JUnit 4 ParameterizedTest を実行すると、グラフィカルな表現はかなりばかげています。各テストには[0]
、 、などのノードがあります。テスト、などに明示的な名前[1]
を付けることは可能ですか? テスト用のメソッドを実装しても役に立たないようです。[0]
[1]
toString
(これは動的テスト数による JUnit テストのフォローアップの質問です。)
Eclipse TestRunner を使用して JUnit 4 ParameterizedTest を実行すると、グラフィカルな表現はかなりばかげています。各テストには[0]
、 、などのノードがあります。テスト、などに明示的な名前[1]
を付けることは可能ですか? テスト用のメソッドを実装しても役に立たないようです。[0]
[1]
toString
(これは動的テスト数による JUnit テストのフォローアップの質問です。)
これを行うためにjUnit 4には何も組み込まれていないと思います。
解決策を実行しました。Parameterized
既存のクラスに基づいて独自のクラスを作成しました。
public class MyParameterized extends TestClassRunner {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Parameters {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Name {
}
public static Collection<Object[]> eachOne(Object... params) {
List<Object[]> results = new ArrayList<Object[]>();
for (Object param : params)
results.add(new Object[] { param });
return results;
}
// TODO: single-class this extension
private static class TestClassRunnerForParameters extends TestClassMethodsRunner {
private final Object[] fParameters;
private final Class<?> fTestClass;
private Object instance;
private final int fParameterSetNumber;
private final Constructor<?> fConstructor;
private TestClassRunnerForParameters(Class<?> klass, Object[] parameters, int i) throws Exception {
super(klass);
fTestClass = klass;
fParameters = parameters;
fParameterSetNumber = i;
fConstructor = getOnlyConstructor();
instance = fConstructor.newInstance(fParameters);
}
@Override
protected Object createTest() throws Exception {
return instance;
}
@Override
protected String getName() {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("[%s]", (name == null ? fParameterSetNumber : name));
}
@Override
protected String testName(final Method method) {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("%s[%s]", method.getName(), (name == null ? fParameterSetNumber : name));
}
private Constructor<?> getOnlyConstructor() {
Constructor<?>[] constructors = getTestClass().getConstructors();
assertEquals(1, constructors.length);
return constructors[0];
}
private Method getNameMethod() throws Exception {
for (Method each : fTestClass.getMethods()) {
if (Modifier.isPublic((each.getModifiers()))) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Name.class) {
if (each.getReturnType().equals(String.class))
return each;
else
throw new Exception("Name annotated method doesn't return an object of type String.");
}
}
}
}
return null;
}
}
// TODO: I think this now eagerly reads parameters, which was never the
// point.
public static class RunAllParameterMethods extends CompositeRunner {
private final Class<?> fKlass;
public RunAllParameterMethods(Class<?> klass) throws Exception {
super(klass.getName());
fKlass = klass;
int i = 0;
for (final Object each : getParametersList()) {
if (each instanceof Object[])
super.add(new TestClassRunnerForParameters(klass, (Object[]) each, i++));
else
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fKlass.getName(), getParametersMethod().getName()));
}
}
private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
return (Collection<?>) getParametersMethod().invoke(null);
}
private Method getParametersMethod() throws Exception {
for (Method each : fKlass.getMethods()) {
if (Modifier.isStatic(each.getModifiers())) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Parameters.class)
return each;
}
}
}
throw new Exception("No public static parameters method on class " + getName());
}
}
public MyParameterized(final Class<?> klass) throws Exception {
super(klass, new RunAllParameterMethods(klass));
}
@Override
protected void validate(MethodValidator methodValidator) {
methodValidator.validateStaticMethods();
methodValidator.validateInstanceMethods();
}
}
次のように使用します。
@RunWith(MyParameterized.class)
public class ParameterizedTest {
private File file;
public ParameterizedTest(File file) {
this.file = file;
}
@Test
public void test1() throws Exception {}
@Test
public void test2() throws Exception {}
@Name
public String getName() {
return "coolFile:" + file.getName();
}
@Parameters
public static Collection<Object[]> data() {
// load the files as you want
Object[] fileArg1 = new Object[] { new File("path1") };
Object[] fileArg2 = new Object[] { new File("path2") };
Collection<Object[]> data = new ArrayList<Object[]>();
data.add(fileArg1);
data.add(fileArg2);
return data;
}
}
これは、以前にテスト クラスをインスタンス化したことを意味します。これによりエラーが発生しないことを願っています...テストをテストする必要があると思います:)
JUnit4では、パラメーター化されたアノテーションに name 属性を指定できるようになりました。これにより、引数の index および toString メソッドから命名パターンを指定できます。例えば:
@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 } });
}
それほど快適ではありませんがコードレスの解決策は、assert メッセージでテストを識別するのに十分なコンテキスト情報を渡すことです。まだ testXY[0] が失敗したことが表示されますが、詳細なメッセージでどれが失敗したかがわかります。
assertEquals("Not the expected decision for the senator " + this.currentSenatorName + " and the law " + this.votedLaw,
expectedVote, actualVote);
JUnitParams ライブラリを使用する場合(ここで説明したように)、パラメーター化されたテストには、文字列化されたパラメーターが独自の既定のテスト名として含まれます。
さらに、サンプルでは、JUnitParams を使用してカスタム テスト名を使用できることも確認できます@TestCaseName
。
@Test @Parameters({ "1,1", "2,2", "3,6" }) @TestCaseName("factorial({0}) = {1}") public void custom_names_for_test_case(int argument, int result) { }
@Test @Parameters({ "value1, value2", "value3, value4" }) @TestCaseName("[{index}] {method}: {params}") public void predefined_macro_for_test_case_name(String param1, String param2) { }
この機能が実装されている、または実装されるというヒントはありません。あると便利なので、この機能をリクエストします。