junit5和junit4_JUnit 5 –条件
junit5和junit4
最近,我們了解了JUnit的新擴展模型以及它如何使我們能夠將自定義行為注入測試引擎。 我向你保證要看情況。 現在就開始吧!
條件允許我們在應該執行或不應該執行測試時定義靈活的標準。 它們的正式名稱是“ 條件測試執行” 。
總覽
本系列中有關JUnit 5的其他文章:
- 建立
- 基本
- 建筑
- 擴展模型
- 條件
- 注射
- …
在新興的《 JUnit 5用戶指南》中可以找到您將在此處閱讀的更多內容以及更多內容。 請注意,它基于Alpha版本,因此可能會發生變化。
確實,我們鼓勵我們提出問題或提出請求,以便JUnit 5可以進一步改進。 請利用這個機會! 這是我們幫助JUnit幫助我們的機會,因此,如果您能在這里看到一些改善,請確保將其上游 。
如有必要,此帖子將得到更新。 我在這里顯示的代碼示例可以在GitHub上找到 。
條件擴展點
還記得我們所說的擴展點嗎? 沒有? 簡而言之:它們很多,每個都與特定的接口有關。 可以將這些接口的實現傳遞給JUnit(帶有@ExtendWith批注),它將在適當的時候調用它們。
對于條件,需要關注兩個擴展點:ContainerExecutionCondition和TestExecutionCondition。
public interface ContainerExecutionCondition extends Extension {/*** Evaluate this condition for the supplied ContainerExtensionContext.** An enabled result indicates that the container should be executed;* whereas, a disabled result indicates that the container should not* be executed.** @param context the current ContainerExtensionContext*/ConditionEvaluationResult evaluate(ContainerExtensionContext context);}public interface TestExecutionCondition extends Extension {/*** Evaluate this condition for the supplied TestExtensionContext.** An enabled result indicates that the test should be executed;* whereas, a disabled result indicates that the test should not* be executed.** @param context the current TestExtensionContext*/ConditionEvaluationResult evaluate(TestExtensionContext context);}ContainerExecutionCondition確定是否執行容器中的測試。 在帶有注釋測試方法的通常情況下,測試類將是容器。 在同一場景中,各個測試方法的執行由TestExecutionConditions確定。
(我說“在通常情況下”是因為不同的測試引擎對容器和測試的解釋可能非常不同。類和方法只是最常見的解釋。)
這已經差不多了。 任何條件都應實現這些接口中的一個或兩個,并在其評估實現中進行所需的檢查。
@已停用
最簡單的條件是甚至沒有評估的條件:如果存在我們手工制作的注釋,我們總是總是禁用測試。
因此,讓我們創建@Disabled:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(@DisabledCondition.class) public @interface Disabled { }和匹配的擴展名:
public class DisabledConditionimplements ContainerExecutionCondition, TestExecutionCondition {private static final ConditionEvaluationResult ENABLED =ConditionEvaluationResult.enabled("@Disabled is not present");@Overridepublic ConditionEvaluationResult evaluate(ContainerExtensionContext context) {return evaluateIfAnnotated(context.getElement());}@Overridepublic ConditionEvaluationResult evaluate(TestExtensionContext context) {return evaluateIfAnnotated(context.getElement());}private ConditionEvaluationResult evaluateIfAnnotated(AnnotatedElement element) {Optional<Disabled> disabled = AnnotationUtils.findAnnotation(element, Disabled.class);if (disabled.isPresent())return ConditionEvaluationResult.disabled(element + " is @Disabled");return ENABLED;}}像餡餅一樣容易,對吧? 也是正確的,因為它與真正的@Disabled實現幾乎相同。 只有兩個小區別:
- 官方注釋不需要隨身攜帶擴展名,因為它是默認注冊的。
- 可以給出一個原因,當跳過禁用的測試時會記錄該原因。
小警告(當然,您有什么想法?):AnnotationUtils是內部API,但其功能可能很快就會正式可用 。
現在,讓我們嘗試一些不那么瑣碎的事情。
@DisabledOnOs
如果我們使用的是正確的操作系統,也許我們只想運行一些測試。
簡單的解決方案
同樣,我們從注釋開始:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OsCondition.class) public @interface DisabledOnOs {OS[] value() default {};}這次需要一個值,如果不是,則取一堆,即不應運行測試的操作系統。 OS只是一個枚舉,每個操作系統都有一個值。 而且它有一個方便的靜態OS define()方法,您猜對了,它確定了代碼在其上運行的操作系統。
這樣,讓我們??轉向OsCondition。 它必須檢查注釋是否存在,但還要檢查當前的操作系統是否是提供給注釋的操作系統之一。
public class OsCondition implements ContainerExecutionCondition, TestExecutionCondition {// both `evaluate` methods forward to `evaluateIfAnnotated` as aboveprivate ConditionEvaluationResult evaluateIfAnnotated(AnnotatedElement element) {Optional<DisabledOnOs> disabled = AnnotationUtils.findAnnotation(element, DisabledOnOs.class);if (disabled.isPresent())return disabledIfOn(disabled.get().value());return ENABLED;}private ConditionEvaluationResult disabledIfOn(OS[] disabledOnOs) {OS os = OS.determine();if (Arrays.asList(disabledOnOs).contains(os))return ConditionEvaluationResult.disabled("Test is disabled on " + os + ".");elsereturn ConditionEvaluationResult.enabled("Test is not disabled on " + os + ".");}}我們可以如下使用它:
@Test @DisabledOnOs(OS.WINDOWS) void doesNotRunOnWindows() {assertTrue(false); }真好
少禮
但是我們可以做得更好! 借助JUnit的可自定義注釋,我們可以使此條件更加平滑:
@TestExceptOnOs(OS.WINDOWS) void doesNotRunOnWindowsEither() {assertTrue(false); }要實現@TestExceptOnOs,只需執行以下操作就可以了:
@Retention(RetentionPolicy.RUNTIME) @Test @DisabledOnOs(/* somehow get the `value` below */) public @interface TestExceptOnOs {OS[] value() default {};}當執行測試并掃描OsCondition :: evaluateIfAnnotated中的@DisabledOnOs時,我們會發現它在@TestExceptOnOs上進行了元注釋,并且我們的邏輯將正常工作。 但是我找不到一種方法使@DisabledOnOs可以訪問提供給@TestExceptOnOs的OS值。 :( (你能?)
下一個最佳選擇是對新注釋簡單地使用相同的擴展名:
@Retention(RetentionPolicy.RUNTIME) @ExtendWith(OsCondition.class) @Test public @interface TestExceptOnOs {OS[] value() default {};}然后我們拉皮條OsCondition :: evaluateIfAnnotated包括新的案例…
private ConditionEvaluationResult evaluateIfAnnotated(AnnotatedElement element) {Optional<DisabledOnOs> disabled = AnnotationUtils.findAnnotation(element, DisabledOnOs.class);if (disabled.isPresent())return disabledIfOn(disabled.get().value());Optional<TestExceptOnOs> testExcept = AnnotationUtils.findAnnotation(element, TestExceptOnOs.class);if (testExcept.isPresent())return disabledIfOn(testExcept.get().value());return ConditionEvaluationResult.enabled(""); }……我們完成了。 現在我們確實可以按照我們希望的方式使用它。
拋光
創建倒置的注釋(如果不在指定的操作系統之一上禁用,則完全相同),但是有了它們,改進的名稱和靜態導入,我們可以在這里結束:
@TestOn(WINDOWS) void doesNotRunOnWindowsEither() {assertTrue(false); }還不錯吧?
在CC-BY 2.0下由CWCS托管主機發布
@DisabledIfTestFails
讓我們再嘗試一件事–這次我們將使其變得非常有趣! 假設有很多(集成?)測試,并且如果其中一個測試由于特定的異常而失敗,那么其他測試也必然會失敗。 因此,為了節省時間,我們想禁用它們。
那么我們在這里需要什么呢? 顯而易見,我們必須以某種方式收集在測試執行過程中引發的異常。 這必須與測試類的生存期綁定,因此我們不會禁用測試,因為某些異常會在完全不同的測試類中發生。 然后,我們需要一個條件實現,該條件實現檢查是否拋出了特定異常,如果存在則禁用測試。
收集例外
查看擴展點列表,我們發現“異常處理”。 相應的接口看起來很有希望:
/*** ExceptionHandlerExtensionPoint defines the API for Extension Extensions* that wish to react to thrown exceptions in tests.** [...]*/ public interface ExceptionHandlerExtensionPoint extends ExtensionPoint {/*** React to a throwable which has been thrown by a test method.** Implementors have to decide if they* * - Rethrow the incoming throwable* - Throw a newly constructed Exception or Throwable* - Swallow the incoming throwable** [...]*/void handleException(TestExtensionContext context, Throwable throwable)throws Throwable; }因此,我們將實現handleException來存儲然后重新拋出異常。
您可能還記得我寫的有關擴展和狀態的內容:
引擎在實例化擴展時以及將實例保留多長時間時不做任何保證,因此它們必須是無狀態的。 他們需要維護的任何狀態都必須寫入JUnit并從中加載。
好的,所以我們使用商店。 有效地收集了我們想要記住的東西。 我們可以通過傳遞給大多數擴展方法的擴展上下文來訪問它。 稍作修改后發現,每個上下文都有其自己的存儲,因此我們必須決定訪問哪個上下文。
每個測試方法(TestExtensionContext)和整個測試類(ContainerExtensionContext)都有一個上下文。 請記住,我們想將在執行所有測試期間拋出的所有異常存儲在一個類中,但不能存儲更多,即不存儲其他測試類拋出的異常。 事實證明,ContainerExtensionContext及其存儲正是我們需要的。
因此,這里我們獲取容器上下文并使用它來存儲一組引發的異常:
private static final Namespace NAMESPACE = Namespace.of("org", "codefx", "CollectExceptions"); private static final String THROWN_EXCEPTIONS_KEY = "THROWN_EXCEPTIONS_KEY";@SuppressWarnings("unchecked") private static Set<Exception> getThrown(ExtensionContext context) {ExtensionContext containerContext = getAncestorContainerContext(context).orElseThrow(IllegalStateException::new);return (Set<Exception>) containerContext.getStore(NAMESPACE).getOrComputeIfAbsent(THROWN_EXCEPTIONS_KEY,ignoredKey -> new HashSet<>()); }private static Optional<ExtensionContext> getAncestorContainerContext(ExtensionContext context) {Optional<ExtensionContext> containerContext = Optional.of(context);while (containerContext.isPresent()&& !(containerContext.get() instanceof ContainerExtensionContext))containerContext = containerContext.get().getParent();return containerContext; }現在添加一個異常很簡單:
@Override public void handleException(TestExtensionContext context, Throwable throwable)throws Throwable {if (throwable instanceof Exception)getThrown(context).add((Exception) throwable);throw throwable; }實際上,這本身就是一個有趣的擴展。 也許它也可以用于分析。 無論如何,我們將要查看拋出的異常,因此我們需要一個公共方法:
public static Stream<Exception> getThrownExceptions(ExtensionContext context) {return getThrown(context).stream(); }有了這個擴展,任何其他擴展都可以檢查到目前為止已經拋出了哪些異常。
禁用
其余部分與以前非常相似,因此讓我們快速了解一下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(DisabledIfTestFailedCondition.class) public @interface DisabledIfTestFailedWith {Class<? extends Exception>[] value() default {};}請注意,我們僅在方法上允許使用此注釋。 在測試類上使用它可能很有意義,但現在讓我們保持簡單。 因此,我們僅實現TestExecutionCondition。 在檢查了是否存在我們的注釋之后,我們使用用戶提供的異常類調用disableIfExceptionWasThrown:
private ConditionEvaluationResult disableIfExceptionWasThrown(TestExtensionContext context,Class<? extends Exception>[] exceptions) {return Arrays.stream(exceptions).filter(ex -> wasThrown(context, ex)).findAny().map(thrown -> ConditionEvaluationResult.disabled(thrown.getSimpleName() + " was thrown.")).orElseGet(() -> ConditionEvaluationResult.enabled("")); }private static boolean wasThrown(TestExtensionContext context, Class<? extends Exception> exception) {return CollectExceptionExtension.getThrownExceptions(context).map(Object::getClass).anyMatch(exception::isAssignableFrom); }把它放在一起
如果在之前拋出特定類型的異常,這就是我們使用這些批注禁用測試的方式:
@CollectExceptions class DisabledIfFailsTest {private static boolean failedFirst = false;@Testvoid throwException() {System.out.println("I failed!");failedFirst = true;throw new RuntimeException();}@Test@DisabledIfTestFailedWith(RuntimeException.class)void disableIfOtherFailedFirst() {System.out.println("Nobody failed yet! (Right?)");assertFalse(failedFirst);}}摘要
哇,那是很多代碼! 但是到目前為止,我們真的知道如何在JUnit 5中實現條件:
- 創建所需的注釋和@ExtendWith條件實現
- 實現ContainerExecutionCondition,TestExecutionCondition或同時實現
- 檢查是否存在新的注釋
- 進行實際檢查并返回結果
我們還看到,這可以與其他擴展點結合使用,如何使用商店來保留信息,并且自定義注釋可以使擴展使用起來更加優雅。
有關標記擴展點的更多樂趣,請在討論參數注入時查看本系列的下一篇文章。
翻譯自: https://www.javacodegeeks.com/2016/05/junit-5-conditions.html
junit5和junit4
總結
以上是生活随笔為你收集整理的junit5和junit4_JUnit 5 –条件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓大白磁力播(安卓大白)
- 下一篇: 怎么防止服务器被入侵(如何防止服务器进入