2017-11-11 132 views
4

假设我开发了一个不允许测试方法名以大写字符开头的扩展。检查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?

+0

此扩展只是一个示例,真正的扩展是特定于域的。我的问题是更多关于如何测试我的扩展。 –

+1

我在https://stackoverflow.com/questions/46841243/how-to-test-extension-implementations中提出了一个类似的问题,我希望很快就会有一个测试工具用于扩展。 – mkobit

回答

0

试图在答案的解决方案,并在评论中链接的问题后,我结束了使用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。所以在构建过程中没有失败的测试。

1

如果扩展抛出一个异常,那么就没有太大的@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() { 

    } 
} 

然而,你的问题表明,上述的扩展仅是一个例子的话,更普遍;如果你的扩展有副作用(例如,在可寻址的上下文中设置某些东西,填充系统属性等),那么你的方法可以断言存在这种副作用。例如:

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

这种方法有副作用的步进的潜在尴尬的设置步骤的好处:创建ExtensionContext,并与你的测试所需要的状态填充它,但它可能会在限制测试覆盖成本因为你真的只能测试一个结果。而且,当然,只有扩展具有可以在使用扩展的测试用例中可以消除的副作用时才是可行的。

因此,在实践中,我怀疑你可能需要这些方法的组合;对于一些扩展,扩展可以是SUT,对于其他扩展可以通过断言其副作用来测试。

2

另一种方法可以是使用由新的JUnit 5提供的设施 - 木星框架。

我把我与Java 1.8 Eclipse的氧气测试下面的代码。代码缺乏优雅和简洁,但可以作为构建元测试用例的强大解决方案的基础。

注意,这实际上是JUnit的5是如何测试时,我是指你the unit tests of the Jupiter engine on 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的容器执行测试,稍后会为真正的单元测试中使用。

随着位的清理,更加可读代码是可以实现的。同样,JUnit-Jupiter资源可以成为一个很好的灵感来源。

相关问题