Как выполнить все тесты, относящиеся к определенной категории В JUnit 4


JUnit 4.8 содержит хорошую новую функцию под названием "категории", которая позволяет группировать определенные виды тестов вместе. Это очень полезно, например, иметь отдельные тестовые прогоны для медленных и быстрых тестов. Я знаю вещи, упомянутые в JUnit 4.8 примечания к выпуску, но хотел бы знать, как я могу на самом деле запустить все тесты, аннотированные с определенной категорией.

в примечаниях к выпуску JUnit 4.8 показано определение набора примеров, где аннотация SuiteClasses выбирает тесты из определенной категории запустить, например:

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

кто-нибудь знает, как я мог бы запустить все тесты в категории SlowTests? Кажется, что у вас должна быть аннотация SuiteClasses...

6 68

6 ответов:

Я нашел один из возможных способов достижения того, что я хочу, но я не считаю это лучшим возможным решением, поскольку он опирается на библиотеку ClassPathSuite, которая не является частью JUnit.

Я определяю набор тестов для медленных тестов такой:

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

класс AllTests определяется следующим образом:

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

мне пришлось использовать ClassPathSuite класс от ClassPathSuite здесь. Он найдет все классы с тестами.

вот некоторые из основных различий между TestNG и JUnit, когда речь заходит о группах (или категориях, как их называет JUnit):

  • JUnit печатаются (аннотации), а TestNG-это строки. Я сделал этот выбор, потому что хотел иметь возможность использовать регулярные выражения при выполнении тестов, например "выполнить все тесты, принадлежащие группе "база данных*". Кроме того, необходимость создавать новую аннотацию всякий раз, когда вам нужно создать новую категорию, раздражает, хотя это имеет то преимущество, что IDE сразу же сообщит вам, где используется эта категория (TestNG показывает вам это в своих отчетах).

  • TestNG очень четко отделяет вашу статическую модель (код ваших тестов) от модели времени выполнения (какие тесты запускаются). Если вы хотите сначала запустить группы "front-end", а затем" сервлеты", вы можете сделать это без необходимости перекомпилировать что-либо. Поскольку JUnit определяет группы в аннотациях, и вам нужно указать эти категории в качестве параметров для бегуна вам обычно приходится перекомпилировать свой код всякий раз, когда вы хотите запустить другой набор категорий, что, на мой взгляд, противоречит цели.

одним из недостатков решения Кайцу является то, что затмение будет запускать ваши тесты дважды, а медленные тесты 3 раза, при запуске всех тестов в проекте. Это связано с тем, что Eclipse будет запускать все тесты, затем alltests suite, а затем SlowTestSuite.

вот решение, которое включает в себя создание подклассов Kaitsu solution test runners для пропуска наборов, если не установлено определенное системное свойство. Позорный хак, но все что я придумал так это далеко.

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

Итак, в вашем fasttestsuite launcher, просто добавьте-Drun.разработка.блок.tests=true для Аргументов виртуальной машины. (Обратите внимание, что это решение ссылается на быстрый набор тестов вместо медленного.)

для выполнения категоризированных тестов без указания всех их явно в @Suite.SuiteClasses аннотация вы можете предоставить свою собственную реализацию пакета. Например,org.junit.runners.ParentRunner может быть продлен. Вместо использования массива классов, предоставленных @Suite.SuiteClasses, новая реализация должна выполнять поиск категоризированных тестов в classpath.

посмотреть этот проект как пример такого подхода. Использование:

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

}

Я не уверен, что именно твоя проблема.

просто добавьте все тесты в набор (или hirachy из наборов). Затем используйте Бегун категорий и аннотацию Include/ExcludeCategory, чтобы указать категории, которые вы хотите запустить.

хорошей идеей может быть иметь один набор, содержащий все тесты, и несколько отдельных наборов, ссылающихся на первый, указав другой набор категорий, которые вам нужны.

Не прямой ответ на ваш вопрос, но, возможно, общий подход может быть улучшен...

Почему ваши тесты медленно? Возможно, настройка длится долго (база данных, ввод / вывод и т. д.), может быть, тесты тестируют слишком много? Если это так, я бы отделил реальные модульные тесты от "длительных", которые часто действительно являются интеграционными тестами.

в моих настройках у меня есть staging env, где юнит-тесты выполняются часто, а интеграционные тесты постоянно, но реже (например после каждой фиксации в системе управления версиями). Я никогда не работал с группировкой для модульных тестов, потому что они должны быть слабо связаны вместе. Я работаю только с группировкой и отношением тестовых случаев в установках integration-test (но с TestNG).

но хорошо знать, что JUnit 4.8 ввел некоторые функции группировки.