【问题标题】:Changing names of parameterized tests更改参数化测试的名称
【发布时间】:2010-10-13 15:32:47
【问题描述】:

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

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

【问题讨论】:

    标签: java junit junit4 parameterized


    【解决方案1】:

    看看 JUnit 4.5,它的运行器显然不支持这一点,因为该逻辑隐藏在 Parameterized 类的私有类中。您不能使用 JUnit Parameterized 运行程序,而是创建自己的运行程序来理解名称的概念(这会导致您如何设置名称的问题......)。

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

    【讨论】:

    • 如果 JUnit 对此有所改进,我将不胜感激!
    • 刚刚检查过,在github.com/KentBeck/junit/issues#issue/44 处有一个未完成的功能请求。请投票。
    • @Frank,我认为解决此问题的版本尚未发布。它将在 JUnit 4.11 中。那时(假设设计保持不变)它将是一种指定如何命名测试的文本方式,包括将参数作为名称。其实挺好看的。
    • JUnit 4.11 现已发布 :-)
    • 这里是原始问题github.com/junit-team/junit/issues/44的更新链接,以供将来参考
    【解决方案2】:

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

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

    虽然我不会一直使用它,但准确找出 143 号是哪个测试号会很有用。

    【讨论】:

      【解决方案3】:

      我最近在使用 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 版本编译。


      注意: 有一些反射 jiggery-pokery,因此它可以跨上面列出的不同 JUnit 版本运行。可以找到专门用于 JUnit 4.3.1 的版本here,对于 JUnit 4.4 和 4.5,可以找到 here

      【讨论】:

      • :-) 今天我的一位合作开发人员遇到了问题,因为我在上述消息中指向的版本使用 JUnit 4.3.1(不是我最初所说的 4.4)。他正在使用 JUnit 4.5.0 并引起了问题。我今天将解决这些问题。
      • 我花了一些时间才明白你需要在构造函数中pass测试名称,而不是memorize它。感谢您的代码!
      • 只要我从 Eclipse 运行测试,效果就很好。不过,有没有人有使它与 JUnit Ant Task 一起工作的经验?测试报告在生成的测试报告中命名为execute[0], execute[1] ... execute[n]
      • 非常好。奇迹般有效。如果您可以添加信息,那就太好了,需要将“字符串标签,...”作为第一个参数添加到调用的@Test-method。
      【解决方案4】:

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

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

      【讨论】:

      • 如果测试未通过断言,这很好,但还有其他情况,例如如果抛出异常导致测试失败,或者如果测试期望抛出异常,则使考虑应该由框架处理的名称开销。
      【解决方案5】:

      Parameterized 为模型,我编写了自己的自定义测试运行程序/套件——只花了大约半个小时。它与 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);
        }
      
      }
      

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 我试过这个但没有帮助。创建新类时 getParametersMethod 失败。
        【解决方案7】:

        这些都不适合我,所以我获得了 Parameterized 的源代码并对其进行了修改,创建了一个新的测试运行器。我不需要改变太多,但它可以工作!!!

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

        【讨论】:

          【解决方案8】:

          您可能还想尝试 JUnitParams:http://code.google.com/p/junitparams/

          【讨论】:

          【解决方案9】:

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

          这是在尝试 LabelledParameterized 并发现它虽然适用于 eclipse 但就 html 报告而言不适用于 ant 之后。

          干杯,

          【讨论】:

            【解决方案10】:

            此功能已进入JUnit 4.11

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

            @Parameters(name="namestring")
            

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

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

            测试的最终名称将是测试方法的名称,后跟括号中的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.11 中,它在 master 中。现在什么时候可以使用 4.11,这是个好问题 :-)
            • 4.11 现在处于测试阶段,可以从与上面相同的链接下载:-)
            • 是的,但是有一个错误。如果您像在这篇文章中所做的那样在参数“名称”值中加上括号,它会破坏 Eclipse 中单元测试名称的显示。
            • 很好,但是如果 {0}{1} 是数组呢?理想情况下,JUnit 应该调用Arrays.toString({0}),而不是{0}.toString()。例如,我的data() 方法返回Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});
            • @djangofan 这是一个已有 8 年历史的 Eclipse 错误:bugs.eclipse.org/bugs/show_bug.cgi?id=102512
            【解决方案11】:

            一种解决方法是将所有 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
            

            【讨论】:

              【解决方案12】:

              由于访问的参数(例如,"{0}" 始终返回 toString() 表示,一种解决方法是进行匿名实现并在每种情况下覆盖 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...
                  );
              }
              

              【讨论】:

                【解决方案13】:

                参数化测试在内部调用toString()。 如果您创建一个覆盖 toString() 的对象包装器,它将更改测试的名称。

                这是一个例子,我在其他帖子中回答过。 https://stackoverflow.com/a/67023556/1839360

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-06-12
                  • 1970-01-01
                  • 2020-08-27
                  • 2017-09-04
                  • 1970-01-01
                  相关资源
                  最近更新 更多