Убедитесь, что расширение 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" и связанные вопросы в комментариях.