Jak uruchomić wszystkie testy należące do określonej kategorii w JUnit 4

JUnit 4.8 zawiera ładną nową funkcję o nazwie "kategorie", która pozwala na grupowanie pewnych rodzajów testów razem. Jest to bardzo przydatne, np. aby mieć oddzielne testy dla powolnych i szybkich testów. Znam rzeczy wymienione w JUnit 4.8 release notes , ale chciałbym wiedzieć, jak mogę uruchomić wszystkie testy przypisane do określonej kategorii.

Uwagi do wydania JUnit 4.8 pokazują przykładową definicję pakietu, w której Adnotacja SuiteClasses wybiera testy z określonych kategoria do uruchomienia, tak:

@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
}

Czy ktoś wie jak mogę uruchomić wszystkie testy w kategorii SlowTests? Wygląda na to, że musisz mieć adnotację SuiteClasses...

Author: Peter Isberg, 2010-02-01

6 answers

Znalazłem jeden możliwy sposób, aby osiągnąć to, czego chcę, ale nie uważam tego za najlepsze możliwe rozwiązanie, ponieważ opiera się na bibliotece ClassPathSuite, która nie jest częścią JUnit.

Definiuję zestaw testów dla powolnych testów takich jak:

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

Klasa AllTests jest zdefiniowana następująco:

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

Musiałem użyć klasy ClassPathSuite z projektu ClassPathSuite tutaj. Znajdzie wszystkie klasy z testami.

 59
Author: Kaitsu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-16 09:16:47

Oto niektóre z głównych różnic pomiędzy TestNG i JUnit, jeśli chodzi o grupy (lub kategorie, jak JUnit nazywa je):

  • JUnit 'S są wpisywane (adnotacje), podczas gdy TestNG' S są ciągami. Dokonałem tego wyboru, ponieważ chciałem móc używać wyrażeń regularnych podczas uruchamiania testów, na przykład " uruchom wszystkie testy należące do grupy "baza danych*". Ponadto, konieczności tworzenia nowej adnotacji, gdy trzeba utworzyć nową kategorię jest irytujące, chociaż ma korzyści, że IDE powie ci od razu, gdzie ta kategoria jest używana (TestNG pokazuje to w swoich raportach).

  • TestNG bardzo wyraźnie oddziela twój model statyczny (kod Twoich testów) od modelu runtime (który testy są uruchamiane). Jeśli chcesz uruchomić najpierw grupy "front-end", a następnie "servlets", możesz to zrobić bez konieczności przekompilowania czegokolwiek. Ponieważ JUnit definiuje grupy w adnotacjach i musisz określić te kategorie jako parametry dla biegacza, możesz zwykle trzeba przekompilować kod, gdy chcesz uruchomić inny zestaw kategorii, co pokonuje cel moim zdaniem.

 7
Author: Cedric Beust,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-13 01:28:42

Minusem rozwiązania Kaitsu jest to, że Eclipse uruchomi twoje testy dwa razy, a Slowtesty 3 razy, gdy uruchomisz wszystkie testy w projekcie. Dzieje się tak dlatego, że Eclipse uruchomi wszystkie testy, następnie AllTests suite, a następnie SlowTestSuite.

Oto rozwiązanie, które polega na tworzeniu podklas testowych rozwiązań Kaitsu, aby pominąć Pakiety, chyba że określona właściwość systemu jest ustawiona. Haniebny hack, ale wszystko co wymyśliłem tak daleko.

@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";
    }
}

Więc, w Twoim fasttestsuite launcher, po prostu dodaj-Drun.dev.unit.tests = true to the VM arguments. (Zauważ, że to rozwiązanie odwołuje się do szybkiego zestawu testów zamiast do powolnego.)

 5
Author: Kevin Wong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:17

Aby uruchomić skategoryzowane testy bez dokładnego określenia wszystkich z nich w adnotacji @Suite.SuiteClasses, możesz podać własną implementację pakietu. Na przykład org.junit.runners.ParentRunner można rozszerzyć. Zamiast używać tablicy klas dostarczonej przez @Suite.SuiteClasses, nowa implementacja powinna wyszukiwać skategoryzowane testy w classpath.

Zobacz ten projekt jako przykład takiego podejścia. Sposób użycia:

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

}
 2
Author: stanislau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-09 20:36:06

Nie jestem pewien, jaki dokładnie masz problem.

Wystarczy dodać wszystkie testy do suite (lub hirachy of suites). Następnie użyj funkcji categories Runner i dodaj adnotację / ExcludeCategory, aby określić Kategorie, które chcesz uruchomić.

Dobrym pomysłem może być posiadanie jednego pakietu zawierającego wszystkie testy i kilku oddzielnych pakietów odnoszących się do pierwszego z nich, określających różne kategorie, których potrzebujesz.

 1
Author: Jens Schauder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-01 12:43:41

Nie jest to bezpośrednia odpowiedź na twój problem, ale może ogólne podejście można by poprawić...

Dlaczego twoje badania są powolne? Może konfiguracja trwa długo (baza danych, I/O itp.), może testy testują za dużo? W takim przypadku oddzieliłbym rzeczywiste testy jednostkowe od "długotrwałych", które często są testami Integracyjnymi.

W moich ustawieniach mam staging env, gdzie testy jednostkowe są uruchamiane często, a testy integracyjne stale, ale rzadziej (np. po każdym commicie w kontroli wersji). Nigdy nie pracowałem z grupowaniem do testów jednostkowych, ponieważ powinny one być luźno połączone ze sobą. Pracuję tylko z grupowaniem i relacjami przypadków testowych w konfiguracjach integracyjnych (ale z TestNG).

Ale dobrze wiedzieć, że JUnit 4.8 wprowadził pewne funkcje grupowania.

 0
Author: manuel aldana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-01 20:01:11