Убедитесь, что расширение JUnit создает определенное исключение
Предположим, я разрабатываю расширение, которое запрещает именам методов тестирования начинаться с прописных букв.
public class DisallowUppercaseLetterAtBeginning implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
char c = context.getRequiredTestMethod().getName().charAt(0);
if (Character.isUpperCase(c)) {
throw new RuntimeException("test method names should start with lowercase.");
}
}
}
Теперь я хочу проверить, что мое расширение работает так, как ожидалось.
@ExtendWith(DisallowUppercaseLetterAtBeginning.class)
class MyTest {
@Test
void validTest() {
}
@Test
void TestShouldNotBeCalled() {
fail("test should have failed before");
}
}
Как я могу написать тест, чтобы проверить, что попытка выполнить второй метод вызывает исключение RuntimeException с определенным сообщением?
3 ответа:
Другой подход может заключаться в использовании возможностей, предоставляемых новой структурой JUnit 5 - Jupiter.
Я поставил ниже код, который я тестировал с Java 1.8 на Eclipse Oxygen. Код страдает от недостатка элегантности и лаконичности, но, надеюсь, может послужить основой для построения надежного решения для вашего варианта использования мета-тестирования.
Обратите внимание, что это на самом деле как в JUnit 5 проверяется, я отсылаю вас к модульных тестов двигателя Юпитер на GitHub.
public final class DisallowUppercaseLetterAtBeginningTest { @Test void testIt() { // Warning here: I checked the test container created below will // execute on the same thread as used for this test. We should remain // careful though, as the map used here is not thread-safe. final Map<String, TestExecutionResult> events = new HashMap<>(); EngineExecutionListener listener = new EngineExecutionListener() { @Override public void executionFinished(TestDescriptor descriptor, TestExecutionResult result) { if (descriptor.isTest()) { events.put(descriptor.getDisplayName(), result); } // skip class and container reports } @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) {} @Override public void executionStarted(TestDescriptor testDescriptor) {} @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) {} @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) {} }; // Build our test container and use Jupiter fluent API to launch our test. The following static imports are assumed: // // import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass // import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request JupiterTestEngine engine = new JupiterTestEngine(); LauncherDiscoveryRequest request = request().selectors(selectClass(MyTest.class)).build(); TestDescriptor td = engine.discover(request, UniqueId.forEngine(engine.getId())); engine.execute(new ExecutionRequest(td, listener, request.getConfigurationParameters())); // Bunch of verbose assertions, should be refactored and simplified in real code. assertEquals(new HashSet<>(asList("validTest()", "TestShouldNotBeCalled()")), events.keySet()); assertEquals(Status.SUCCESSFUL, events.get("validTest()").getStatus()); assertEquals(Status.FAILED, events.get("TestShouldNotBeCalled()").getStatus()); Throwable t = events.get("TestShouldNotBeCalled()").getThrowable().get(); assertEquals(RuntimeException.class, t.getClass()); assertEquals("test method names should start with lowercase.", t.getMessage()); }
Хотя немного многословный, одним из преимуществ этого подхода является то, что он не требует насмешек и выполнения тестов в том же контейнере JUnit, который будет использоваться позже для реальных модульных тестов.
С небольшим количеством очистки, гораздо более читаемый код достижим. Опять же, источники юнит-Юпитер могут быть отличным источником вдохновения.
Если расширение создает исключение, то метод
@Test
мало что может сделать, так как тестовый бегун никогда не достигнет метода@Test
. В этом случае, я думаю, вы должны проверить расширение вне его использования в нормальном тестовом потоке, т. е. пусть расширение будет SUT. Для расширения, указанного в вашем вопросе, тест может быть примерно таким:@Test public void willRejectATestMethodHavingANameStartingWithAnUpperCaseLetter() throws NoSuchMethodException { ExtensionContext extensionContext = Mockito.mock(ExtensionContext.class); Method method = Testable.class.getMethod("MethodNameStartingWithUpperCase"); Mockito.when(extensionContext.getRequiredTestMethod()).thenReturn(method); DisallowUppercaseLetterAtBeginning sut = new DisallowUppercaseLetterAtBeginning(); RuntimeException actual = assertThrows(RuntimeException.class, () -> sut.beforeEach(extensionContext)); assertThat(actual.getMessage(), is("test method names should start with lowercase.")); } @Test public void willAllowTestMethodHavingANameStartingWithAnLowerCaseLetter() throws NoSuchMethodException { ExtensionContext extensionContext = Mockito.mock(ExtensionContext.class); Method method = Testable.class.getMethod("methodNameStartingWithLowerCase"); Mockito.when(extensionContext.getRequiredTestMethod()).thenReturn(method); DisallowUppercaseLetterAtBeginning sut = new DisallowUppercaseLetterAtBeginning(); sut.beforeEach(extensionContext); // no exception - good enough } public class Testable { public void MethodNameStartingWithUpperCase() { } public void methodNameStartingWithLowerCase() { } }
Однако ваш вопрос предполагает, что приведенное выше расширение является только примером, так что, в более общем плане; если ваше расширение имеет побочный эффект (например, устанавливает что-то в адресуемом контексте, заполняет системное свойство и т. д.), то ваш метод
@Test
может утверждать, что этот побочный эффект присутствует. Например:public class SystemPropertyExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) { System.setProperty("foo", "bar"); } } @ExtendWith(SystemPropertyExtension.class) public class SystemPropertyExtensionTest { @Test public void willSetTheSystemProperty() { assertThat(System.getProperty("foo"), is("bar")); } }
Этот подход имеет преимущество бокового шага потенциально неудобных шагов настройки: создание
Таким образом, на практике, я подозреваю, что вам может понадобиться комбинация этих подходов; для некоторых расширений расширение может быть SUT, а для других расширение может быть проверено путем утверждения против его побочного эффекта(ов).ExtensionContext
и заполнение его состоянием, требуемым вашим тестом, но это может стоить ограничения покрытия теста, так как вы действительно можете протестировать только один результат. И, конечно, это возможно только в том случае, если расширение имеет побочный эффект, который может быть устранен в тестовом случае, использующем расширение.
После того, как я попробовал решения в ответах и вопрос, связанный в комментариях, я получил решение с помощью запуска платформы JUnit.
class DisallowUppercaseLetterAtBeginningTest { @Test void should_succeed_if_method_name_starts_with_lower_case() { TestExecutionSummary summary = runTestMethod(MyTest.class, "validTest"); assertThat(summary.getTestsSucceededCount()).isEqualTo(1); } @Test void should_fail_if_method_name_starts_with_upper_case() { TestExecutionSummary summary = runTestMethod(MyTest.class, "InvalidTest"); assertThat(summary.getTestsFailedCount()).isEqualTo(1); assertThat(summary.getFailures().get(0).getException()) .isInstanceOf(RuntimeException.class) .hasMessage("test method names should start with lowercase."); } private TestExecutionSummary runTestMethod(Class<?> testClass, String methodName) { SummaryGeneratingListener listener = new SummaryGeneratingListener(); LauncherDiscoveryRequest request = request().selectors(selectMethod(testClass, methodName)).build(); LauncherFactory.create().execute(request, listener); return listener.getSummary(); } @ExtendWith(DisallowUppercaseLetterAtBeginning.class) static class MyTest { @Test void validTest() { } @Test void InvalidTest() { fail("test should have failed before"); } } }
Сам JUnit не будет работать
MyTest
, потому что это внутренний класс без@Nested
. Таким образом, в процессе сборки нет неудачных тестов.Обновление
Сам JUnit не будет работать
MyTest
, потому что это внутренний класс без@Nested
. Таким образом, во время сборки нет неудачных тестов процесс.Это не совсем правильно. Сам JUnit также будет работать
MyTest
, например, если" выполнить все тесты " запускается в IDE или в сборке Gradle.Причина, по которой
MyTest
не был выполнен, заключается в том, что я использовал Maven и протестировал его сmvn test
. Maven использует плагин maven Surefire для выполнения тестов. Этот плагин имеет конфигурацию по умолчанию , которая исключает все вложенные классы, такие какMyTest
.Смотрите также этот ответ о "выполнении тестов из внутренних классов через Maven" и связанные вопросы в комментариях.