Как разделить логику JUnit BeforeClass между несколькими тестовыми классами

В настоящее время все мои тесты JUnit распространяются от общего базового класса, который предоставляет методы, помеченные аннотациями @BeforeClass и @AfterClass, - все это на самом деле - это настройка множества статических ресурсов/сервисов для использования тестов.

Это кажется мне неудобным по нескольким причинам:

  • Часть JUnit4 (по моему мнению) заключается в том, что нам больше не нужно классическое тестовое наследование.
  • Когда я запускаю эти тесты как часть набора, а не отдельно (что мы часто делаем), @BeforeClass и @AfterClass получают вызов несколько раз, замедляя тесты - мы действительно должны называть их только один раз

То, что я хотел бы сделать, - это как-то переместить текущую логику BeforeClass/AfterClass из цепочки наследования и во что-то, что может быть использовано отдельными тестами и пакетом в целом.

Можно ли это сделать? Если да, то как? (Если это имеет значение, я использую JUnit 4.7, и это может быть трудно продать, чтобы обновить другую версию)

Ответ 1

Решение первой проблемы состоит в том, чтобы переместить логику в расширение org.junit.rules.ExternalResource, подключенного к тесту, с помощью @ClassRule, введенный в JUnit 4.9:

public class MyTest {
    @ClassRule
    public static final TestResources res = new TestResources();
    @Test
    public void testFoo() {
        // test logic here
    }
}

public class TestResources extends ExternalResource {
    protected void before() {
        // Setup logic that used to be in @BeforeClass
    }
    protected void after() {
        // Setup logic that used to be in @AfterClass
    }
}

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

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

Просто измените создание ресурса @ClassRule на шаблон factory, который внутренне ссылается на подсчет, чтобы определить, создавать или уничтожать ресурс.

Например (обратите внимание, что это грубо и может потребоваться некоторая настройка/обработка ошибок для обеспечения надежности):

public class TestResources extends ExternalResource {
    private static int refCount = 0;

    private static TestResources currentInstance;

    public static TestResources getTestResources () {
        if (refCount == 0) {
            // currentInstance either hasn't been created yet, or after was called on it - create a new one
            currentInstance = new TestResources();
        }
        return currentInstance;
    }

    private TestResources() {
        System.out.println("TestResources construction");
        // setup any instance vars
    }

    protected void before() {
        System.out.println("TestResources before");
        try {
            if (refCount == 0) {
                System.out.println("Do actual TestResources init");
            }
        }
        finally {
            refCount++;
        }
    }

    protected void after() {
        System.out.println("TestResources after");
        refCount--;
        if (refCount == 0) {
            System.out.println("Do actual TestResources destroy");
        }
    }
}

Оба класса вашего класса/теста просто используют ресурс как @ClassResource через метод factory:

@RunWith(Suite.class)
@SuiteClasses({FooTest.class, BarTest.class})
public class MySuite {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();
    @BeforeClass
    public static void suiteSetup() {
        System.out.println("Suite setup");
    }
    @AfterClass
    public static void suiteTeardown() {
        System.out.println("Suite teardown");
    }
}
public class FooTest {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();

    @Test
    public void testFoo() {
        System.out.println("testFoo");
    }
}
public class BarTest {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();

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

При запуске отдельного теста пересчет не будет иметь никакого эффекта - "фактический init" и "фактическое отключение" произойдет только один раз. При запуске через пакет пакет будет создавать TestResource, и отдельные тесты будут просто повторно использовать уже созданный (refcounting не позволяет ему фактически уничтожаться и воссоздаваться между тестами в пакете).

Ответ 2

Вы можете использовать @BeforeClass и @AfterClass В ЛЮКОМ КЛАССЕ.

Это запустит методы до того, как какой-либо из тестовых классов в пакете будет выполнен и после завершения всех тестовых классов (соответственно)

Таким образом вы можете запускать их только один раз.

//..usual @RunWith etc annotations here
public class MySuite{

@BeforeClass
public static void setup(){

}

@AfterClass
public static void tearDown(){

}

}

Ответ 3

Я столкнулся с подобной проблемой (Spring не был вариантом, и я не пишу TestSuites в maven проектах), поэтому я написал простой бегун junit для решения этой проблемы.

Вам нужно написать класс SharedResource и пометить свой тест, чтобы потребовать этот ресурс.

public class SampleSharedResource implements SharedResource {
public void initialize() throws Exception {
    //init your resource
}

}

@RunWith(JUnitSharedResourceRunner.class)
@JUnitSharedResourceRunner.WithSharedResources({SampleSharedResource.class})
public class SharedResourceRunnerATest {
...

Источники в https://github.com/eanlr/junit-shared-resources-runner