Написание тестов JUnit с @BeforeClass @Before в Suite

Я использую JUnit 4 для тестирования серверной системы с базой данных в памяти. Я использую @BeforeClass@До@После и @После занятий.

Пока что он отлично работает на уровне класса.

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

@До просто стирает все, чтобы запустить следующий тест. Это довольно быстро.

Мои тесты выглядят примерно так:

class CompanyTest  {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run(); //Setup In Memory Database!!! Very time intensive!!!
    }
    @Before
    public void beforeTest() throws Exception {
        BeforeTest.run(); //Setup data in database. Relatively quick
    }


    @Test
    public void testCreateCompany() throws Exception {
        ///...
    }
    @Test
    public void testDeleteCompany() throws Exception {
        ///...
    }
    @Test
    public void testAdminCompany() throws Exception {
        ///...
    }


    @After
    public void afterTest() {
        AfterTest.run(); //clear data
    }
    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run(); //tear down database
    }
}

Пока что он отлично работает на уровне класса.

Я могу щелкнуть правой кнопкой мыши (в Eclipse) по отдельному тесту, и он запустит @BeforeClass, а затем @До.

Я также могу щелкнуть (в Eclipse) сам класс, и он будет запускать @BeforeClass только один раз, а затем @До перед каждым тестом.

.... но как этот принцип распространяется на уровень Suite?

Я хочу запустить @BeforeClass перед всеми уроками в моем Suite. Если я напишу свой сюит так:

@Suite.SuiteClasses({ CompanyTest.class, CustomerTest.class, SomeOtherTest.class, })

public class AllTests {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run();
    }

    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run();
    }
}

... Мне нужно удалить @BeforeClass из всех моих тестовых классов. Это раздражает, потому что у меня много тестовых классов, и я не хочу удалять свой @BeforeClass, потому что я хочу протестировать их предварительно.

В основном я говорю:

Есть ли простой способ (щелчком мыши в среде IDE) протестировать тесты JUnit на (а) уровне метода, (б) уровне класса и (в) уровне набора, сохраняя при этом процессы setUp и tearDown на уровне сеанса?

Вместо удаления @BeforeClass из всех ваших тестовых классов вы можете добавить статический счетчик «nestingLevel», увеличивать его каждый BeforeAll.run (), уменьшать его каждый AfterAll.run () и выполнять фактическую настройку / удаление только тогда, когда nestingLevel равно 1.

Yoav Gur 16.10.2018 14:30

Предложение, которое я дал в аналогичный вопрос, заключалось бы в использовании правила JUnit (класса) и проверки внутри правила, если оно уже было инициализировано ранее, и пропустить инициализацию в этом случае. Это должно позволить вам повторно использовать правило как для наборов, так и для конкретных тестовых классов.

Roman Vottner 16.10.2018 14:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
811
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Итак, у меня есть аналогичная проблема (инициализация БД, пул соединений, кешированные данные и т. д.), Которую необходимо выполнить один раз перед запуском любых тестов. Обычно это делается при запуске сервера приложений. Для тестов, требующих этого, я добавил базовый класс:

public class BaseInitializedTest {
    private boolean isInitialized;

    @BeforeClass
    public static void init() {
        doOnetimeInit();

        // Do other per unique class initialization here
    }

    private static synchronized void doOnetimeInit() {
        if (!isInitialized) {
            // Do you one time init here

            isInitialized = true;
        }
    }

    @AfterClass
    public static void deinit() {
        // Cleanup
    }
}

А потом мои тесты, требующие инициализации:

public class SomethingTest extends BaseInitializedTest {
    @Test
    public testSomething() {
        // Your test here
    }
}

Не для всех тестов требуется инициализация стека, но те, которые это делают, наследуются от этого базового класса и будут правильно обрабатывать init. Надеюсь, это поможет.

Это не работает, потому что если у меня есть три разных класса, которые наследуются от BaseInitialisezedTest, и эти три класса настроены в моем Suite, то, если я запустил свой Suite, я все равно получу запуск @BeforeClass три раза (а не только один раз)

Oliver Watkins 17.10.2018 13:49

Конечно, в том-то и дело, что init будет вызываться все время, вам нужно убедиться, что фактическая инициализация выполняется один раз. Моя инициализация выполняется с помощью синглтона, поэтому повторные вызовы init ничего не сделают после первой инициализации кода. Используйте статический синхронизированный метод для инициализации, который устанавливает флаг при инициализации и, если установлен, просто возвращает. Я добавлю код для него выше.

AlexC 17.10.2018 20:43
Ответ принят как подходящий

Вам необходимо, чтобы глобальное состояние управлялось через разные точки входа. В вашем классе BeforeAll или AfterAll вы можете сохранить статическую ссылку, то есть AtomicBoolean, чтобы отслеживать, выполнили ли вы ее уже или нет.

Теперь проблема - с двумя отдельными классами для beforeAll и afterAll - какой класс должен отвечать за этот флаг.

Поэтому я предлагаю вам определить Rule, который содержит всю логику для установки и удаления (в основном весь код BeforeAll и AfterAll) и управляет глобальным состоянием для отслеживания инициализации.

В основном что-то вроде

class MyRule implements TestRule {
    static final AtomicBoolean INITIALIZED = new AtomicBoolean();

    @Override
    public Statement apply(final Statement base, final Description description) {

        return new Statement() {

            @Override
            public void evaluate() throws Throwable {
                boolean needsTearDown = false;
                try {
                    if (INITIALIZED.compareAndSet(false, true)) {
                        BeforeAll.run();
                        needsTearDown = true;
                    }
                    base.evaluate();
                } finally {
                    if (needsTearDown) {
                        AfterAll.run();
                        INITIALIZED.set(false);
                    }
                }
            }
        };
    }
}

И в вашем Test and Suite просто добавьте

class YourTestOrSuite {

  @ClassRule
  public static MyRule rule = new MyRule();
}

Это не компилируется. Если я вставлю это в свою IDE, то скобки покажутся не в порядке.

Oliver Watkins 17.10.2018 14:06

(a) Уровень метода: BeforeAll не запускается, (b) Уровень класса: BeforeAll не запускается, (c) Уровень Suite: AfterAll запускается после первого класса, поэтому все другие классы терпят неудачу после того, как первый класс прошел.

Oliver Watkins 17.10.2018 14:33

Мое плохое, знак "!" в предложении if было неверно (compareAndSet возвращает true, если ожидаемое значение равно фактическому значению). Это происходит, когда вы кодируете прямо в SoW, не тестируя его :) ... теперь это исправлено и должно работать

Gerald Mücke 17.10.2018 15:36

Другие вопросы по теме