Как запустить все тесты, принадлежащие определенной категории в 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...

Ответ 1

Я выяснил один из возможных способов достижения того, что я хочу, но я не считаю это лучшим решением, поскольку он полагается на библиотеку 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 здесь. Он найдет все классы с тестами.

Ответ 2

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

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

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

Ответ 3

Один недостаток решения Kaitsu заключается в том, что Eclipse будет запускать ваши тесты дважды, а SlowTests - 3 раза при выполнении всех тестов в проекте. Это связано с тем, что Eclipse будет запускать все тесты, а затем пакет AllTests, затем SlowTestSuite.

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

@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 просто добавьте -Drun.dev.unit.tests = true аргументы VM. (Обратите внимание, что это решение ссылается на быстрый тестовый пакет вместо медленного.)

Ответ 4

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

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

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

}

Ответ 5

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

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

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

Ответ 6

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

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

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

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