JUnit 5 –动态测试
在定義測試時,JUnit 4有一個很大的弱點:它必須在編譯時發生。 現在,JUnit 5將解決此問題! Milestone 1 剛剛發布 ,并帶有全新的動態測試,該動態測試允許在運行時創建測試。
總覽
本系列中有關JUnit 5的其他文章:
- 設定
- 基本
- 建筑
- 擴展模型
- 條件
- 注射
- 動態測試
- …
本系列基于預發行版本Milestone 1 ,當然可以隨時更改。 發布新的里程碑或一般可用性版本時,帖子將更新。
您將在此處閱讀的大部分內容和更多內容都可以在新興的JUnit 5用戶指南中找到 (該鏈接已鏈接到Milestone 1版本–您可以在此處找到最新版本)。 我在這里顯示的代碼示例可以在GitHub上找到 。
靜態測試
JUnit 3通過解析方法名稱并檢查它們是否以測試開頭來識別測試。 JUnit 4利用了(后來的)注解,并引入了@Test,這給了我們更多的自由。 這兩種技術共享相同的方法:測試是在編譯時定義的。
但是,事實證明這是非常有限的。 例如,考慮一種常見情況,即應該對多種輸入數據執行相同的測試,在這種情況下,應針對許多不同點:
void testDistanceComputation(Point p1, Point p2, double distance) {assertEquals(distance, p1.distanceTo(p2)); }我們有什么選擇? 最直接的方法是創建許多有趣的點,然后在循環中調用我們的測試方法:
@Test void testDistanceComputations() {List<PointPointDistance> testData = createTestData();for (PointPointDistance datum : testData) {testDistanceComputation(datum.point1(), datum.point2(), datum.distance());} }但是,如果這樣做,JUnit會將循環視為單個測試。 這意味著僅在第一個失敗之前執行測試,報告將受到影響,并且工具支持通常低于標準。
有幾個JUnit 4功能和擴展可解決此問題。 它們或多或少都可以工作,但通常僅限于特定的用例( Theories ), 難以使用 ( Parameterized ),并且通常需要運行程序(值得稱贊的JUnitParams )。 原因是它們都受到相同的限制:JUnit 4確實不支持在運行時創建測試。
使用lambda創建測試也是如此。 有些人想定義這樣的測試:
class PointTest {"Distance To Origin" -> {Point origin = Point.create(0,0);Point p = Point.create(3,4);assertEquals(5, origin.distanceTo(p));}}當然,這只是一個理想選擇-它甚至無法在Java中進行編譯。 盡管如此,看到我們能達到多近還是很有趣的。 las,也無法靜態標識各個lambda,因此此處也有相同的限制。
但是,如果JUnit 5沒有提出解決方案,我不會寫所有這些內容:進行動態測試以解決問題!
發布時間由NASA戈達德太空飛行中心在CC-BY-SA 2.0
動態測試
從最近開始,JUnit 5代碼庫就采用了一種新類型和一種新注釋,它們共同解決了我們的問題。
首先,有DynamicTest ,它是測試的簡單包裝。 它有一個名稱,并保存構成測試主體的代碼。 后者以Executable的形式發生,就像Runnable但是可以拋出任何Throwable (可格式化的命名)。 它是使用靜態工廠方法創建的:
public static DynamicTest dynamicTest(String name, Executable test);然后是@TestFactory ,可以注釋方法。 這些方法必須返回動態測試的Iterator , Iterable或Stream 。 (這當然不能在編譯時強制執行,因此,如果我們返回其他內容,JUnit將在運行時發出barf。)
很容易看出他們是如何合作的:
因此,我們能夠在運行時動態創建測試:
@TestFactory List<DynamicTest> createPointTests() {return Arrays.asList(DynamicTest.dynamicTest("A Great Test For Point",() -> {// test code}),DynamicTest.dynamicTest("Another Great Test For Point",() -> {// test code})); }讓我們看看如何使用它來解決我們上面描述的問題。
要創建參數化測試,我們執行與之前非常相似的操作:
@TestFactory Stream<DynamicTest> testDistanceComputations() {List<PointPointDistance> testData = createTestData();return testData.stream().map(datum -> DynamicTest.dynamicTest("Testing " + datum,() -> testDistanceComputation(datum.point1(), datum.point2(), datum.distance()))); }與上面所做的操作的關鍵區別在于,我們不再直接執行testDistanceComputation 。 取而代之的是,我們為每個數據創建一個動態測試,這意味著JUnit將知道這些測試很多,而不僅僅是一個。
在這種情況下,我們可能會使用其他方法來生成動態測試:
@TestFactory Stream<DynamicTest> testDistanceComputations() {return DynamicTest.stream(createTestData().iterator(),datum -> "Testing " + datum,datum -> testDistanceComputation(datum.point1(), datum.point2(), datum.distance())); }在這里,我們將測試數據stream ,然后告訴它如何從中創建名稱和測試。
所以你怎么看? 也許符合“ JUnit 5將這些作為單獨的測試來對待,但是從語法上來看仍然很麻煩”的思路嗎? 好吧,至少我是這樣認為的。 該功能很好,但有點笨拙。
但這只是里程碑1,因此有足夠的時間進行改進。 也許擴展可以提供一種更舒適的方式來創建動態測試,但是我不太清楚如何。 我想, 新的擴展點會有所幫助。
Lambda測試
好的,讓我們看看我們距離備受期待的lambda測試有多近。 現在,沒有為此明確創建動態測試,因此我們必須進行一些修改。 (這種修補是錯誤的,受Jens Schauder 關于JUnit 5的演示之一“啟發”。謝謝Jens!)
動態測試需要一個名稱和一個可執行文件,用lambda創建后者聽起來很合理。 為了能夠做到這一點,我們需要一個目標,即lambda被分配給的目標。 想到一個方法參數...
但是該方法會做什么? 顯然,它應該創建一個動態測試,然后呢? 也許我們可以將該測試轉儲到某個地方,然后讓JUnit進行測試?
public class LambdaTest {private final List<DynamicTest> tests = new ArrayList<>();// use lambda to create the 'Executable'public void registerTest(String name, Executable test) {tests.add(DynamicTest.dynamicTest(name, test));}@TestFactoryvoid List<DynamicTest> tests() {return tests;}}好的,這看起來很有希望。 但是,我們從哪里獲得LambdaTest的實例? 對于我們的測試類,最簡單的解決方案是簡單地對其進行擴展,然后重復調用registerTest 。 如果這樣做的話,我們可能更愿意使用一個較短的名稱。 我們還可以使其受到保護:
// don't do this at home! protected void λ(String name, Executable test) {tests.add(DynamicTest.dynamicTest(name, test)); }看來我們要到達那里。 剩下的就是調用λ ,并且唯一明顯的方法是從測試類的構造函數內部進行:
class PointTest extends LambdaTest {public PointTest() {λ("A Great Test For Point", () -> {// test code})}}我們已經完成修補工作。 為了進一步發展,我們必須開始黑客攻擊。 有沒有聽說過雙括號初始化 ? 這是一個有點奇怪的功能,它創建一個匿名子類并在新類的構造函數中執行給定的代碼。 有了它,我們可以走得更遠:
class PointTest extends LambdaTest {{λ("A Great Test For Point", () -> {// test code});}}如果我們真的很渴望,我們可以刪除另外兩個符號。 有了這個怪異的技巧 (我們現在受到Benji Weber的啟發),我們可以通過反射來確定lambda的參數名稱,并將其用作測試的名稱。 要利用這一點,我們需要一個新的接口,并且必須稍微更改LambdaTest ::λ:
@FunctionalInterface // the interface we are extending here allows us // to retrieve the parameter name via 'prettyName()' // (the black magic is hidden inside that method; // look at 'MethodFinder' and 'NamedValue' in Benji's post) public interface NamedTest extends ParameterNameFinder {void execute(String name); }protected void λ(NamedTest namedTest) {String name = namedTest.prettyName();Executable test = () -> namedTest.execute(name);tests.add(DynamicTest.dynamicTest(name, test)); }綜上所述,我們可以創建如下測試:
class PointTest extends LambdaTest {{λ(A_Great_Test_For_Point -> {// test code});}}你怎么看? 所有這些黑客值得嗎? 老實說,我不介意讓我的IDE生成測試方法樣板,所以我的回答是“否”。 但這是一個有趣的實驗。 :)
生命周期
動態測試的當前實現是故意的。 這種顯示方式之一是它們沒有集成到生命周期中。 從用戶指南中:
這意味著對于動態測試,不會執行@BeforeEach和@AfterEach方法及其對應的擴展回調。 換句話說,如果您在lambda表達式中訪問來自測試實例的字段以進行動態測試,則這些字段將不會由回調方法或由同一@TestFactory方法生成的動態測試在執行之間的擴展名進行重置。
不過,已經有一個問題可以解決 。
反射
那我們看到了什么? 到目前為止,JUnit只知道在編譯時聲明的測試。 JUnit 5具有動態測試的概念,動態測試是在運行時創建的,由名稱和保存測試代碼的可執行文件組成。 到此為止,我們已經看到了如何創建參數化測試以及如何使用lambda來以更現代的風格定義測試。
你怎么看? 渴望嘗試嗎?
翻譯自: https://www.javacodegeeks.com/2016/07/junit-5-dynamic-tests.html
總結
以上是生活随笔為你收集整理的JUnit 5 –动态测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring本地化默认英文_Spring
- 下一篇: 梅西动态电脑壁纸高清壁纸高清壁纸高清(梅