Изменение имен параметризованных тестов - PullRequest
184 голосов
/ 16 марта 2009

Есть ли способ установить мои собственные имена тестовых случаев при использовании параметризованных тестов в JUnit4?

Я бы хотел изменить значение по умолчанию - [Test class].runTest[n] - на что-то значимое.

Ответы [ 12 ]

271 голосов
/ 13 апреля 2012

Эта функция превратилась в 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).

37 голосов
/ 16 марта 2009

Глядя на JUnit 4.5, его исполнитель явно не поддерживает это, так как эта логика скрыта внутри частного класса внутри класса Parameterized. Вы не могли бы использовать JUnit Parameterized runner и создать вместо него свое собственное, которое бы понимало концепцию имен (что приводит к вопросу о том, как вы можете установить имя ...).

С точки зрения JUnit было бы неплохо, если бы вместо (или в дополнение к) просто передачи приращения они передавали аргументы с разделителями-запятыми. TestNG делает это. Если эта функция важна для вас, вы можете прокомментировать список рассылки Yahoo, указанный на www.junit.org.

20 голосов
/ 12 января 2010

Недавно я столкнулся с той же проблемой при использовании JUnit 4.3.1. Я реализовал новый класс, который расширяет Parameterized под названием LabelledParameterized. Он был протестирован с использованием JUnit 4.3.1, 4.4 и 4.5. Он восстанавливает экземпляр Description, используя строковое представление первого аргумента каждого массива параметров из метода @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.


Примечание: есть некоторые jiggery-pokery отражения, так что он работает на разных версиях JUnit, как указано выше. Специально для JUnit 4.3.1 можно найти версию здесь , а для JUnit 4.4 и 4.5 здесь .

13 голосов
/ 04 августа 2010

Используя 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);
  }

}
6 голосов
/ 08 июня 2011

Вы также можете попробовать JUnitParams: http://code.google.com/p/junitparams/

6 голосов
/ 05 января 2011

из junit4.8.2, вы можете создать свой собственный класс MyParameterized, просто скопировав класс Parameterized. измените методы getName () и testName () в TestClassRunnerForParameters.

2 голосов
/ 05 июня 2013

Обходной путь может заключаться в том, чтобы перехватить и вложить все Throwables в новый 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
2 голосов
/ 24 мая 2011

У меня ничего не получалось, поэтому я получил исходный текст для 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;
}
}
2 голосов
/ 14 января 2010

Я широко использую статический импорт для 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}
    });
}
2 голосов
/ 11 августа 2009

Вы можете создать метод, подобный

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

Хотя я бы не использовал его все время, было бы полезно выяснить, какой именно тест № 143.

...