wake-up-neo.com

Intelligentere Parametrisierung mit Array in Junit 5 (oder einer anderen Java-Testbibliothek)

Ich versuche diesen Test zu parametrieren:

@Test
public void reverseQuote(double[] qsp) throws Exception {
...}

Es erscheint mir absurd, dass es keine schnelle Methode gibt, um das Array qsp zu initialisieren, wie zum Beispiel ValueSource:

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
    assertNotNull(argument);
}

mein Ziel ist es, etwas wie @ValueSource(doublesArray = {new double[]{1.0, 2.0, 3.0}}) zu tun (das gibt jetzt einen Fehler zurück). Gibt es nichts, was etwas ähnliches zulässt? Andere Antworten scheinen nur ausgefeilte Wege vorzuschlagen, wie z. B. @MethodSource oder @ConvertWith

Ich akzeptiere auch Antworten, die andere Testbibliotheken implementieren.

19
Lore

Die Verwendung einer Kombination aus parametrisierten Junit-Tests und YAML-Analyse kann in Betracht gezogen werden. 

@RunWith(Parameterized.class)
public class AnotherParameterizedTest {

    private final HashMap row;

    @Parameterized.Parameters(name="Reverse Lists Tests # {index}:")
    public static List<Map<String, Object>> data() {
        final TestData testData = new TestData(""+
             "|   ID   |       List         |  Expected   |                \n"+
             "|   0    |    [1, 2, 3]       |  [3, 2, 1]  |                \n"+
             "|   1    |    [2, 3, 5]       |  [3, 2, 1]  |                \n"+
             "|   2    |    [5, 6, 7]       |  [ 7, 6, 5] |                \n"
        );
        // parsing each row using simple YAML parser and create map per row
        return testData.getDataTable();
    }

    // Each row from the stringified table above will be 
    // split into key=value pairs where the value are parsed using a 
    // yaml parser. this way, values can be pretty much any yaml type
    // like a list of integers in this case. 
    public AnotherParameterizedTest(HashMap obj) {
        this.row = obj;
    }

    @Test
    public void test() throws Exception {
        List orgListReversed = new ArrayList((List) row.get("List"));
        Collections.reverse(orgListReversed);
        assertEquals((List) row.get("Expected"), orgListReversed);
    }

}

Anstelle eines Strings verwende ich einen Excel-Reader, um das gleiche mit einfachen Excel-Tabellen zu tun. Analysieren jeder Zeile in eine Map mit YAML für die Werte.

Junit IDE Testergebnisse

Dasselbe, wie es kürzlich mit Junit Jupiter getestet wurde, liefert im IDE Runner schönere Ergebnisse. 

import static org.junit.jupiter.api.Assertions.assertEquals;

import de.deicon.yatf.runner.dsl.TestData;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import Java.util.Collections;
import Java.util.List;
import Java.util.Map;

public class FirstTest {

    @ParameterizedTest
    @MethodSource("testTable")
    public void test(Map row){
        List reversedList = (List) row.get("List");
        Collections.reverse(reversedList);
        assertEquals((List)row.get("Expected"), reversedList);
    }

    static List<Map<String, Object>> testTable() {
        return new TestData(""+
                "|ID|   List                  |Expected               |         \n"+
                "|0 | [1,2,3]                 | [3,2,1]               |         \n"+
                "|1 | [hans, peter, klaus]    | [klaus, peter, hans]  |         \n"
        ).getDataTable();
    }

}

 enter image description here

7
Dieter

Ok, das wird eine seltsame Antwort sein, aber es funktioniert und es hat Spaß gemacht.

Erstens: Dein Weg ist unmöglich. Nicht wegen JUnit oder einer verwandten API, sondern wegen Java - gültiger Annotationstypelemente (Annotationsargumente können nur Grundelemente, String, Klasse, Enum, andere Annotationen und Arrays aller sein).

Zweitens: Wir können den ersten umgehen. Überprüfen Sie dies:

@ArraySources(
  arrays = {
    @ArraySource(array = {1, 2, 3}),
    @ArraySource(array = {4, 5, 6}),
    @ArraySource(array = {7, 8, 9})
  }
)

Wie es heißt, kann Annotation andere Annotationen als Argumente und Arrays davon enthalten. Daher verwenden wir diese beiden Regeln hier.

Drittens: wie hilft das? Wir können unseren eigenen Annotation + Argument-Provider hinzufügen, JUnit 5 ist auf diese Weise erweiterbar.

Beide Anmerkungen:

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(ArrayArgumentsProvider.class)
public @interface ArraySources {
    ArraySource[] arrays();
}

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ArraySource {
    int[] array() default {};
}

Argumentanbieter basierend auf den Anmerkungen:

public class ArrayArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<ArraySources> {
    private List<int[]> arguments;

    public void accept(ArraySources source) {
        List<ArraySource> arrays = Arrays.asList(source.arrays());

        this.arguments = arrays.stream().map(ArraySource::array).collect(Collectors.toList());
    }

    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return this.arguments.stream().map(Arguments::of);
    }
}

Und der letzte Test mit diesen:

public class ArraySourcesTest {
    @ParameterizedTest
    @ArraySources(
            arrays = {
                    @ArraySource(array = {1, 2, 3}),
                    @ArraySource(array = {4, 5, 6}),
                    @ArraySource(array = {7, 8, 9})
            }
    )
    void example(int[] array) {
        System.out.println(Arrays.toString(array));
        System.out.println("Test Over");
    }
}

/* Output
[1, 2, 3]
Test Over
[4, 5, 6]
Test Over
[7, 8, 9]
Test Over
*/

Sie haben @MethodSource als kompliziert erwähnt, gut, also glaube ich, dass ich in dieser Angelegenheit versagt habe, aber es funktioniert. Es könnte offensichtlich vereinfacht und erweitert werden (wie Annotationsargumente als Standardwerte benennen - value - und ich habe es nur für int getan, um die Idee zu zeigen). Sie sind sich nicht sicher, ob Sie mit vorhandenen Features (ArgumentsProvider und ArgumentSources) dasselbe erreichen könnten, aber dies sieht spezifischer aus (Sie wissen, dass Sie mit Arrays arbeiten) und zeigt Möglichkeiten zur Erweiterung von JUnit5 auf. Dies kann in anderen Fällen nützlich sein.

5
Shadov

Ich mag Spock zum Testen von Java-Code. Es ist ein groovy-basiertes Test-Framework, das auf JUnit 4 sitzt. Parametrisierte Tests in Spock sind eine integrierte Funktion:

def "The reverseQuote method doesn't return null"(double[] qsp) {

    when: "reverseQuote is called"
    double[] rev = reverseQuote(qsp)

    then: "the result is not null"
    null != rev

    where: "there are various input values"
    qsp << [
        [0.1, 0.2, 0.3] as double[],
        [1.0, 2.0, 3.0] as double[]
    ]
}

... alternativ können Sie Ihre Testdaten tabellarisch auslegen:

def "The reverseQuote method reverses the input array"(List qsp, List expected) {

    when: "reverseQuote is called"
    double[] rev = reverseQuote(qsp as double[])

    then: "the result is the reverse of the input"
    expected as double[] == rev

    where: "there are various input values"
    qsp             | expected
    [0.1, 0.2, 0.3] | [0.3, 0.2, 0.1]
    [1.0, 2.0, 3.0] | [3.0, 2.0, 1.0]
}

Hinweis dass die as double[]-Prävalenz eine unglückliche Folge der automatischen Konvertierung von Arrays durch Groovy in Listen ist. Daher müssen wir sie explizit in den Fällen zurückwerfen, in denen wir mit Java-Code interagieren, für den tatsächlich ein Array erforderlich ist.

4
user31601

Die JUnit-Dokumentation schlägt vor, @MethodSource zu verwenden.

@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(double argument) {
    assertNotEquals(9.0, argument);
}

static DoubleStream range() {
   return DoubleStream.range(0.0, 20.0);
}

Andernfalls könnten Sie Folgendes in Betracht ziehen: https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/params/provider/ValueSource.html#doubles

@ParameterizedTest
@ValueSource(doubles = { 1, 2, 3 })
public void test(double numberToTest){
   //do whatever
}
0
Ben Sch