2017-11-11 85 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資源可以成爲一個很好的靈感來源。

相關問題