亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何分析JUnit 5中的Extension Model擴展模型

發布時間:2022-01-11 14:31:14 來源:億速云 閱讀:108 作者:柒染 欄目:編程語言

這篇文章主要為大家分析了如何分析JUnit  5中的Extension Model擴展模型的相關知識點,內容詳細易懂,操作細節合理,具有一定參考價值。如果感興趣的話,不妨跟著跟隨小編一起來看看,下面跟著小編一起深入學習“如何分析JUnit  5中的Extension Model擴展模型”的知識吧。

JUnit 4 的擴展模型

我們先來看看 JUnit 4 中是如何實現擴展的。在 JUnit 4  中實現擴展主要是通過兩個,有時也互有重疊的擴展機制:運行器(Runners)和規則(Rules)。

運行器(Runners)

測試運行器負責管理諸多測試的生命周期,包括它們的實例化、setup/teardown 方法的調用、測試運行、異常處理、發送消息等。在 JUnit 4  提供的運行器實現中,它負責了這所有的事情。

在 JUnit 4 中,擴展 JUnit  的唯一方法是:創建一個新的運行器,然后使用它標記你新的測試類:@Runwith(MyRunner.class)。這樣 JUnit  就會識別并使用它來運行測試,而不會使用其默認的實現。

這個方式很重,對于小定制小擴展來說很不方便。同時它有個很苛刻的限制:一個測試類只能用一個運行器來跑,這意味著你不能組合不同的運行器。也即是說,你不能同時享受到兩個以上運行器提供的特性,比如說不能同時使用  Mockito 和 Spring 的運行器,等。

規則(Rules)

為了克服這個限制,JUnit 4.7 中引入了規則的概念,它是指測試類中特別的注解字段。 JUnit 4  會把測試方法(與一些其他的行為)包裝一層傳給規則。規則因此可以在測試代碼執行前后插入,執行一些代碼。很多時候在測試方法中也會直接調規則類上的方法。

這里有一個例子,展示的是 temporary folder (臨時文件夾)規則:

public static class HasTempFolder {     @Rule     public TemporaryFolder folder= new TemporaryFolder();       @Test     public void testUsingTempFolder() throws IOException {         File createdFile= folder.newFile("myfile.txt");         File createdFolder= folder.newFolder("subfolder");         // ...     } }

因為 @Rule 注解的存在,JUnit 會先把測試方法 testUsingTempFolder 包裝成一個可執行代碼塊,傳給 folder  規則。這個規則的作用是執行時, 由 folder  創建一個臨時目錄,執行測試,測試完成后刪除臨時目錄。因此,在測試內部可以放心地在臨時目錄下創建文件和文件夾。

當然還有其他的規則,比如允許你在 Swing 的事件分發線程中執行測試 的規則,負責連接和斷開數據庫的規則,以及讓運行過久的測試直接超時的規則等。

規則特性其實已經是個很大的改進了,不過仍有局限,它只能在測試運行之前或之后定制操作。如果你想在此之外的時間點進行擴展,這個特性也無能為力了。

現狀

總而言之,在 JUnit 4 中存在兩種不同的擴展機制,兩者均各有局限,并且功能還有重疊的部分。在 JUnit 4  下編寫干凈的擴展是很難的事。此外,即使你嘗試組合兩種不同的擴展方式,通常也不會一帆風順,有時它可能根本不按照開發者期望的方式工作。

如何分析JUnit  5中的Extension Model擴展模型

JUnit 5 的擴展模型

Junit Lambda 項目成立伊始便有幾點核心準則,其中一條便是“擴展點優于新特性”。這個準則其實也就是新版本 JUnit  中最重要的擴展機制了——并非唯一,但無疑是最重要之一。

擴展點

JUnit 5 擴展可以聲明其主要關注的是測試生命周期的哪部分。JUnit 5  引擎在處理測試時,它會依次檢查這些擴展點,并調用每個已注冊的擴展。大體來說,這些擴展點出現次序如下:

  • 測試類實例 后處理

  • BeforeAll 回調

  • 測試及容器執行條件檢查

  • BeforeEach 回調

  • 參數解析

  • 測試執行前

  • 測試執行后

  • 異常處理

  • AfterEach 回調

  • AfterAll 回調

(如果上面有你覺得不甚清晰或理解的點,請不用擔心,我們接下來會挑其中的一些來講解。)

每個擴展點都對應一個接口。接口方法會接受一些參數,一些擴展點所處生命周期的上下文信息。比如,被測實例與方法、測試的名稱、參數、注解等信息。

一個擴展可以實現任意個以上的接口方法,引擎會在調用它們時傳入相應的上下文信息作為參數。有了這些信息,擴展就可以放心地實現所需的功能了。

無狀態

這里我們需要考慮一個重要的細節:引擎對擴展實例的初始化時間、實例的生存時間未作出任何規約和保證,因此,擴展必須是無狀態的。如果一個擴展需要維持任何狀態信息,那么它必須使用  JUnit 提供的一個倉庫(store)來進行信息讀取和寫入。

這樣做的原因有幾個:

  • 擴展的初始化時機和方式對引擎是未知的(每個測試實例化一次?每個類實例化一次?還是每次運行實例化一次?)。

  • JUnit 不想額外維護和管理每個擴展創建的實例。

  • 如果擴展之間想要進行通信,那么無論如何 JUnit 都必須提供一個數據交互的機制。

應用擴展

創建完擴展后,接下來需要做的就僅僅是告訴 JUnit  它的存在。這可以通過在需要使用該擴展的測試類或測試方法上添加一個@ExtendWith(MyExtension.class) 簡單實現。

其實,還有另一種更簡明的方式。不過要理解那種方式,我們必須先看一下 JUnit 的擴展模型中還有哪些內容。

自定義注解

JUnit 5 的 API  大部分是基于注解的,而且引擎在檢查注解時還做了些額外的工作:它不僅會查找字段、類、參數上應用的注解,還會注解上的注解。引擎會把找到的所有注解都應用到被注解元素上。注解另一個注解可以通過所謂的元注解做到,酷的是  Junit 提供的所有注解都說得上是元注解了。

它的意義在于,JUnit 5 中我們就能夠創建并組合不同的注解了,并且它們具備組合多個注解特性的能力:

/**  * We define a custom annotation that:  * - stands in for '@Test' so that the method gets executed  * - has the tag "integration" so we can filter by that,  *   e.g. when running tests from the command line  */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Test @Tag("integration") public @interface IntegrationTest { }

這個自定義的“集成測試”注解 @IntegrationTest 可以這樣使用:

@IntegrationTest void runsWithCustomAnnotation() {     // this gets executed     // even though `@IntegrationTest` is not defined by JUnit }

進一步我們可以為擴展使用更簡明的注解:

@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(ExternalDatabaseExtension.class) public @interface Database { }

現在我們可以直接使用 @Database  注解了,而不需要再聲明測試應用了特定的擴展@ExtendWith(ExternalDatabaseExtension.class)。并且由于我們把注解類型  ElementType.ANNOTATION_TYPE 也添加到擴展支持的目標類型中去了,因此該注解也可以被我們或他人進一步的使用、組合。

例子

假設現在有個場景,我想量化一下測試運行花費的時間。首先,可以先創建一個我們想要的注解:

@Target({ TYPE, METHOD, ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(BenchmarkExtension.class) public @interface Benchmark { }

注解聲明其應用了 BenchmarkExtension 擴展,這是我們接下來要實現的。TODOLIST 如下:

  • 計算所有測試類的運行時間,在所有測試執行前保存其起始時間

  • 計算每個測試方法的運行時間,在每個測試方法執行前保存其起始時間

  • 在每個測試方法執行完畢后,獲取其結束時間,計算并輸出該測試方法的運行時間

  • 在所有測試類執行完畢后,獲取其結束時間,計算并輸出所有測試的運行時間

  • 以上操作,僅對所有注解了 @BenchMark 的測試類或測試方法生效

最后一點需求可能不是一眼便能發現。如果一個方法并未注解 @Benchmark 注解,它有什么可能被我們的擴展處理?  一個語法上的原因是,如果一個擴展被應用到了一個類上,那么它默認也會應用到類中的所有方法上。因此,如果我們的需求是計算整個測試類的運行時間,但不需具體到類中每個單獨方法的運行時間時,類中的測試方法就必須被手動排除。這點我們可以通過單獨檢查每個方法是否應用了注解來做到。

有趣的是,需求的前四點與擴展點中的其中四個是一一對應的:BeforeAll、BeforeTestExecution、AfterTestExecution  與 AfterAll。因此我們要做的任務便是實現這四個對應的接口。具體實現很簡單,把上面說的翻譯成代碼即是:

public class BenchmarkExtension implements         BeforeAllExtensionPoint, BeforeTestExecutionCallback,         AfterTestExecutionCallback, AfterAllExtensionPoint {       private static final Namespace NAMESPACE =             Namespace.of("BenchmarkExtension");       @Override     public void beforeAll(ContainerExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           writeCurrentTime(context, LaunchTimeKey.CLASS);     }       @Override     public void beforeTestExecution(TestExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           writeCurrentTime(context, LaunchTimeKey.TEST);     }       @Override     public void afterTestExecution(TestExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           long launchTime = loadLaunchTime(context, LaunchTimeKey.TEST);         long runtime = currentTimeMillis() - launchTime;         print("Test", context.getDisplayName(), runtime);     }       @Override     public void afterAll(ContainerExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           long launchTime = loadLaunchTime(context, LaunchTimeKey.CLASS);         long runtime = currentTimeMillis() - launchTime;         print("Test container", context.getDisplayName(), runtime);     }       private static boolean shouldBeBenchmarked(ExtensionContext context) {         return context.getElement()                 .map(el -> el.isAnnotationPresent(Benchmark.class))                 .orElse(false);     }       private static void writeCurrentTime(             ExtensionContext context, LaunchTimeKey key) {         context.getStore(NAMESPACE).put(key, currentTimeMillis());     }       private static long loadLaunchTime(             ExtensionContext context, LaunchTimeKey key) {         return (Long) context.getStore(NAMESPACE).remove(key);     }       private static void print(             String unit, String displayName, long runtime) {         System.out.printf("%s '%s' took %d ms.%n", unit, displayName, runtime);     }       private enum LaunchTimeKey {         CLASS, TEST     } }  「譯者:啊這代碼讓人心曠神怡。」

上面代碼有幾個地方值得留意。首先是 shouldBeBenchmarked 方法,它使用了 JUnit 的 API  來獲取當前元素是否(被元)注解了@Benchmark 注解;其次, writeCurrentTime / loadLaunchTime 方法中使用了 Junit  提供的 store 以寫入和讀取運行時間。

關于“如何分析JUnit  5中的Extension Model擴展模型”就介紹到這了,更多相關內容可以搜索億速云以前的文章,希望能夠幫助大家答疑解惑,請多多支持億速云網站!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

镶黄旗| 绍兴市| 广德县| 江永县| 进贤县| 巴楚县| 桂平市| 轮台县| 时尚| 绥滨县| 曲水县| 吉林省| 南通市| 固镇县| 长泰县| 平邑县| 潜江市| 海安县| 哈巴河县| 余庆县| 鄄城县| 错那县| 道孚县| 龙游县| 彩票| 丰都县| 信丰县| 龙胜| 绥阳县| 江源县| 柞水县| 鞍山市| 平乐县| 沙田区| 包头市| 浦县| 措勤县| 鱼台县| 镇原县| 通化县| 武夷山市|