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

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

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


person Epaga    schedule 16.03.2009    source источник


Ответы (13)


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

person rescdsk    schedule 13.04.2012
comment
Нет причин, по которым этого не было бы в 4.11, это в master. Теперь, когда будет доступна 4.11, это хороший вопрос :-) - person Matthew Farwell; 27.06.2012
comment
4.11 сейчас находится в стадии бета-тестирования, и его можно скачать по той же ссылке, что и выше :-) - person rescdsk; 17.10.2012
comment
Да, но есть ошибка. Если вы поместите круглые скобки в значение имени параметра, как в этой публикации, это нарушит отображение имени модульного теста в Eclipse. - person djangofan; 17.11.2012
comment
@djangofan, вы говорите, что это ошибка Eclipse? Я больше не работаю с Eclipse на ежедневной основе, поэтому не могу подтвердить. Или это ошибка jUnit? - person rescdsk; 21.12.2012
comment
@rescdsk - я не уверен. имя параметра, которое отображается на вкладке JUNit в eclipse, изменяется (если в строке есть скобки). - person djangofan; 22.12.2012
comment
отлично, но что, если {0} и {1} массивы? В идеале JUnit должен вызывать Arrays.toString({0}), а не {0}.toString(). Например, мой метод data() возвращает Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});. - person dogbane; 26.03.2013
comment
@djangofan Это ошибка Eclipse, возникшая 8 лет назад: bugs.eclipse.org/bugs /show_bug.cgi?id=102512 - person Pool; 29.08.2013
comment
@Pool - это не та же ошибка. ошибка, о которой я говорю, относится только к JUnit версии 4.11. - person djangofan; 30.08.2013
comment
@djangofan - я считаю, что он будет отображаться только с JUnit версии 4.11 из-за возможности добавления скобок. Вы написали the parameter name that is displayed in the JUNit tab in eclipse gets munged (when parenthesis exists in the string). В отчете об ошибке указано, что Eclipse удаляет детали в скобках и что это сделано намеренно. Разве это не то, что вы имели в виду под «возмутился»? - person Pool; 30.08.2013
comment
@Pool - да, это связанная ошибка, но я сомневаюсь, что это то же самое. - person djangofan; 30.08.2013
comment
@djangofan - Хорошо, а что для вас происходит с именем? Персонажи повреждены и отображение искажено? Это кажется более серьезной проблемой, и стоит сообщить об ошибке, если она все еще возникает. - person Pool; 31.08.2013
comment
Я использую JUnit 4.9, а у параметров нет имени (). - person Valentine Zakharenko; 06.10.2016
comment
Я никак не могу с этим что-нибудь сделать, верно? Нравится name = makeNameForObj(xy)? - person Bowi; 02.02.2017
comment
@Bowi вы можете сделать класс NewClass расширением YourTestObject и переопределить его метод toString на все, что вам нравится - person aelimill; 04.02.2017

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

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

person Yishai    schedule 16.03.2009
comment
Я был бы очень признателен, если в JUnit есть улучшения! - person guerda; 16.03.2009
comment
Вместо передачи аргумента, возможно, просто используйте toString () в тестовом классе. Я полагаю, что добавить эту функцию в JUnit было бы довольно просто. - person reccles; 05.02.2010
comment
Только что проверил, для этого есть выдающийся запрос функции: github.com/KentBeck/junit / issues # issue / 44 Проголосуйте. - person reccles; 05.02.2010
comment
Похоже, он вошел и будет в следующем выпуске JUnit :-) / KentBeck / junit / commit / - person rescdsk; 13.04.2012
comment
Каков текущий ответ по этому вопросу? - person Franz Ebner; 24.05.2012
comment
@Frank, я думаю, что релиз, посвященный этой проблеме, еще не выпущен. Это будет в JUnit 4.11. В то время (при условии, что дизайн останется прежним) речь пойдет о текстовом способе указания имени теста, включая использование параметров в качестве имен. Вообще-то, довольно мило. - person Yishai; 24.05.2012
comment
Выпущен JUnit 4.11 :-) - person rescdsk; 17.11.2012
comment
Вот обновленная ссылка на исходную проблему github.com/junit-team/junit/issues / 44 на будущее - person kldavis4; 26.03.2013

Недавно я столкнулся с той же проблемой при использовании 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/uk/ac/lkl/common/util/testing/LabelledParameterized.java?r=3789

и пример его использования по адресу:

http://code.google.com/p/migen/source/browse/trunk/java/src/uk/ac/lkl/common/util/restlet/test/builder/ServerBuilderTest.java?r=3789

Описание тестов прекрасно форматируется в Eclipse, что я и хотел, поскольку это значительно упрощает поиск неудачных тестов! Я, вероятно, доработаю и задокументирую занятия в течение следующих нескольких дней / недель. Отбросьте '?' часть URL-адресов, если вам нужна передовая кромка. :-)

Чтобы использовать его, все, что вам нужно сделать, это скопировать этот класс (GPL v3) и изменить @RunWith (Parameterized.class) на @RunWith (LabelledParameterized.class), предполагая, что первый элемент вашего списка параметров является разумной меткой.

Я не знаю, решат ли эту проблему какие-либо более поздние выпуски JUnit, но даже если бы они и сделали, я не могу обновить JUnit, так как всем моим со-разработчикам тоже придется обновлять, и у нас есть более высокие приоритеты, чем переоснащение. Следовательно, работа в классе будет компилироваться несколькими версиями JUnit.


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

person darrenp    schedule 12.01.2010
comment
:-) У одного из моих соразработчиков сегодня возникла проблема с этим, поскольку версия, на которую я указал в сообщении выше, использует JUnit 4.3.1 (а не 4.4, как я изначально сказал). Он использует JUnit 4.5.0, и это вызвало проблемы. Я обращусь к ним сегодня. - person darrenp; 13.01.2010
comment
Мне потребовалось некоторое время, чтобы понять, что вам нужно передать имя теста в конструкторе, но не запоминать его. Спасибо за код! - person giraff; 06.08.2011
comment
Отлично работает, пока я запускаю тесты из Eclipse. Но есть ли у кого-нибудь опыт работы с JUnit Ant Task? Отчеты об испытаниях имеют имя execute[0], execute[1] ... execute[n] в сгенерированных отчетах об испытаниях. - person Henrik Aasted Sørensen; 10.10.2012
comment
Очень хорошо. Работает как шарм. Было бы неплохо, если бы вы могли добавить информацию о том, что требуется добавить метку String, ... в качестве первого параметра для вызываемого @ Test-метода. - person gia; 11.07.2013

Используя Parameterized в качестве модели, я написал свой собственный тестовый прогон / набор - всего за полчаса. Он немного отличается от LabelledParameterized darrenp тем, что позволяет указать имя явно, а не полагаться на 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);
  }

}
person David Moles    schedule 04.08.2010


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

person yliang    schedule 04.01.2011
comment
Я пробовал это, но не помогло. При создании нового класса getParametersMethod не удается. - person java_enthu; 29.12.2011

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

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

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

person Community    schedule 10.08.2009

Я широко использую статический импорт для Assert и друзей, поэтому мне легко переопределить assertion:

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}
    });
}
person binkley    schedule 14.01.2010
comment
Это нормально, если тест не дает утверждения, но есть и другие случаи, например, если генерируется исключение, которое не проходит тест, или если тест ожидает выброса исключения, что заставляет задуматься о накладных расходах имени, которые должны быть обрабатывается фреймворком. - person Yishai; 14.01.2010

Ничего из этого у меня не сработало, поэтому я получил исходный код для 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;
}
}
person Christian    schedule 24.05.2011

Обходной путь - перехватить и вложить все объекты 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
person mmirwaldt    schedule 05.06.2013

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

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

Ваше здоровье,

person quarkonium    schedule 27.03.2012

Поскольку параметр, к которому был осуществлен доступ (например, с "{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...
    );
}
person Sina Madani    schedule 06.06.2017

Параметризованный тест вызывает внутренний вызов toString(). Если вы создадите оболочку объекта, перекрывающую toString(), она изменит имена теста.

Вот пример, я ответил в другом посте. https://stackoverflow.com/a/67023556/1839360

person Yan Khonski    schedule 09.04.2021