Запуск одного теста из Suite с @ClassRule завершается неудачно

Чтобы создать среду только один раз и избежать наследования, я определил класс JUnit Suite с @ClassRule:

@RunWith(Suite.class)               
@Suite.SuiteClasses({               
  SuiteTest1.class              
})      

public class JUnitTest {

    @ClassRule
    private static DockerComposeContainer env = ...


    @BeforeClass
    public static void init(){
        ...
    }

    ...

}

И есть класс Test, который использует env в тестовом методе:

public class SuiteTest1 {               

    @Test
    public void method(){
        client.query(...);// Executes a query against docker container


    }
}

Когда я выполняю тесты, запуская Test Suite, все работает, как и ожидалось. Но когда я напрямую пытаюсь запустить (даже с IDE) тестовый класс SuiteTest1, он терпит неудачу, и ничего из Suite не вызывается (т.е. @Правило класса и @BeforeClass).

Любые предложения о том, как добиться также однократного выполнения SuiteTest1 (без вызова статических методов JUnitTest из SuiteTest1)?


person Momo    schedule 28.09.2018    source источник
comment
Привет @RomanVottner, спасибо за ваш ответ. Пакет запускает и останавливает контейнер, если я запускаю пакет. Мой вопрос касается запуска одного теста набора (например, из IDE): кажется, что если я запускаю только тестовый класс, контейнеры докеров не запускаются. Знаете ли вы способ решить обе проблемы: 1- Запустите один раз контейнеры докеров для каждого набора 2- Запустите один раз контейнеры докеров, если я запускаю только один тест   -  person Momo    schedule 28.09.2018
comment
Тестовый класс не наследует вещи, определенные в наборе тестов. Кроме того, он вообще не знает, является ли он членом определенного (или нескольких) тестовых наборов. По сути, вам нужно создать набор тестов для каждой комбинации, которую вы, возможно, захотите запустить, или переместить материал из набора в соответствующий тестовый класс.   -  person Roman Vottner    schedule 28.09.2018
comment
Хорошо, но для варианта использования интеграционного теста это означало бы раскручивание/отключение среды докера для каждого тестового класса... что в основном убивает производительность этапа тестирования. Цель состоит в том, чтобы раскрутить env один раз для всех тестовых классов, вероятно, я должен поднять сценарий до тестовых контейнеров.   -  person Momo    schedule 28.09.2018
comment
Еще одним способом было бы написать тестовое правило, которое в основном выполняет работу по запуску и остановке док-контейнера. Вам нужно сделать это внутри синглтона, т.е. и сначала проверить, был ли уже создан экземпляр заранее. Это позволит вам определить тестовое правило как в наборе, так и в тестовом классе. При выполнении набора тестов набор тестов будет инициализировать контейнер, а нижестоящие тестовые классы будут игнорировать задачу, в то время как выполнение одного тестового класса будет инициализировать контейнер, как это не делал ни один набор раньше.   -  person Roman Vottner    schedule 28.09.2018
comment
Да, я думал об этом, но мне не очень понравилось решение. Но спасибо!   -  person Momo    schedule 28.09.2018


Ответы (1)


Перефразируя вопрос: вам нужен пакет JUnit с хуками «до всех» и «после», которые также будут запускаться при запуске тестов один за другим (например, из IDE).

AFAIK JUnit 4 не предоставляет ничего готового для этого, но если вы согласны с включением некоторых сторонних приложений Spring (spring-test и spring-context) в ваш проект, я могу предложить обходной путь, который я использовал.

Полный пример того, что описано в этом вопросе может быть найдено здесь.

Решение (с использованием Spring)

Мы будем использовать контекст Spring для реализации нашей инициализации и очистки. Давайте добавим базовый класс для наших тестов:

@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {

    @ClassRule
    public final static SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    public static class ContextInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            System.out.println("Initializing context");

            context.addApplicationListener(
                    (ApplicationListener<ContextClosedEvent>)
                            contextClosedEvent ->
                                    System.out.println("Closing context"));
        }
    }
}

Обратите внимание на SpringClassRule и SpringMethodRule Правила JUnit, которые улучшают наш базовый класс с помощью Spring-superpowers (Обработка аннотаций Spring test - ContextConfiguration в этом случае, но в там - см. Справочник по тестированию Spring подробнее). Вы можете использовать SpringRunner для этой цели, но это гораздо менее гибкое решение (поэтому опущено).

Тестовые классы:

public class TestClass1 extends AbstractTestClass {

    @Test
    public void test() {
        System.out.println("TestClass1 test");
    }
}

public class TestClass2 extends AbstractTestClass {

    @Test
    public void test() {
        System.out.println("TestClass2 test");
    }
}

И набор тестов:

@RunWith(Suite.class)
@SuiteClasses({TestClass1.class, TestClass2.class})
public class TestSuite {
}

Вывод при запуске пакета (для краткости удалены журналы Spring):

Initializing context
TestClass1 test
TestClass2 test
Closing context

Вывод при запуске одного теста (TestClass1):

Initializing context
TestClass1 test
Closing context

Слово объяснения

Это работает благодаря кэшированию контекста Spring. Цитата из документов:

Как только платформа TestContext загружает ApplicationContext (или WebApplicationContext) для теста, этот контекст кэшируется и повторно используется для всех последующих тестов, которые объявляют ту же уникальную конфигурацию контекста в том же наборе тестов. Чтобы понять, как работает кэширование, важно понимать, что подразумевается под «уникальным» и «набором тестов».

-- https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/testing.html#testcontext-ctx-management-caching

Помните, что вы получите другой контекст (и другую инициализацию), если переопределите конфигурацию контекста (например, добавите еще один инициализатор контекста с ContextConfiguration) для любого из классов в иерархии (TestClass1 или TestClass2 в нашем примере).

Использование bean-компонентов для совместного использования экземпляров

Вы можете определить bean-компоненты в своем контексте. Они будут общими для всех тестов, использующих один и тот же контекст. Это может быть полезно для совместного использования объекта в наборе тестов (контейнер Testcontainers в вашем случае, судя по тегам).

Добавим боб:

@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {

    @ClassRule
    public final static SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    public static class ContextInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            ADockerContainer aDockerContainer = new ADockerContainer();
            aDockerContainer.start();

            context.getBeanFactory().registerResolvableDependency(
                    ADockerContainer.class, aDockerContainer);

            context.addApplicationListener(
                    (ApplicationListener<ContextClosedEvent>)
                            contextClosedEvent ->
                                    aDockerContainer.stop());
        }
    }
}

И внедрить его в тестовые классы:

public class TestClass1 extends AbstractTestClass {

    @Autowired
    private ADockerContainer aDockerContainer;

    @Test
    public void test() {
        System.out.println("TestClass1 test " + aDockerContainer.getData());
    }
}

public class TestClass2 extends AbstractTestClass {

    @Autowired
    private ADockerContainer aDockerContainer;

    @Test
    public void test() {
        System.out.println("TestClass2 test " + aDockerContainer.getData());
    }
}

ADockerContainer класс:

public class ADockerContainer {
    private UUID data;

    public void start() {
        System.out.println("Start container");
        data = UUID.randomUUID();
    }

    public void stop() {
        System.out.println("Stop container");
    }

    public String getData() {
        return data.toString();
    }
}

(Пример) вывод:

Start container
TestClass1 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
TestClass2 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
Stop container
person jannis    schedule 14.11.2018