216

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

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

4

13 回答 13

320

此功能已进入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]and的名称testFib[4: fib(4)=3]。(名称的testFib一部分是 的方法名称@Test)。

于 2012-04-13T15:34:58.697 回答
38

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

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

于 2009-03-16T15:49:14.363 回答
19

我最近在使用 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 的版本,对于 JUnit 4.4 和 4.5,可以在此处找到

于 2010-01-12T20:42:19.960 回答
13

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

}
于 2010-08-04T09:35:17.430 回答
7

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

于 2011-06-08T14:30:53.800 回答
6

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

于 2011-01-04T21:34:06.800 回答
2

您可以创建一个方法,如

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

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

于 2009-08-10T20:23:50.170 回答
2

我为 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}
    });
}
于 2010-01-14T17:19:14.807 回答
2

没有一个对我有用,所以我获得了 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;
}
}
于 2011-05-24T17:11:09.267 回答
2

一种解决方法是将所有 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
于 2013-06-05T17:09:57.873 回答
0

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

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

干杯,

于 2012-03-27T16:19:44.817 回答
0

由于访问的参数(例如"{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...
    );
}
于 2017-06-06T17:00:04.053 回答
0

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

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

于 2021-04-09T15:09:47.410 回答