代码之家  ›  专栏  ›  技术社区  ›  Epaga Alex Reynolds

更改参数化测试的名称

  •  181
  • Epaga Alex Reynolds  · 技术社区  · 16 年前

    在JUnit4中使用参数化测试时,是否有方法设置自己的自定义测试用例名称?

    我想更改默认值- [Test class].runTest[n] -有意义的东西。

    12 回复  |  直到 8 年前
        1
  •  262
  •   rescdsk    9 年前

    这项功能使它成为 JUnit 4.11 .

    要使用更改参数化测试的名称,可以说:

    @Parameters(name="namestring")
    

    namestring 是一个字符串,可以具有以下特殊占位符:

    • {index} -这组参数的索引。默认值 名称字符串 {index } .
    • {0} -测试调用的第一个参数值。
    • {1} -第二个参数值
    • 等等

    测试的最终名称将是测试方法的名称,后跟 名称字符串 括号内,如下所示。

    例如(改编自 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] . (The testFib 部分名称是 @Test )

        2
  •  37
  •   Roy Tinker    13 年前

    从JUnit4.5来看,它的运行程序显然不支持这一点,因为该逻辑隐藏在参数化类中的私有类中。您不能使用JUnit参数化的运行程序,而是创建自己的运行程序来理解名称的概念(这就引出了如何设置名称的问题…)。

    从JUnit的角度来看,如果不只是传递一个增量,而是传递逗号分隔的参数,那就更好了。testng这样做。如果该功能对您很重要,您可以在www.junit.org上引用的雅虎邮件列表上发表评论。

        3
  •  20
  •   darrenp David Hedlund    15 年前

    我最近在使用JUnit4.3.1时遇到了同样的问题。我实现了一个新的类,它扩展了参数化的labelledParametered。已使用JUnit 4.3.1、4.4和4.5对其进行了测试。它使用@parameters方法中每个参数数组的第一个参数的字符串表示形式重新构造描述实例。您可以在以下位置看到此代码:

    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的特定版本 here 对于Junit 4.4和4.5, here .

        4
  •  13
  •   David Moles paddy-p    14 年前

    Parameterized 作为一个模型,我编写了自己的定制测试运行程序/套件——只花了大约半个小时。和达伦的有点不同 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);
      }
    
    }
    
        5
  •  6
  •   yliang    14 年前

    从JUnit4.8.2中,您可以通过简单地复制参数化类来创建自己的MyParameterized类。更改testclassrunnerforparameters中的getname()和testname()方法。

        6
  •  6
  •   dsaff    14 年前

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

        7
  •  2
  •   Terra Caines    15 年前

    您可以创建类似

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

    虽然我不会一直使用它,但弄清楚143号测试到底是什么会很有用。

        8
  •  2
  •   binkley    15 年前

    我对断言和朋友广泛使用静态导入,因此很容易重新定义断言:

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

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

    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}
        });
    }
    
        9
  •  2
  •   Christian    14 年前

    这些都不适合我,所以我得到了参数化的源代码,并修改了它,创建了一个新的测试运行程序。我不需要改变太多,但它起作用了!!!!

    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;
    }
    }
    
        10
  •  2
  •   mmirwaldt    12 年前

    解决方法是用包含所有参数信息的自定义消息捕获并将所有可丢弃项嵌套到新的可丢弃项中。消息将出现在堆栈跟踪中。 每当测试失败时,对于所有断言、错误和异常都有效,因为它们都是可丢弃的子类。

    我的代码如下:

    @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
    
        11
  •  0
  •   quarkonium    13 年前

    如dsaff所述,检查junitparams,使用ant在HTML报告中构建参数化测试方法描述。

    这是在尝试LabelledParametered并发现它虽然可以与Eclipse一起工作,但就HTML报告而言,它不能与Ant一起工作。

    干杯,

        12
  •  0
  •   Sina Madani    8 年前

    因为参数被访问(例如 "{0}" 总是返回 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...
        );
    }