Изменение имен параметризованных тестов

204

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

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

Epaga
источник

Ответы:

300

Эта функция включена в 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).

rescdsk
источник
4
Нет никаких причин, что это не было бы в 4.11, это в мастере. Теперь, когда выйдет 4.11, это хороший вопрос :-)
Мэтью Фарвелл
1
4.11 сейчас в бета-версии и может быть загружен по той же ссылке, что и выше :-)
rescdsk
2
Да, но есть ошибка. Если вы поставите скобку в значение параметра «имя», как вы делаете в этой публикации, это нарушит отображение имени модульного теста в Eclipse.
Джангофан
7
отлично, а что если {0}и {1}есть массивы? Юнит должен в идеале звонить Arrays.toString({0}), а не {0}.toString(). Например, мой data()метод возвращает Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
Dogbane
1
@djangofan Это 8-летняя ошибка Eclipse: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Пул
37

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

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

Ишай
источник
3
Я был бы очень признателен, если бы это улучшилось в JUnit!
Guerda
17
Только что проверил, есть ожидающий запрос функции для этого на: github.com/KentBeck/junit/issues#issue/44 Пожалуйста, проголосуйте.
reccles
8
@ Фрэнк, я думаю, что выпуск, посвященный этой проблеме, еще не выпущен. Это будет в JUnit 4.11. В то время (при условии, что дизайн остается прежним), речь пойдет о текстовом способе указания того, как вы называете тест, включая принятие параметров в качестве имен. Довольно мило, на самом деле.
Ишай
5
Вышел JUnit 4.11 :-)
rescdsk
7
Вот обновленная ссылка на оригинальный выпуск github.com/junit-team/junit/issues/44 для дальнейшего использования
kldavis4
20

Недавно я столкнулся с той же проблемой при использовании 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 - здесь .

darrenp
источник
:-) У одного из моих со-разработчиков сегодня была проблема с этим, так как версия, на которую я указал в вышеупомянутом сообщении, использует JUnit 4.3.1 (не 4.4, как я первоначально сказал). Он использует JUnit 4.5.0, и это вызвало проблемы. Я займусь этим сегодня.
Дарренп
Мне потребовалось некоторое время, чтобы понять, что вам нужно передать имя теста в конструкторе, но не запомнить его. Спасибо за код!
Жираф
Прекрасно работает, пока я запускаю тесты из Eclipse. Кто-нибудь имеет опыт работы с JUnit Ant Task? Отчеты об испытаниях названы execute[0], execute[1] ... execute[n]в созданных отчетах об испытаниях.
Хенрик Аастед Соренсен
Очень хорошо. Работает как шарм. Было бы неплохо, если бы вы могли добавить информацию о том, что необходимо добавить «String label, ...» в качестве первого параметра в вызываемый @ Test-метод.
Гия
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);
  }

}
Дэвид Моулз
источник
6

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

yliang
источник
Я пробовал это, но не помогает. При создании нового класса getParametersMethod не удается.
java_enthu
2

Вы можете создать метод, как

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

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


источник
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}
    });
}
Бинкли
источник
Это хорошо, если тест не проходит утверждение, но есть и другие случаи, например, если выбрасывается исключение, которое не проходит тест, или если тест ожидает выдачу исключения, что заставляет задуматься об издержках имени, которые должны быть обрабатывается рамками.
Ишай
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;
}
}
Кристиан
источник
2

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

Проверьте JUnitParams, как упоминалось в dsaff, работает с использованием ant для построения параметризованных описаний методов тестирования в отчете html.

Это было после попытки LabelledParameterized и обнаружения, что, хотя он работает с eclipse, он не работает с ant в том, что касается отчета html.

Ура,

кварконий
источник
0

Поскольку доступ к параметру (например, with "{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...
    );
}
Сина Мадани
источник