So führen Sie alle Tests aus, die zu einer bestimmten Kategorie in JUnit 4 gehören

72

JUnit 4.8 enthält eine nette neue Funktion namens "Kategorien", mit der Sie bestimmte Arten von Tests zusammenfassen können. Dies ist sehr nützlich, z. B. um separate Testläufe für langsame und schnelle Tests durchzuführen. Ich kenne die in den Versionshinweisen zu JUnit 4.8 erwähnten Dinge , möchte aber wissen, wie ich tatsächlich alle Tests ausführen kann, die mit einer bestimmten Kategorie versehen sind.

Die Versionshinweise zu JUnit 4.8 zeigen eine Beispielsuite-Definition, in der die SuiteClasses-Annotation die Tests aus einer bestimmten Kategorie auswählt, die ausgeführt werden sollen:

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b and B.c, but not A.a
}

Weiß jemand, wie ich alle Tests in der Kategorie SlowTests ausführen kann? Es scheint, dass Sie die SuiteClasses-Annotation haben müssen ...

Kaitsu
quelle
1
Hallo. Ich habe eine verwandte Frage. fühlen Sie sich frei zu läuten: stackoverflow.com/questions/15776718/...
amphibient
Es ist nicht direkt verwandt, aber dies ist eine Möglichkeit, den Zähler "Tests nach Kategorie" zu berechnen .
Bsquare 18

Antworten:

62

Ich habe einen möglichen Weg gefunden, um das zu erreichen, was ich will, aber ich halte dies nicht für die bestmögliche Lösung, da sie auf der ClassPathSuite-Bibliothek basiert, die nicht Teil von JUnit ist.

Ich definiere die Testsuite für langsame Tests wie folgt:

@RunWith(Categories.class)
@Categories.IncludeCategory(SlowTests.class)
@Suite.SuiteClasses( { AllTests.class })
public class SlowTestSuite {
}

Die AllTests-Klasse ist wie folgt definiert:

@RunWith(ClasspathSuite.class)
public class AllTests {
}

Ich musste hier die ClassPathSuite-Klasse aus dem ClassPathSuite- Projekt verwenden. Es werden alle Klassen mit Tests gefunden.

Kaitsu
quelle
4
Es ist eigentlich eine ziemlich vernünftige Lösung. Vielen Dank, dass Sie Ihre eigene Frage beantwortet haben, da sie wirklich gut ist :-)
Konrad 'ktoso' Malawski
Für alle, die sich fragen, wie die Ausführung einer Testkategorie (mit genau diesem Setup) mit Ant automatisiert werden kann, ist diese Frage möglicherweise hilfreich.
Jonik
3
Als Kompliment zu meiner Frage stackoverflow.com/q/2698174/59470 und einer detaillierten Erklärung habe ich einen Blogeintrag hinzugefügt: novyden.blogspot.com/2011/06/…
topchef
Es ist nicht direkt verwandt, aber dies ist eine Möglichkeit, den Zähler "Tests nach Kategorie" zu berechnen .
Bsquare 18
7

Hier sind einige der Hauptunterschiede zwischen TestNG und JUnit, wenn es um Gruppen geht (oder Kategorien, wie JUnit sie nennt):

  • JUnits werden eingegeben (Anmerkungen), während TestNGs Zeichenfolgen sind. Ich habe diese Wahl getroffen, weil ich beim Ausführen von Tests reguläre Ausdrücke verwenden wollte, z. B. "Alle Tests ausführen, die zur Gruppe" Datenbank * "gehören. Außerdem muss ich immer dann eine neue Anmerkung erstellen, wenn Sie eine neue erstellen müssen Kategorie ist ärgerlich, obwohl es den Vorteil hat, dass eine IDE Ihnen sofort mitteilt, wo diese Kategorie verwendet wird (TestNG zeigt Ihnen dies in seinen Berichten).

  • TestNG trennt Ihr statisches Modell (den Code Ihrer Tests) sehr deutlich vom Laufzeitmodell (welche Tests ausgeführt werden). Wenn Sie zuerst die Gruppen "Front-End" und dann "Servlets" ausführen möchten, können Sie dies tun, ohne etwas neu kompilieren zu müssen. Da JUnit Gruppen in Anmerkungen definiert und Sie diese Kategorien als Parameter für den Runner angeben müssen, müssen Sie Ihren Code normalerweise neu kompilieren, wenn Sie eine andere Gruppe von Kategorien ausführen möchten, was meiner Meinung nach den Zweck zunichte macht.

Cedric Beust
quelle
Wir haben unsere eigene Kategorienunterstützung in unsere JUnit-Tests auf sehr ähnliche Weise wie JUnit integriert. Der Hauptunterschied besteht darin, dass wir anstelle der Annotation @ Categories.IncludeCategory unsere über eine Systemeigenschaft konfigurierbar gemacht haben. Warum dies für JUnit zu schwer für uns war, ist unklar.
Trejkaz
6

Ein Nachteil von Kaitsus Lösung ist, dass Eclipse Ihre Tests zweimal und die SlowTests dreimal ausführt, wenn alle Tests in einem Projekt ausgeführt werden. Dies liegt daran, dass die Eclipse alle Tests ausführt, dann die AllTests-Suite und dann die SlowTestSuite.

Hier ist eine Lösung, bei der Unterklassen der Kaitsu-Lösungstestläufer erstellt werden, um die Suiten zu überspringen, sofern keine bestimmte Systemeigenschaft festgelegt ist. Ein beschämender Hack, aber alles, was ich mir bisher ausgedacht habe.

@RunWith(DevFilterClasspathSuite.class)
public class AllTests {}

.

@RunWith(DevFilterCategories.class)
@ExcludeCategory(SlowTest.class)
@SuiteClasses(AllTests.class)
public class FastTestSuite
{
}

.

public class DevFilterCategories extends Suite
{
    private static final Logger logger = Logger
        .getLogger(DevFilterCategories.class.getName());
    public DevFilterCategories(Class<?> suiteClass, RunnerBuilder builder) throws InitializationError {
        super(suiteClass, builder);
        try {
            filter(new CategoryFilter(getIncludedCategory(suiteClass),
                    getExcludedCategory(suiteClass)));
            filter(new DevFilter());
        } catch (NoTestsRemainException e) {
            logger.info("skipped all tests");
        }
        assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
    }

    private Class<?> getIncludedCategory(Class<?> klass) {
        IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
        return annotation == null ? null : annotation.value();
    }

    private Class<?> getExcludedCategory(Class<?> klass) {
        ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
        return annotation == null ? null : annotation.value();
    }

    private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
        if (!canHaveCategorizedChildren(description))
            assertNoDescendantsHaveCategoryAnnotations(description);
        for (Description each : description.getChildren())
            assertNoCategorizedDescendentsOfUncategorizeableParents(each);
    }

    private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError {           
        for (Description each : description.getChildren()) {
            if (each.getAnnotation(Category.class) != null)
                throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods.");
            assertNoDescendantsHaveCategoryAnnotations(each);
        }
    }

    // If children have names like [0], our current magical category code can't determine their
    // parentage.
    private static boolean canHaveCategorizedChildren(Description description) {
        for (Description each : description.getChildren())
            if (each.getTestClass() == null)
                return false;
        return true;
    }
}

.

public class DevFilterClasspathSuite extends ClasspathSuite
{
    private static final Logger logger = Logger
        .getLogger(DevFilterClasspathSuite.class.getName());
    public DevFilterClasspathSuite(Class<?> suiteClass, RunnerBuilder builder) 
        throws InitializationError {
        super(suiteClass, builder);
        try
        {
            filter(new DevFilter());
        } catch (NoTestsRemainException e)
        {
            logger.info("skipped all tests");
        }
    }
}

.

public class DevFilter extends Filter
{
    private static final String RUN_DEV_UNIT_TESTS = "run.dev.unit.tests";

    @Override
    public boolean shouldRun(Description description)
    {
        return Boolean.getBoolean(RUN_DEV_UNIT_TESTS);
    }

    @Override
    public String describe()
    {
        return "filter if "+RUN_DEV_UNIT_TESTS+" system property not present";
    }
}

Fügen Sie in Ihrem FastTestSuite-Launcher einfach -Drun.dev.unit.tests = true zu den VM-Argumenten hinzu. (Beachten Sie, dass diese Lösung auf eine schnelle Testsuite anstatt auf eine langsame verweist.)

Kevin Wong
quelle
2

Um kategorisierte Tests auszuführen, ohne sie alle in @Suite.SuiteClassesAnmerkungen explizit anzugeben , können Sie Ihre eigene Implementierung von Suite bereitstellen. Zum Beispiel org.junit.runners.ParentRunnerkann a erweitert werden. Anstatt ein Array von Klassen zu verwenden, die von bereitgestellt werden @Suite.SuiteClasses, sollte die neue Implementierung nach kategorisierten Tests im Klassenpfad suchen.

Sehen Sie sich dieses Projekt als Beispiel für einen solchen Ansatz an. Verwendung:

@Categories(categoryClasses = {IntegrationTest.class, SlowTest.class})
@BasePackage(name = "some.package")
@RunWith(CategorizedSuite.class)
public class CategorizedSuiteWithSpecifiedPackage {

}
Stanislau Fink
quelle
1

Ich bin mir nicht sicher, was genau Ihr Problem ist.

Fügen Sie einfach alle Tests zu einer Suite (oder einer Reihe von Suiten) hinzu. Verwenden Sie dann die Annotation "Categories Runner" und "Include / ExcludeCategory", um die Kategorien anzugeben, die Sie ausführen möchten.

Eine gute Idee könnte sein, eine Suite mit allen Tests und ein paar separate Suiten zu haben, die sich auf die erste beziehen und die verschiedenen Kategorien angeben, die Sie benötigen.

Jens Schauder
quelle
19
Mein Problem ist, dass ich Tausende von Tests habe und sie nicht manuell zu Suiten hinzufügen möchte. Ich möchte nur, dass Tests mit einer bestimmten Kategorie ausgeführt werden. Es sollte für JUnit nicht so schwierig sein, herauszufinden, welche Tests bestimmte Anmerkungen enthalten, da dies beim Auffinden von Testmethoden ohnehin tatsächlich der Fall ist.
Kaitsu
0

Keine direkte Antwort auf Ihr Problem, aber vielleicht könnte der allgemeine Ansatz verbessert werden ...

Warum sind Ihre Tests langsam? Vielleicht dauert die Einrichtung lange (Datenbank, E / A usw.), vielleicht testen die Tests zu viel? Wenn dies der Fall ist, würde ich die realen Unit-Tests von den "lang laufenden" trennen, bei denen es sich häufig tatsächlich um Integrationstests handelt.

In meinen Setups habe ich Staging env, wo Unit-Tests häufig ausgeführt werden und Integrationstests ständig, aber seltener (z. B. nach jedem Commit in der Versionskontrolle). Ich habe noch nie mit Gruppierung für Komponententests gearbeitet, weil sie insgesamt lose miteinander verbunden sein sollten. Ich arbeite nur mit der Gruppierung und Beziehung von Testfällen in Integrationstest-Setups (aber mit TestNG).

Gut zu wissen, dass JUnit 4.8 einige Gruppierungsfunktionen eingeführt hat.

Manuel Aldana
quelle
1
Danke Manuel für deine Kommentare! Ich muss Unit-Tests nicht wirklich trennen, aber ich verwende JUnit auch für Integrationstests und möchte sie von Unit-Tests trennen. Ich habe mir auch TestNG angesehen und es scheint das Testen (und nicht nur das Testen von Einheiten) schöner zu machen als JUnit. Und es hat auch eine bessere Dokumentation und ein gutes Buch.
Kaitsu