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

NullPointerException s - исключения, возникающие при попытке использовать ссылку, которая указывает на отсутствие местоположения в памяти (null), как если бы она ссылалась на объект. Вызов метода по нулевой ссылке или попытка получить доступ к полю нулевой ссылки вызовет функцию NullPointerException. Они наиболее распространены, но другие способы перечислены на странице NullPointerException javadoc.

Вероятно, самый быстрый пример кода, который я мог бы придумать для иллюстрации NullPointerException, be:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

В первой строке внутри main я явно устанавливаю ссылку Object obj равной null. Это означает, что у меня есть ссылка, но она не указывает на какой-либо объект. После этого я пытаюсь обработать ссылку так, как если бы она указывала на объект, вызывая метод на нем. Это приводит к NullPointerException, потому что нет кода для выполнения в местоположении, на которое указывает ссылка.

(Это техничность, но я думаю, что она упоминает: ссылка, которая указывает на null, равна 't то же, что и указатель C, указывающий на недопустимую ячейку памяти. Нулевой указатель буквально не указывает на в любом месте , который отличается от указаний на местоположение, которое оказывается недопустимым.)

25
задан Krease 9 December 2014 в 23:51
поделиться

2 ответа

Решением первой проблемы является перемещение логики в расширение 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 на фабричный шаблон, который выполняет внутренний подсчет ссылок, чтобы определить, является ли или не создавать / уничтожать ресурс.

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

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 через Заводской метод:

@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");
    }
}

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

29
ответ дан R. Oosterholt 9 December 2014 в 23:51
поделиться

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

Вам нужно написать класс 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

1
ответ дан Eanlr 9 December 2014 в 23:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: