更改参数化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... ); }