junit5和junit4_JUnit 5 –条件
junit5和junit4
最近,我們了解了JUnit的新擴(kuò)展模型以及它如何使我們能夠?qū)⒆远x行為注入測(cè)試引擎。 我向你保證要看情況。 現(xiàn)在就開始吧!
條件允許我們?cè)趹?yīng)該執(zhí)行或不應(yīng)該執(zhí)行測(cè)試時(shí)定義靈活的標(biāo)準(zhǔn)。 它們的正式名稱是“ 條件測(cè)試執(zhí)行” 。
總覽
本系列中有關(guān)JUnit 5的其他文章:
- 建立
- 基本
- 建筑
- 擴(kuò)展模型
- 條件
- 注射
- …
在新興的《 JUnit 5用戶指南》中可以找到您將在此處閱讀的更多內(nèi)容以及更多內(nèi)容。 請(qǐng)注意,它基于Alpha版本,因此可能會(huì)發(fā)生變化。
確實(shí),我們鼓勵(lì)我們提出問題或提出請(qǐng)求,以便JUnit 5可以進(jìn)一步改進(jìn)。 請(qǐng)利用這個(gè)機(jī)會(huì)! 這是我們幫助JUnit幫助我們的機(jī)會(huì),因此,如果您能在這里看到一些改善,請(qǐng)確保將其上游 。
如有必要,此帖子將得到更新。 我在這里顯示的代碼示例可以在GitHub上找到 。
條件擴(kuò)展點(diǎn)
還記得我們所說的擴(kuò)展點(diǎn)嗎? 沒有? 簡(jiǎn)而言之:它們很多,每個(gè)都與特定的接口有關(guān)。 可以將這些接口的實(shí)現(xiàn)傳遞給JUnit(帶有@ExtendWith批注),它將在適當(dāng)?shù)臅r(shí)候調(diào)用它們。
對(duì)于條件,需要關(guān)注兩個(gè)擴(kuò)展點(diǎn):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確定是否執(zhí)行容器中的測(cè)試。 在帶有注釋測(cè)試方法的通常情況下,測(cè)試類將是容器。 在同一場(chǎng)景中,各個(gè)測(cè)試方法的執(zhí)行由TestExecutionConditions確定。
(我說“在通常情況下”是因?yàn)椴煌臏y(cè)試引擎對(duì)容器和測(cè)試的解釋可能非常不同。類和方法只是最常見的解釋。)
這已經(jīng)差不多了。 任何條件都應(yīng)實(shí)現(xiàn)這些接口中的一個(gè)或兩個(gè),并在其評(píng)估實(shí)現(xiàn)中進(jìn)行所需的檢查。
@已停用
最簡(jiǎn)單的條件是甚至沒有評(píng)估的條件:如果存在我們手工制作的注釋,我們總是總是禁用測(cè)試。
因此,讓我們創(chuàng)建@Disabled:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(@DisabledCondition.class) public @interface Disabled { }和匹配的擴(kuò)展名:
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;}}像餡餅一樣容易,對(duì)吧? 也是正確的,因?yàn)樗c真正的@Disabled實(shí)現(xiàn)幾乎相同。 只有兩個(gè)小區(qū)別:
- 官方注釋不需要隨身攜帶擴(kuò)展名,因?yàn)樗悄J(rèn)注冊(cè)的。
- 可以給出一個(gè)原因,當(dāng)跳過禁用的測(cè)試時(shí)會(huì)記錄該原因。
小警告(當(dāng)然,您有什么想法?):AnnotationUtils是內(nèi)部API,但其功能可能很快就會(huì)正式可用 。
現(xiàn)在,讓我們嘗試一些不那么瑣碎的事情。
@DisabledOnOs
如果我們使用的是正確的操作系統(tǒng),也許我們只想運(yùn)行一些測(cè)試。
簡(jiǎn)單的解決方案
同樣,我們從注釋開始:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OsCondition.class) public @interface DisabledOnOs {OS[] value() default {};}這次需要一個(gè)值,如果不是,則取一堆,即不應(yīng)運(yùn)行測(cè)試的操作系統(tǒng)。 OS只是一個(gè)枚舉,每個(gè)操作系統(tǒng)都有一個(gè)值。 而且它有一個(gè)方便的靜態(tài)OS define()方法,您猜對(duì)了,它確定了代碼在其上運(yùn)行的操作系統(tǒng)。
這樣,讓我們??轉(zhuǎn)向OsCondition。 它必須檢查注釋是否存在,但還要檢查當(dāng)前的操作系統(tǒng)是否是提供給注釋的操作系統(tǒng)之一。
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); }要實(shí)現(xiàn)@TestExceptOnOs,只需執(zhí)行以下操作就可以了:
@Retention(RetentionPolicy.RUNTIME) @Test @DisabledOnOs(/* somehow get the `value` below */) public @interface TestExceptOnOs {OS[] value() default {};}當(dāng)執(zhí)行測(cè)試并掃描OsCondition :: evaluateIfAnnotated中的@DisabledOnOs時(shí),我們會(huì)發(fā)現(xiàn)它在@TestExceptOnOs上進(jìn)行了元注釋,并且我們的邏輯將正常工作。 但是我找不到一種方法使@DisabledOnOs可以訪問提供給@TestExceptOnOs的OS值。 :( (你能?)
下一個(gè)最佳選擇是對(duì)新注釋簡(jiǎn)單地使用相同的擴(kuò)展名:
@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(""); }……我們完成了。 現(xiàn)在我們確實(shí)可以按照我們希望的方式使用它。
拋光
創(chuàng)建倒置的注釋(如果不在指定的操作系統(tǒng)之一上禁用,則完全相同),但是有了它們,改進(jìn)的名稱和靜態(tài)導(dǎo)入,我們可以在這里結(jié)束:
@TestOn(WINDOWS) void doesNotRunOnWindowsEither() {assertTrue(false); }還不錯(cuò)吧?
在CC-BY 2.0下由CWCS托管主機(jī)發(fā)布
@DisabledIfTestFails
讓我們?cè)賴L試一件事–這次我們將使其變得非常有趣! 假設(shè)有很多(集成?)測(cè)試,并且如果其中一個(gè)測(cè)試由于特定的異常而失敗,那么其他測(cè)試也必然會(huì)失敗。 因此,為了節(jié)省時(shí)間,我們想禁用它們。
那么我們?cè)谶@里需要什么呢? 顯而易見,我們必須以某種方式收集在測(cè)試執(zhí)行過程中引發(fā)的異常。 這必須與測(cè)試類的生存期綁定,因此我們不會(huì)禁用測(cè)試,因?yàn)槟承┊惓?huì)在完全不同的測(cè)試類中發(fā)生。 然后,我們需要一個(gè)條件實(shí)現(xiàn),該條件實(shí)現(xiàn)檢查是否拋出了特定異常,如果存在則禁用測(cè)試。
收集例外
查看擴(kuò)展點(diǎn)列表,我們發(fā)現(xiàn)“異常處理”。 相應(yīng)的接口看起來很有希望:
/*** 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; }因此,我們將實(shí)現(xiàn)handleException來存儲(chǔ)然后重新拋出異常。
您可能還記得我寫的有關(guān)擴(kuò)展和狀態(tài)的內(nèi)容:
引擎在實(shí)例化擴(kuò)展時(shí)以及將實(shí)例保留多長(zhǎng)時(shí)間時(shí)不做任何保證,因此它們必須是無狀態(tài)的。 他們需要維護(hù)的任何狀態(tài)都必須寫入JUnit并從中加載。
好的,所以我們使用商店。 有效地收集了我們想要記住的東西。 我們可以通過傳遞給大多數(shù)擴(kuò)展方法的擴(kuò)展上下文來訪問它。 稍作修改后發(fā)現(xiàn),每個(gè)上下文都有其自己的存儲(chǔ),因此我們必須決定訪問哪個(gè)上下文。
每個(gè)測(cè)試方法(TestExtensionContext)和整個(gè)測(cè)試類(ContainerExtensionContext)都有一個(gè)上下文。 請(qǐng)記住,我們想將在執(zhí)行所有測(cè)試期間拋出的所有異常存儲(chǔ)在一個(gè)類中,但不能存儲(chǔ)更多,即不存儲(chǔ)其他測(cè)試類拋出的異常。 事實(shí)證明,ContainerExtensionContext及其存儲(chǔ)正是我們需要的。
因此,這里我們獲取容器上下文并使用它來存儲(chǔ)一組引發(fā)的異常:
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; }現(xiàn)在添加一個(gè)異常很簡(jiǎn)單:
@Override public void handleException(TestExtensionContext context, Throwable throwable)throws Throwable {if (throwable instanceof Exception)getThrown(context).add((Exception) throwable);throw throwable; }實(shí)際上,這本身就是一個(gè)有趣的擴(kuò)展。 也許它也可以用于分析。 無論如何,我們將要查看拋出的異常,因此我們需要一個(gè)公共方法:
public static Stream<Exception> getThrownExceptions(ExtensionContext context) {return getThrown(context).stream(); }有了這個(gè)擴(kuò)展,任何其他擴(kuò)展都可以檢查到目前為止已經(jīng)拋出了哪些異常。
禁用
其余部分與以前非常相似,因此讓我們快速了解一下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(DisabledIfTestFailedCondition.class) public @interface DisabledIfTestFailedWith {Class<? extends Exception>[] value() default {};}請(qǐng)注意,我們僅在方法上允許使用此注釋。 在測(cè)試類上使用它可能很有意義,但現(xiàn)在讓我們保持簡(jiǎn)單。 因此,我們僅實(shí)現(xiàn)TestExecutionCondition。 在檢查了是否存在我們的注釋之后,我們使用用戶提供的異常類調(diào)用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); }把它放在一起
如果在之前拋出特定類型的異常,這就是我們使用這些批注禁用測(cè)試的方式:
@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中實(shí)現(xiàn)條件:
- 創(chuàng)建所需的注釋和@ExtendWith條件實(shí)現(xiàn)
- 實(shí)現(xiàn)ContainerExecutionCondition,TestExecutionCondition或同時(shí)實(shí)現(xiàn)
- 檢查是否存在新的注釋
- 進(jìn)行實(shí)際檢查并返回結(jié)果
我們還看到,這可以與其他擴(kuò)展點(diǎn)結(jié)合使用,如何使用商店來保留信息,并且自定義注釋可以使擴(kuò)展使用起來更加優(yōu)雅。
有關(guān)標(biāo)記擴(kuò)展點(diǎn)的更多樂趣,請(qǐng)?jiān)谟懻搮?shù)注入時(shí)查看本系列的下一篇文章。
翻譯自: https://www.javacodegeeks.com/2016/05/junit-5-conditions.html
junit5和junit4
總結(jié)
以上是生活随笔為你收集整理的junit5和junit4_JUnit 5 –条件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓大白磁力播(安卓大白)
- 下一篇: java jinq_将JINQ与JPA和