更改参数化testing的名称

在JUnit4中使用参数化testing时,有没有办法设置我自己的自定义testing用例名称?

我想改变默认 – [Test class].runTest[n] – 有意义的。

这个特性已经成为JUnit 4.11 。

要使用更改参数化testing的名称,请说:

 @Parameters(name="namestring") 

namestring是一个string,它可以有以下特殊的占位符:

  • {index} – 这组参数的索引。 默认的namestring{index}
  • {0} – 这个调用的第一个参数值。
  • {1} – 第二个参数值
  • 等等

testing的最终名称将是testing方法的名称,接着是括号中的名称namestring ,如下所示。

例如(根据Parameterized注释的unit testing进行调整):

 @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的方法名称)。

看看JUnit 4.5,它的运行者显然不支持,因为这个逻辑被隐藏在Parameterized类的私有类中。 你不能使用JUnit参数化的runner,而是创build你自己的名字来理解名字的概念(这会导致你如何设置一个名字的问题)。

从JUnit的angular度来看,如果不是(或者除了)传递一个增量,它们会传递逗号分隔的参数。 TestNG做到这一点。 如果该function对您很重要,您可以在www.junit.org上提到的yahoo邮件列表发表评论。

我最近在使用JUnit 4.3.1时遇到了同样的问题。 我实现了一个扩展Parameterized的新类,名为LabelledParameterized。 它已经使用JUnit 4.3.1,4.4和4.5进行了testing。 它使用@Parameters方法中每个参数数组的第一个参数的string表示重构了描述实例。 你可以看到这个代码:

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中testing描述格式很好,这是我想要的,因为这使得失败的testing更容易find! 我可能会在接下来的几天/几周内进一步完善和logging这些课程。 放下'?' 部分url,如果你想stream血的边缘。 🙂

要使用它,你所要做的就是复制那个类(GPL v3),并把@RunWith(Parameterized.class)改为@RunWith(LabelledParameterized.class),假设你的参数列表的第一个元素是一个合理的标签。

我不知道JUnit的后续版本是否可以解决这个问题,但即使他们这样做了,我也无法更新JUnit,因为我的所有合作开发者都必须更新,而且我们的优先级高于重新加工。 因此,类中的工作可以由JUnit的多个版本进行编译。


注意:有一些reflectionjiggery-pokery,以便它可以跨上面列出的不同JUnit版本运行。 JUnit 4.3.1的版本可以在这里find,对于JUnit 4.4和4.5,可以在这里find 。

Parameterized化为模型,我写了自己的定制testing跑步者/套件 – 只花了大约半个小时。 它与darrenp的LabelledParameterized稍有不同,它可以让你明确地指定一个名字,而不是依赖于第一个参数的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); } } 

从junit4.8.2开始,你可以通过复制Parameterized类来创build你自己的MyParameterized类。 更改TestClassRunnerForParameters中的getName()和testName()方法。

您也可以尝试JUnitParams: http : //code.google.com/p/junitparams/

你可以创build一个类似的方法

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

虽然我不会一直使用它,但是确切地说明哪个testing编号143是有用的。

我广泛使用静态导入Assert和朋友,所以我很容易重新定义断言:

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

例如,你可以在testing类中添加一个“name”字段,在构造函数中初始化,并在testing失败时显示。 只需将它作为参数数组的第一个元素传递给每个testing即可。 这也有助于标记数据:

 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} }); } 

它没有为我工作,所以我得到了参数化的来源,并修改它创build一个新的testing亚军。 我不必改变很多,但它工作!

 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; } } 

解决方法是将所有Throwables捕获并嵌套到包含有关参数的所有信息的自定义消息的新Throwable中。 该消息将出现在堆栈跟踪中。 只要testing对所有断言,错误和exception都是失败的,因为它们都是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); } } } 

失败testing的堆栈跟踪是:

 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 

查看JUnitParams提到的dsaff,使用ant在html报告中构build参数化的testing方法描述。

这是在尝试LabelledParameterized后,发现它尽pipe它与Eclipse的工作,它不能与ant一起工作,只要有关的HTML报告。

干杯,

由于访问的参数(例如"{0}"总是返回toString()表示,所以一种解决方法是创build匿名实现并在每种情况下重写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... ); }