Junit - запустіть метод налаштування один раз


119

Я створив клас з парою тестів, і замість того, щоб використовувати, @Beforeя хотів би мати метод налаштування, який виконується лише один раз перед усіма тестами. Чи можливо це за допомогою Junit 4.8?


Відповіді:


205

Хоча я погоджуюся з @assylias, що використовувати @BeforeClassкласичне рішення, це не завжди зручно. Спосіб, в якому зазначається, @BeforeClassповинен бути статичним. Це дуже незручно для деяких тестів, які потребують прикладу тестового випадку. Наприклад, весняні тести, які використовуються @Autowiredдля роботи з сервісами, визначеними у весняному контексті.

У цьому випадку я особисто використовую звичайний setUp()метод, позначений з @Beforeанотацією, і керую своїм власним static(!) booleanПрапором:

private static boolean setUpIsDone = false;
.....
@Before
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}

10
Додаючи до коментаря Кенні Кейсона, чому він повинен бути статичним. Він повинен бути статичним, оскільки JUnit створює новий екземпляр тестового класу для кожного методу @Test. Змінна екземпляра буде скинута до значення за замовчуванням (false) для кожного екземпляра, якщо воно не є статичним. Дивіться детальну інформацію: martinfowler.com/bliki/JunitNewInstance.html
dustin.schultz

2
Це працює, за винятком випадків, коли setUp()метод знаходиться у надкласовому класі - опублікувавши відповідь нижче, намагаючись вирішити це.
Стів Чемберс

4
Я не вагаюся сказати це комусь із репрезентативом 84k, але програма BeforeClass насправді не відповідає на питання: BeforeClass працює на початку кожного тестового класу. Але ОП попросив того, хто працює "лише один раз перед усіма тестами". Ваше запропоноване рішення могло б зробити це, але вам доведеться змусити всі тестові класи поширити клас "CommonTest" ...
Майк гризун

1
@mikerodent, ІМХО ОП запитав про всі тести в його тестовому випадку, а не про всі тести в цілому. Отже, ваш коментар менш актуальний. До речі, не хвилюйтеся нічого казати будь-якій людині, навіть якщо його репутація висока. Принаймні це я роблю :). І моя репутація була значно нижчою серпня 2012 року, коли я відповів на питання.
AlexR

Не працює в моєму випадку, змінні, ініціалізовані в налаштуваннях, перезавантажуються після кожного тесту, тому безглуздо ініціювати лише один раз.
Афакс

89

Ви можете використовувати в BeforeClassанотації :

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}

12
Я не можу цим скористатися, у мене є декілька методів настройки, які базуються на нестатичних компонентах, таких як getClass ()
Bober02

1
@ Bober02 BeforeClass дійсно повинен бути статичним. Якщо ви не можете використати це, інша відповідь дає вирішення.
assylias

2
Впевнені, що ви не можете використовувати TheClassYouWant.classзамість виклику getClass ()? Це актуально Java: String.class.getName().
stolsvik


1
@mikerodent Я зрозумів це питання як "усі тести в класі" - але ти маєш рацію, можливо, це не те, чого хотів ОП.
assylias

29

JUnit 5 тепер має анотацію @BeforeAll:

Позначає, що анотований метод повинен бути виконаний перед усіма методами @Test у поточній ієрархії класів або класів; аналогічний @BeforeClass JUnit 4 Такі методи повинні бути статичними.

Анотації про життєвий цикл JUnit 5, здається, нарешті правильно підійшли! Ви можете здогадатися, які анотації доступні, навіть не дивлячись (наприклад, @BeforeEach @AfterAll)


6
У нього така ж проблема була @BeforeClass, як і треба static. Рішення IMO @ AlexR приємніше.
zengr

@zengr прагнуть погодитися з вами: як я вже сказав AlexR, його рішення вимагає всіх тестових класів підкласи з класу CommonTest, якщо він запускається лише один раз. Але це так просто, як може бути простим, і IMHO вам, мабуть, не слід використовувати "фантазійне" рамкове рішення, коли простий механізм доступний з мови. Якщо тільки немає вагомих причин, звичайно. Також використання такої простої речі, як його, з хорошою назвою типу "робить те, що написано на жерсті", допомагає читати.
мійський гризун

Сказавши це, знову ж таки, IMHO, здається, набагато більше виправдання для того, щоб мати анотацію "AfterAll": було б дуже важко і надумано розробити механізм виявлення, коли всі тести були зроблені. І навпаки, звичайно, пуристи повинні, мабуть, сказати, що вам ніколи не доведеться робити "остаточне очищення", тобто кожен "сліз" повинен залишити всі ресурси в первозданному стані ... і вони, мабуть, праві!
мійський гризун

Чи працює це з Maven, де є кілька модулів, кожен зі своїми тестами?
Марк Бун

@mike гризун, в моєму випадку налаштування та виривання тестових файлів у файловій системі до / після кожного тесту, здається, призводить до тупиків у файлах. Поки що я самостійно прийшов до рішення AlexR про встановлення одного разу. У мене є два статичні прапори, вже налаштовані та брудні. setup () викликає очищення (), якщо спочатку виявлено забруднений стан, або якщо збій налаштування призводить до брудного стану. Для очищення після запуску тестів я запускаю їх ще раз. Брудний, зовсім не ідеальний, не в нашому процесі збирання. Ще шукаємо кращий шлях (jUnit 4.12).
Ребекка

9

Коли setUp()знаходиться у надкласі тестового класу (наприклад, AbstractTestBaseнижче), прийняту відповідь можна змінити наступним чином:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

Це повинно працювати для єдиного нестатичного setUp()методу, але я не в змозі створити еквівалент, tearDown()не забиваючись у світ складного відображення ... Баунті вказує на кожного, хто може!


3

Редагувати: Я щойно під час налагодження з'ясував, що клас перед цим тестом перед усіма тестами. Я думаю, анотація @BeforeClass тут найкраща.

Ви можете налаштувати і на конструктор, тестовий клас - це все-таки клас. Я не впевнений, чи це погана практика, оскільки майже всі інші методи зазначаються, але це працює. Ви можете створити такий конструктор:

public UT () {
    // initialize once here
}
@Test
// Some test here...

Цито буде викликано перед тестами, оскільки вони не є статичними.


0

Спробуйте це рішення: https://stackoverflow.com/a/46274919/907576 :

за допомогою @BeforeAllMethods/ @AfterAllMethodsannotation ви можете виконати будь-який метод у класі Test у контексті екземпляра, де доступні всі введені значення.


Покладається на сторонні бібліотеки.
Андрій

0

Моє брудне рішення:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

Я використовую його як базову базу для всіх моїх тестових справ.


публічний клас TestCaseExtended розширює TestCase {приватний статичний булевий isInitialized = false; приватний статичний TestCaseExtended caseExtended; private int serId; @Override public void setUp () кидає виняток {super.setUp (); if (! isInitialized) {caseExtended = new TestCaseExtended (); caseExtended.loadSaveNewSerId (); caseExtended.emptyTestResultsDirectory (); isInitialized = правда; }}
Обі два

0

Якщо ви не хочете примушувати декларування змінної, яка встановлюється і перевіряється на кожному підтесті, то додавання цього до SuperTest може зробити:

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}

0

Я вирішив цю проблему так:

Додайте до базового абстрактного класу (я маю на увазі абстрактний клас, де ви ініціалізуєте свій драйвер методом setUpDriver () ) цю частину коду:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

А тепер, якщо ваші тестові класи поширяться з базового абстрактного класу -> setUpDriver () метод буде виконуватися до першого @Test лише ОДИН раз на запуск.


0

Використовуйте метод @PostConstruct Spring для виконання всіх ініціалізаційних робіт, і цей метод запускається до того, як виконується будь-який з @Test

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.