Внимание! Механизм правил, описанный в этой статье касается только JUnit 4. Начиная с JUnit 5 механизм правил является устаревшим. Его заменил новый механизм расширений.
Правила используются для добавления определённых действий, которые должны выполняться для каждого теста в классе.
В JUnit 4 есть несколько уже встроенных правил: TemporaryFolder, ExternalResource, ErrorCollector, VerifierRule, TestWatchman / TestWatcher, TestName, Timeout, ExpectedException, ClassRule, RuleChain.
Правило TemporaryFolder
Позволяет создавать файлы и каталоги, которые удаляются по завершении метода.
1 2 3 4 5 6 7 8 9 10 11 |
public static class HasTempFolder { @Rule public final TemporaryFolder folder = new TemporaryFolder(); @Test public void testUsingTempFolder() throws IOException { File createdFile = folder.newFile("myfile.txt"); File createdFolder = folder.newFolder("subfolder"); // ... } } |
По умолчанию никакой ошибки не возникает, если файл или каталог не удаётся удалить. Начиная с версии 4.13 TemporaryFolder позволяет задать строгую проверку удаления, которая вызывает AssertionError, если файл(ы) и/или каталог(и) не удаётся удалить. Для включения этой строгой проверки нужно использовать метод builder:
1 2 |
@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build(); |
Правила наследники ExternalResource
ExternalResource — базовый класс для правил (вроде TemporaryFolder), который настраивает внешний ресурс при старте (сокет, сервер, подключение к базе данных и т. д.) и гарантирует его корректное освобождение в конце.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static class UsesExternalResource { Server myServer = new Server(); @Rule public final ExternalResource resource = new ExternalResource() { @Override protected void before() throws Throwable { myServer.connect(); }; @Override protected void after() { myServer.disconnect(); }; }; @Test public void testFoo() { new Client().run(myServer); } } |
Правило ErrorCollector
Используется для сбора ошибок, произошедших во время выполнения теста. Пример:
1 2 3 4 5 6 7 8 9 10 |
public static class UsesErrorCollectorTwice { @Rule public final ErrorCollector collector = new ErrorCollector(); @Test public void example() { collector.addError(new Throwable("first thing went wrong")); collector.addError(new Throwable("second thing went wrong")); } } |
Правило Verifier
Базовый класс для правил вроде ErrorCollector, который может сделать результат теста неуспешным, который был бы успешным в противном случае.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private static String sequence; public static class UsesVerifier { @Rule public final Verifier collector = new Verifier() { @Override protected void verify() { sequence += "verify "; } }; @Test public void example() { sequence += "test "; } @Test public void verifierRunsAfterTest() { sequence = ""; assertThat(testResult(UsesVerifier.class), isSuccessful()); assertEquals("test verify ", sequence); } } |
Правило TestWatcher / TestWatchman
TestWatcher заменяет
TestWatchman начиная с версии 4.9. Он реализует
TestRule, а не
MethodRule.
http://junit.org/javadoc/latest/org/junit/rules/TestWatcher.html
TestWatchman был добавлен в JUnit 4.7, он использует
MethodRule, который сейчас устарел.
http://junit.org/javadoc/latest/org/junit/rules/TestWatchman.html
TestWatcher (и устаревший TestWatchman) — базовые классы для правил, которые записывают что-либо о тестируемом действии без его модификации. Например, этот класс будет хранить лог для каждого пройденного и проваленного теста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import static org.junit.Assert.fail; import org.junit.AssumptionViolatedException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class WatchmanTest { private static String watchedLog; @Rule public final TestRule watchman = new TestWatcher() { @Override public Statement apply(Statement base, Description description) { return super.apply(base, description); } @Override protected void succeeded(Description description) { watchedLog += description.getDisplayName() + " " + "success!\n"; } @Override protected void failed(Throwable e, Description description) { watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n"; } @Override protected void skipped(AssumptionViolatedException e, Description description) { watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n"; } @Override protected void starting(Description description) { super.starting(description); } @Override protected void finished(Description description) { super.finished(description); } }; @Test public void fails() { fail(); } @Test public void succeeds() { } } |
Правило TestName
Правило TestName делает доступным имя теста внутри самого теста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class NameRuleTest { @Rule public final TestName name = new TestName(); @Test public void testA() { assertEquals("testA", name.getMethodName()); } @Test public void testB() { assertEquals("testB", name.getMethodName()); } } |
Правило Timeout
Правило Timeout задаёт таймаут для всех тестовых методов в классе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static class HasGlobalTimeout { public static String log; @Rule public final TestRule globalTimeout = Timeout.millis(20); @Test public void testInfiniteLoop1() { log += "ran1"; for(;;) {} } @Test public void testInfiniteLoop2() { log += "ran2"; for(;;) {} } } |
Правило ExpectedException
Позволяет задавать ожидаемые исключения и сообщения внутри метода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static class HasExpectedException { @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void throwsNothing() { } @Test public void throwsNullPointerException() { thrown.expect(NullPointerException.class); throw new NullPointerException(); } @Test public void throwsNullPointerExceptionWithMessage() { thrown.expect(NullPointerException.class); thrown.expectMessage("happened?"); thrown.expectMessage(startsWith("What")); throw new NullPointerException("What happened?"); } } |
ClassRule
Аннотация ClassRule позволяет добавляеть статические поля, которые действуют на операции во всём классе. Любой дочерний класс от ParentRunner, включая стандартные классы BlockJUnit4ClassRunner и Suite , поддерживает ClassRules .
Пример тестов, в которых соединение с сервером происходит один раз перед запуском всех тестов, и отключение от сервера происходит после того, как они все закончатся.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@RunWith(Suite.class) @SuiteClasses({A.class, B.class, C.class}) public class UsesExternalResource { public static final Server myServer = new Server(); @ClassRule public static final ExternalResource resource = new ExternalResource() { @Override protected void before() throws Throwable { myServer.connect(); }; @Override protected void after() { myServer.disconnect(); }; }; } |
RuleChain
Правило RuleChain позволяет выстраивать цепочку правил:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static class UseRuleChain { @Rule public final TestRule chain = RuleChain .outerRule(new LoggingRule("outer rule")) .around(new LoggingRule("middle rule")) .around(new LoggingRule("inner rule")); @Test public void example() { assertTrue(true); } } |
Выведет в лог следующее:
1 2 3 4 5 6 |
starting outer rule starting middle rule starting inner rule finished inner rule finished middle rule finished outer rule |
Пользовательские правила
Большинство пользовательских правил могут быть реализованы как расширение правила ExternalResource. Если же вам нужно больше информации о тестовом классе или методе, то вы можете реализовать интерфейс TestRule.
1 2 3 4 5 6 7 8 9 10 |
import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class IdentityRule implements TestRule { @Override public Statement apply(final Statement base, final Description description) { return base; } } |
Конечно, большая польза от реализации интерфейса TestRule приходит при использовании его в комбинации с пользовательскими конструкторами, добавлении новых методов в класс для использования в тестах и оборачивании полученного Statement в новом Statement. Например, рассмотрим следующее правило, которое предоставляет именнованный логгер для каждого теста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package org.example.junit; import java.util.logging.Logger; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class TestLogger implements TestRule { private Logger logger; public Logger getLogger() { return this.logger; } @Override public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { logger = Logger.getLogger( description.getTestClass().getName() + '.' + description.getDisplayName()); base.evaluate(); } }; } } |
Затем правило может быть применено как здесь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.logging.Logger; import org.example.junit.TestLogger; import org.junit.Rule; import org.junit.Test; public class MyLoggerTest { @Rule public final TestLogger logger = new TestLogger(); @Test public void checkOutMyLogger() { final Logger log = logger.getLogger(); log.warn("Your test is showing!"); } } |
Кто вообще решает, что какой-то инструмент «устарел»?
Кому дано это право и кем?