Test JUnit avec nombre dynamique de tests

95

Dans notre projet, j'ai plusieurs tests JUnit qui, par exemple, prennent chaque fichier d'un répertoire et exécutent un test dessus. Si j'implémente une testEveryFileInDirectoryméthode dans le, TestCasecela apparaît comme un seul test qui peut échouer ou réussir. Mais je suis intéressé par les résultats sur chaque dossier individuel. Comment puis-je écrire un TestCase/ TestSuitetel que chaque fichier apparaisse comme un test séparé, par exemple dans le TestRunner graphique d'Eclipse? (Le codage d'une méthode de test explicite pour chaque fichier n'est pas une option.)

Comparez également la question ParameterizedTest avec un nom dans Eclipse Testrunner .

Hans-Peter Störr
la source

Réponses:

102

Jetez un œil aux tests paramétrés dans JUnit 4.

En fait, je l'ai fait il y a quelques jours. Je vais essayer d'expliquer ...

Commencez par construire votre classe de test normalement, comme vous le faisiez simplement avec un fichier d'entrée. Décorez votre classe avec:

@RunWith(Parameterized.class)

Construisez un constructeur qui prend l'entrée qui changera à chaque appel de test (dans ce cas, il peut s'agir du fichier lui-même)

Ensuite, créez une méthode statique qui renverra un Collectiontableau de. Chaque tableau de la collection contiendra les arguments d'entrée pour votre constructeur de classe, par exemple le fichier. Décorez cette méthode avec:

@Parameters

Voici un exemple de classe.

@RunWith(Parameterized.class)
public class ParameterizedTest {

    private File file;

    public ParameterizedTest(File file) {
        this.file = file;
    }

    @Test
    public void test1() throws Exception {  }

    @Test
    public void test2() throws Exception {  }

    @Parameters
    public static Collection<Object[]> data() {
        // load the files as you want
        Object[] fileArg1 = new Object[] { new File("path1") };
        Object[] fileArg2 = new Object[] { new File("path2") };

        Collection<Object[]> data = new ArrayList<Object[]>();
        data.add(fileArg1);
        data.add(fileArg2);
        return data;
    }
}

Consultez également cet exemple

bruno conde
la source
1
Merci! La méthode JUnit 4 est meilleure que la méthode JUnit 3 donnée dans une autre réponse, car le JUnit 3 confond le testeur d'éclipse et avec la méthode JUnit 4, vous pouvez ré-exécuter les tests, etc. Je me demande seulement comment je peux avoir une nom du test - il ne montre que [0], [1] etc.
Hans-Peter Störr
@hstoerr, on dirait que cela sera dans la prochaine version de JUnit :-) github.com/KentBeck/junit/commit/…
rescdsk
Comment transformeriez-vous cela si vous vouliez que chaque exécution [avec une combinaison de données différente] modifie le nom de l'essai? [Ie fichier Path1 serait testé comme: test1Path1, test2Path?
monksy le
^ Lien mis à jour: github.com/junit-team/junit4/commit/…
Alexander Udalov
27

JUnit 3

public class XTest extends TestCase {

    public File file;

    public XTest(File file) {
        super(file.toString());
        this.file = file;
    }

    public void testX() {
        fail("Failed: " + file);
    }

}

public class XTestSuite extends TestSuite {

    public static Test suite() {
        TestSuite suite = new TestSuite("XTestSuite");
        File[] files = new File(".").listFiles();
        for (File file : files) {
            suite.addTest(new XTest(file));
        }
        return suite;
    }

}

JUnit 4

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class TestY {

    @Parameters
    public static Collection<Object[]> getFiles() {
        Collection<Object[]> params = new ArrayList<Object[]>();
        for (File f : new File(".").listFiles()) {
            Object[] arr = new Object[] { f };
            params.add(arr);
        }
        return params;
    }

    private File file;

    public TestY(File file) {
        this.file = file;
    }

    @Test
    public void testY() {
        fail(file.toString());
    }

}
McDowell
la source
11

Tests paramétrés Junit 5

Les tests paramétrés JUnit 5 prennent en charge cela en permettant l'utilisation d'une méthode comme source de données :

@ParameterizedTest
@MethodSource("fileProvider")
void testFile(File f) {
    // Your test comes here
}

static Stream<File> fileProvider() {
    return Arrays.asList(new File(".").list()).stream();
}

Tests dynamiques JUnit 5

JUnit 5 prend également en charge cela à travers la notion de a DynamicTest, qui doit être généré dans a @TestFactory, au moyen de la méthode statique dynamicTest.

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.stream.Stream;

@TestFactory
public Stream<DynamicTest> testFiles() {
    return Arrays.asList(new File(".").list())
            .stream()
            .map((file) -> dynamicTest(
                    "Test for file: " + file,
                    () -> { /* Your test comes here */ }));
}

Les tests exécutés dans votre IDE (IntelliJ ici) seront affichés comme ceci:

Sortie dans IntelliJ

avandeursen
la source
3

Devrait être possible dans JUnit 3 en héritant TestSuiteet en remplaçant la tests()méthode pour lister les fichiers et pour chaque retour une instance d'une sous-classe deTestCase qui prend le nom de fichier comme paramètre de constructeur et a une méthode de test qui teste le fichier donné dans le constructeur.

Dans JUnit 4, cela pourrait être encore plus facile.

Michael Borgwardt
la source
2

Vous pourriez envisager d'utiliser la bibliothèque JUnitParams , de sorte que vous disposiez de quelques autres options (plus propres):

@org.junit.runner.RunWith(junitparams.JUnitParamsRunner.class)
public class ParameterizedTest {

    @org.junit.Test
    @junitparams.Parameters(method = "data")
    public void test1(File file) throws Exception {  }

    @org.junit.Test
    @junitparams.Parameters(method = "data")
    public void test2(File file) throws Exception {  }

    public static File[] data() {
        return new File[] { new File("path1"), new File("path2") };
    }
}

@org.junit.runner.RunWith(junitparams.JUnitParamsRunner.class)
public class ParameterizedTest {

    @org.junit.Test
    @junitparams.Parameters(value = { "path1", "path2" })
    public void test1(String path) throws Exception {
        File file = new File(path);
    }

    @org.junit.Test
    @junitparams.Parameters(value = { "path1", "path2" })
    public void test2(String path) throws Exception {
        File file = new File(path);
    }
}

Vous pouvez voir plus d' exemples d'utilisation ici .

En plus de JUnitParams, pourquoi écrire des tests paramétrés avec lui est plus facile et plus lisible :

Le projet JUnitParams ajoute un nouveau runner à JUnit et fournit des tests paramétrés beaucoup plus faciles et lisibles pour JUnit> = 4.6.

Principales différences par rapport au coureur paramétré JUnit standard:

  • plus explicite - les paramètres sont dans les paramètres de la méthode de test, pas dans les champs de classe
  • moins de code - vous n'avez pas besoin d'un constructeur pour configurer les paramètres
  • vous pouvez mélanger des méthodes paramétrées avec des méthodes non paramétrées dans une classe
  • les paramètres peuvent être passés sous forme de chaîne CSV ou à partir d'une classe de fournisseur de paramètres
  • La classe de fournisseur de paramètres peut avoir autant de paramètres fournissant des méthodes que vous le souhaitez, afin que vous puissiez regrouper différents cas
  • vous pouvez avoir une méthode de test qui fournit des paramètres (plus de classes externes ou de statiques)
  • vous pouvez voir les valeurs de paramètres réelles dans votre IDE (dans JUnit's Parametrised, il n'y a que des nombres consécutifs de paramètres)
falsarella
la source
1

Si TestNG est une option, vous pouvez utiliser des paramètres avec DataProviders .

Le test de chaque fichier individuel verra son résultat affiché dans le rapport textuel ou dans l'interface utilisateur du plugin TestNG d'Eclipse. Le nombre total de tests exécutés comptera chacun de vos fichiers individuellement.

Ce comportement diffère des théories JUnit , dans lesquelles tous les résultats sont regroupés sous une seule entrée "théorie" et ne comptent que pour 1 test. Si vous voulez des rapports de résultats séparés dans JUnit, vous pouvez essayer des tests paramétrés .

Test et entrées

public class FileTest {

    @DataProvider(name="files")
    public File[][] getFiles(){
        return new File[][] {
            { new File("file1") },
            { new File("file2") }
        };
        // or scan a directory
    }

    @Test(dataProvider="files")
    public void testFile(File file){
        //run tests on file
    }
}

Exemple de sortie

PASSED: testFile(file1)
PASSED: testFile(file2)

===============================================
    Default test
    Tests run: 2, Failures: 0, Skips: 0
===============================================
Ben Hutchison
la source
Je ne connais pas les théories, mais les tests paramétrés dans JUnit sont affichés séparément dans eclipse, et non regroupés.
Hans-Peter Störr