Antworten:
Diese Funktion hat es in JUnit 4.11 geschafft .
Um den Namen parametrisierter Tests zu ändern, sagen Sie:
@Parameters(name="namestring")
namestring
ist eine Zeichenfolge, die die folgenden speziellen Platzhalter haben kann:
{index}
- der Index dieser Argumente. Der Standardwert namestring
ist {index}
.{0}
- der erste Parameterwert aus diesem Aufruf des Tests.{1}
- der zweite ParameterwertDer endgültige Name des Tests ist der Name der Testmethode, gefolgt von den namestring
in Klammern gesetzten Klammern (siehe unten).
Zum Beispiel (angepasst aus dem Unit-Test für die Parameterized
Annotation):
@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;
}
}
wird Namen wie testFib[1: fib(1)=1]
und geben testFib[4: fib(4)=3]
. (Der testFib
Teil des Namens ist der Methodenname von @Test
).
{0}
und {1}
sind Arrays? JUnit sollte im Idealfall nennt Arrays.toString({0})
, nicht {0}.toString()
. Zum Beispiel gibt meine data()
Methode zurück Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});
.
Bei JUnit 4.5 unterstützt der Runner dies eindeutig nicht, da diese Logik in einer privaten Klasse innerhalb der parametrisierten Klasse vergraben ist. Sie könnten den JUnit-parametrisierten Runner nicht verwenden und stattdessen einen eigenen erstellen, der das Konzept der Namen versteht (was zu der Frage führt, wie Sie einen Namen festlegen könnten ...).
Aus Sicht von JUnit wäre es schön, wenn anstelle (oder zusätzlich zu) der Übergabe eines Inkrements die durch Kommas getrennten Argumente übergeben würden. TestNG macht das. Wenn die Funktion für Sie wichtig ist, können Sie die Yahoo-Mailingliste kommentieren, auf die unter www.junit.org verwiesen wird.
Ich bin kürzlich auf dasselbe Problem gestoßen, als ich JUnit 4.3.1 verwendet habe. Ich habe eine neue Klasse implementiert, die Parameterized mit dem Namen LabelledParameterized erweitert. Es wurde mit JUnit 4.3.1, 4.4 und 4.5 getestet. Es rekonstruiert die Description-Instanz mithilfe der String-Darstellung des ersten Arguments jedes Parameterarrays aus der @ Parameters-Methode. Sie können den Code dafür sehen unter:
http://code.google.com/p/migen/source/browse/trunk/java/src/.../LabelledParameterized.java?r=3789
und ein Beispiel für seine Verwendung bei:
http://code.google.com/p/migen/source/browse/trunk/java/src/.../ServerBuilderTest.java?r=3789
Die Testbeschreibung ist in Eclipse gut formatiert, was ich wollte, da dies das Auffinden fehlgeschlagener Tests erheblich erleichtert! Ich werde die Kurse wahrscheinlich in den nächsten Tagen / Wochen weiter verfeinern und dokumentieren. Lass fallen '?' Teil der URLs, wenn Sie die Blutungskante wollen. :-)
Um es zu verwenden, müssen Sie nur diese Klasse (GPL v3) kopieren und @RunWith (Parameterized.class) in @RunWith (LabelledParameterized.class) ändern, vorausgesetzt, das erste Element Ihrer Parameterliste ist eine sinnvolle Bezeichnung.
Ich weiß nicht, ob spätere Versionen von JUnit dieses Problem beheben, aber selbst wenn dies der Fall ist, kann ich JUnit nicht aktualisieren, da alle meine Mitentwickler ebenfalls aktualisieren müssten und wir höhere Prioritäten als das Nachrüsten haben. Daher muss die Arbeit in der Klasse von mehreren Versionen von JUnit kompiliert werden.
Hinweis: Es gibt einige Reflection Jiggery-Pokery, so dass es über die verschiedenen JUnit-Versionen läuft, wie oben aufgeführt. Die Version speziell für JUnit 4.3.1 finden Sie hier und für JUnit 4.4 und 4.5 hier .
execute[0], execute[1] ... execute[n]
in den generierten Testberichten benannt .
Mit Parameterized
als Modell habe ich meinen eigenen Testläufer / meine eigene Test-Suite geschrieben - es dauerte nur etwa eine halbe Stunde. Es unterscheidet sich geringfügig von Darrenp LabelledParameterized
darin, dass Sie einen Namen explizit angeben können, anstatt sich auf die ersten Parameter zu verlassen toString()
.
Es werden auch keine Arrays verwendet, weil ich Arrays hasse. :) :)
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);
}
}
}
Und ein Beispiel:
@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);
}
}
Ab junit4.8.2 können Sie Ihre eigene MyParameterized-Klasse erstellen, indem Sie einfach die parametrisierte Klasse kopieren. Ändern Sie die Methoden getName () und testName () in TestClassRunnerForParameters.
Möglicherweise möchten Sie auch JUnitParams ausprobieren: http://code.google.com/p/junitparams/
Sie können eine Methode wie erstellen
@Test
public void name() {
Assert.assertEquals("", inboundFileName);
}
Obwohl ich es nicht die ganze Zeit benutzen würde, wäre es nützlich, genau herauszufinden, welche Testnummer 143 ist.
Ich verwende den statischen Import für Assert und Freunde in großem Umfang, sodass ich Assertion leicht neu definieren kann:
private <T> void assertThat(final T actual, final Matcher<T> expected) {
Assert.assertThat(editThisToDisplaySomethingForYourDatum, actual, expected);
}
Sie können beispielsweise Ihrer Testklasse ein Feld "Name" hinzufügen, das im Konstruktor initialisiert wurde, und dieses Feld bei einem Testfehler anzeigen. Übergeben Sie es einfach als erstes Element Ihres Parameterarrays für jeden Test. Dies hilft auch beim Beschriften der Daten:
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}
});
}
Nichts davon funktionierte für mich, also habe ich die Quelle für Parametrisiert und modifiziert, um einen neuen Testläufer zu erstellen. Ich musste nicht viel ändern, aber es funktioniert !!!
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;
}
}
Eine Problemumgehung besteht darin, alle Throwables mit einer benutzerdefinierten Nachricht zu fangen und in eine neue Throwable zu verschachteln, die alle Informationen zu den Parametern enthält. Die Nachricht wird im Stack-Trace angezeigt. Dies funktioniert immer dann, wenn ein Test für alle Zusicherungen, Fehler und Ausnahmen fehlschlägt, da sie alle Unterklassen von Throwable sind.
Mein Code sieht folgendermaßen aus:
@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);
}
}
}
Die Stapelverfolgung des fehlgeschlagenen Tests lautet:
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
Schauen Sie sich JUnitParams an, wie von dsaff erwähnt, und arbeiten Sie mit ant, um parametrisierte Testmethodenbeschreibungen im HTML-Bericht zu erstellen.
Dies geschah, nachdem LabelledParameterized ausprobiert wurde und festgestellt wurde, dass es, obwohl es mit Eclipse funktioniert, mit Ant nicht funktioniert, was den HTML-Bericht betrifft.
Prost,
Da auf den Parameter zugegriffen wird (z. B. mit "{0}"
immer die toString()
Darstellung zurückgibt , besteht eine Problemumgehung darin, eine anonyme Implementierung vorzunehmen und jeweils zu überschreiben toString()
. Beispiel:
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...
);
}