什么是Junit5 ?
創(chuàng)新互聯(lián)建站專注于新城企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,商城網(wǎng)站制作。新城網(wǎng)站建設(shè)公司,為新城等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站制作,專業(yè)設(shè)計,全程項目跟蹤,創(chuàng)新互聯(lián)建站專業(yè)和態(tài)度為您提供的服務(wù)
先看來個公式:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
這看上去比Junit4 復(fù)雜,實際上在導(dǎo)入包時也會復(fù)雜一些。
JUnit Platform是在JVM上啟動測試框架的基礎(chǔ)。
JUnit Jupiter是JUnit5擴(kuò)展的新的編程模型和擴(kuò)展模型,用來編寫測試用例。Jupiter子項目為在平臺上運(yùn)行Jupiter的測試提供了一個TestEngine (測試引擎)。
JUnit Vintage提供了一個在平臺上運(yùn)行JUnit 3和JUnit 4的TestEngine 。
關(guān)鍵要點
JUnit是最受歡迎的基于JVM的測試框架,在第5個主要版本中進(jìn)行了徹底的改造。JUnit 5提供了豐富的功能——從改進(jìn)的注解、標(biāo)簽和過濾器到條件執(zhí)行和對斷言消息的惰性求值。這讓基于TDD編寫單元測試變得輕而易舉。新框架還帶來了一個強(qiáng)大的擴(kuò)展模型。擴(kuò)展開發(fā)人員可以使用這個新模型向JUnit 5中添加自定義功能。本文將指導(dǎo)你完成自定義擴(kuò)展的設(shè)計和實現(xiàn)。這種自定義擴(kuò)展機(jī)制為Java程序員提供了一種創(chuàng)建和執(zhí)行故事和行為(即BDD規(guī)范測試)的方法。
我們首先使用JUnit 5和我們的自定義擴(kuò)展(稱為“StoryExtension”)來編寫一個示例故事和行為(測試方法)。這個示例使用了兩個新的自定義注解“@Story”和“@Scenario”,以及“Scene”類,用以支持我們的自定義StoryExtension:
import org.junit.jupiter.api.extension.ExtendWith; import ud.junit.bdd.ext.Scenario; import ud.junit.bdd.ext.Scene; import ud.junit.bdd.ext.Story; import ud.junit.bdd.ext.StoryExtension; @ExtendWith(StoryExtension.class) @Story(name=“Returns go back to the stockpile”, description=“...“) public class StoreFrontTest { @Scenario(“Refunded items should be returned to the stockpile”) public void refundedItemsShouldBeRestocked(Scene scene) { scene .given(“customer bought a blue sweater”, () -> buySweater(scene, “blue”)) .and(“I have three blue sweaters in stock”, () -> assertEquals(3, sweaterCount(scene, “blue”), “Store should carry 3 blue sweaters”)) .when(“the customer returns the blue sweater for a refund”, () -> refund(scene, 1, “blue”)) .then(“I should have four blue sweaters in stock”, () -> assertEquals(4, sweaterCount(scene, “blue”), “Store should carry 4 blue sweaters”)) .run(); } }
從代碼片段中我們可以看到,Jupiter的擴(kuò)展模型非常強(qiáng)大。我們還可以看到,我們的自定義擴(kuò)展及其相應(yīng)的注解為測試用例編寫者提供了簡單而干凈的方法來編寫B(tài)DD規(guī)范。
作為額外的獎勵,當(dāng)使用我們的自定義擴(kuò)展程序執(zhí)行測試時,會生成如下所示的文本報告:
STORY: Returns go back to the stockpile
As a store owner, in order to keep track of stock, I want to add items back to stock when they're returned.
SCENARIO: Refunded items should be returned to stock
GIVEN that a customer previously bought a blue sweater from me
AND I have three blue sweaters in stock
WHEN the customer returns the blue sweater for a refund
THEN I should have four blue sweaters in stock
這些報告可以作為應(yīng)用程序功能集的文檔。
自定義擴(kuò)展StoryExtension能夠借助以下核心概念來支持和執(zhí)行故事和行為:
注解
示例中的“@ExtendWith”注解是由Jupiter提供的標(biāo)記接口。這是在測試類或方法上注冊自定義擴(kuò)展的方法,目的是讓Jupiter測試引擎調(diào)用給定類或方法的自定義擴(kuò)展?;蛘撸瑴y試用例編寫者可以通過編程的方式注冊自定義擴(kuò)展,或者通過服務(wù)加載器機(jī)制進(jìn)行自動注冊。
我們的自定義擴(kuò)展需要一種識別故事的方法。為此,我們定義了一個名為“Story”的自定義注解類,如下所示:
import org.junit.platform.commons.annotation.Testable; @Testable public @interface Story {...}
測試用例編寫者應(yīng)該使用這個自定義注解將測試類標(biāo)記為故事。請注意,這個注解本身使用了JUnit 5內(nèi)置的“@Testable”注解。這個注解為IDE和其他工具提供了一種識別可測試的類和方法的方式——也就是說,帶有這個注解的類或方法可以通過JUnit 5 Jupiter測試引擎來執(zhí)行。
我們的自定義擴(kuò)展還需要一種方法來識別故事中的行為或場景。為此,我們定義一個名為“Scenario”的自定義注解類,看起來像這樣:
import org.junit.jupiter.api.Test; @Test public @interface Scenario {...}
測試用例編寫者應(yīng)使用這個自定義注解將測試方法標(biāo)記為場景。這個注解本身使用了JUnit 5 Jupiter的內(nèi)置“@Test”注解。當(dāng)IDE和測試引擎掃描給定的一組測試類并在公共實例方法上找到@Scenario注解時,就會將這些方法標(biāo)記為可執(zhí)行的測試方法。
請注意,與JUnit 4的@Test注解不同,Jupiter的@Test注解不支持可選的“預(yù)期”異常和“超時”參數(shù)。Jupiter的@Test注解是從頭開始設(shè)計的,并考慮到了可擴(kuò)展性。
生命周期
JUnit 5 Jupiter提供了擴(kuò)展回調(diào),可用于訪問測試生命周期事件。擴(kuò)展模型提供了幾個接口,用于在測試執(zhí)行生命周期的各個時間點對測試進(jìn)行擴(kuò)展:
擴(kuò)展開發(fā)者可以自由地實現(xiàn)所有或部分生命周期接口。
“BeforeAllCallback”接口提供了一種方法用于初始化擴(kuò)展并在調(diào)用JUnit測試容器中的測試用例之前添加自定義邏輯。我們的StoryExtension類將實現(xiàn)這個接口,以確保給定的測試類使用了“@Story”注解。
import org.junit.jupiter.api.extension.BeforeAllCallback; public class StoryExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { if (!AnnotationSupport .isAnnotated(context.getRequiredTestClass(), Story.class)) { throw new Exception(“Use @Story annotation...“); } } }
Jupiter引擎將提供一個用于運(yùn)行擴(kuò)展的執(zhí)行上下文。我們使用這個上下文來確定正在執(zhí)行的測試類是否使用了“@Story”注解。我們使用JUnit平臺提供的AnnotationSupport輔助類來檢查是否存在這個注解。
回想一下,我們的自定義擴(kuò)展在執(zhí)行測試后會生成BDD報告。這些報告的某些部分是從“@Store”注解的元素中提取的。我們使用beforeAll回調(diào)來保存這些字符串。稍后,在執(zhí)行生命周期結(jié)束時,再基于這些字符串生成報告。我們使用了一個簡單的POJO。我們將這個類命名為“StoryDetails”。以下代碼片段演示了創(chuàng)建這個類實例的過程,并將注解元素保存到實例中:
public class StoryExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { Class<?> clazz = context.getRequiredTestClass(); Story story = clazz.getAnnotation(Story.class); StoryDetails storyDetails = new StoryDetails() .setName(story.name()) .setDescription(story.description()) .setClassName(clazz.getName()); context.getStore(NAMESPACE).put(clazz.getName(), storyDetails); } }
我們需要解釋一下方法的最后一個語句。我們實際上是從執(zhí)行上下文中獲取一個帶有名字的存儲,并將新創(chuàng)建的“StoryDetails”實例保存到這個存儲中。
自定義擴(kuò)展可以使用存儲來保存和獲取任意數(shù)據(jù)——基本上就是一個存在于內(nèi)存中的map。為了避免多個擴(kuò)展之間出現(xiàn)意外的key沖突,JUnit引入了命名空間的概念。命名空間是一種對不同擴(kuò)展保存的數(shù)據(jù)進(jìn)行隔離的方法。用于隔離擴(kuò)展數(shù)據(jù)的一種常用方法是使用自定義擴(kuò)展類名:
private static final Namespace NAMESPACE = Namespace .create(StoryExtension.class);
我們的擴(kuò)展需要用到的另一個自定義注解是“@Scenario”注解。這個注解用于將測試方法標(biāo)記為故事中的場景或行為。我們的擴(kuò)展將解析這些場景,以便將它們作為JUnit測試用例來執(zhí)行并生成報告?;叵胍幌挛覀冎翱吹降纳芷趫D中的“BeforeEachCallback”接口,在調(diào)用每個測試方法之前,我們將使用回調(diào)來添加附加邏輯:
import org.junit.jupiter.api.extension.BeforeEachCallback; public class StoryExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) throws Exception { if (!AnnotationSupport. isAnnotated(context.getRequiredTestMethod(), Scenario.class)) { throw new Exception(“Use @Scenario annotation...“); } } }
如前所述,Jupiter引擎將提供一個用于運(yùn)行擴(kuò)展的執(zhí)行上下文。我們使用上下文來確定正在執(zhí)行的測試方法是否使用了“@Scenario”注解。
回到本文的開頭,我們提供了一個故事的示例代碼,我們的自定義擴(kuò)展負(fù)責(zé)將“Scene”類的實例注入到每個測試方法中。Scene類讓測試用例編寫者能夠使用“given”、“then”和“when”等步驟來定義場景(行為)。Scene類是我們自定義擴(kuò)展的中心單元,它包含了特定于測試方法的狀態(tài)信息。狀態(tài)信息可以在場景的各個步驟之間傳遞。我們使用“BeforeEachCallback”接口在調(diào)用測試方法之前準(zhǔn)備一個Scene實例:如前所述,Jupiter引擎將提供一個用于運(yùn)行擴(kuò)展執(zhí)行上下文。我們使用上下文來確定正在執(zhí)行的測試方法是否使用了“@Scenario”注解。
public class StoryExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) throws Exception { Scene scene = new Scene() .setDescription(getValue(context, Scenario.class)); Class<?> clazz = context.getRequiredTestClass(); StoryDetails details = context.getStore(NAMESPACE) .get(clazz.getName(), StoryDetails.class); details.put(scene.getMethodName(), scene); } }
上面的代碼與我們在“BeforeAllCallback”接口方法中所做的非常相似。
動態(tài)參數(shù)解析
現(xiàn)在我們還缺少一個東西,即如何將場景實例注入到測試方法中。Jupiter的擴(kuò)展模型為我們提供了一個“ParameterResolver”接口。這個接口為測試引擎提供了一種方法,用于識別希望在測試執(zhí)行期間動態(tài)注入?yún)?shù)的擴(kuò)展。我們需要實現(xiàn)這個接口的兩個方法,以便注入我們的場景實例:
import org.junit.jupiter.api.extension.ParameterResolver; public class StoryExtension implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Parameter parameter = parameterContext.getParameter(); return Scene.class.equals(parameter.getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Class<?> clazz = extensionContext.getRequiredTestClass(); StoryDetails details = extensionContext.getStore(NAMESPACE) .get(clazz.getName(), StoryDetails.class); return details.get(extensionContext .getRequiredTestMethod().getName()); } }
上面的第一個方法告訴Jupiter我們的自定義擴(kuò)展是否可以注入測試方法所需的參數(shù)。
在第二個方法“resolveParameter()”中,我們從執(zhí)行上下文的存儲中獲取StoryDetails實例,然后從StoryDetails實例中獲取先前為給定測試方法創(chuàng)建的場景實例,并將其傳給測試引擎。測試引擎將這個場景實例注入到測試方法中并執(zhí)行測試。請注意,僅當(dāng)“supportsParameter()”方法返回true值時才會調(diào)用“resolveParameter()”方法。
最后,為了在執(zhí)行完所有故事和場景后生成報告,自定義擴(kuò)展實現(xiàn)了“AfterAllCallback”接口:
import org.junit.jupiter.api.extension.AfterAllCallback; public class StoryExtension implements AfterAllCallback { @Override public void afterAll(ExtensionContext context) throws Exception { new StoryWriter(getStoryDetails(context)).write(); } }
“StoryWriter”是一個自定義類,可生成報告并將其保存到JSON或文本文件中。
現(xiàn)在,讓我們看看如何使用這個自定義擴(kuò)展來編寫B(tài)DD風(fēng)格的測試用例。Gradle 4.6及更高版本支持使用JUnit 5運(yùn)行單元測試。你可以使用build.gradle文件來配置JUnit 5。
dependencies { testCompile group: “ud.junit.bdd”, name: “bdd-junit”, version: “0.0.1-SNAPSHOT” testCompile group: “org.junit.jupiter”, name: “junit-jupiter-api”, version: “5.2.0" testRuntime group: “org.junit.jupiter”, name: “junit-jupiter-engine”, version: “5.2.0” } test { useJUnitPlatform() }
如你所見,我們通過“useJUnitPlatform()”方法要求gradle使用JUnit 5。然后我們就可以使用StoryExtension類來編寫測試用例。這是本文開頭給出的示例:
import org.junit.jupiter.api.extension.ExtendWith; import ud.junit.bdd.ext.Scenario; import ud.junit.bdd.ext.Story; import ud.junit.bdd.ext.StoryExtension; @ExtendWith(StoryExtension.class) @Story(name=“Returns go back to the stockpile”, description=“...“) public class StoreFrontTest { @Scenario(“Refunded items should be returned to the stockpile”) public void refundedItemsShouldBeRestocked(Scene scene) { scene .given(“customer bought a blue sweater”, () -> buySweater(scene, “blue”)) .and(“I have three blue sweaters in stock”, () -> assertEquals(3, sweaterCount(scene, “blue”), “Store should carry 3 blue sweaters”)) .when(“the customer returns the blue sweater for a refund”, () -> refund(scene, 1, “blue”)) .then(“I should have four blue sweaters in stock”, () -> assertEquals(4, sweaterCount(scene, “blue”), “Store should carry 4 blue sweaters”)) .run(); } }
我們可以通過“gradle testClasses”來運(yùn)行測試,或者使用其他支持JUnit 5的IDE。除了常規(guī)的測試報告外,自定義擴(kuò)展還為所有測試類生成BDD文檔。
結(jié)論
我們描述了JUnit 5擴(kuò)展模型以及如何利用它來創(chuàng)建自定義擴(kuò)展。我們設(shè)計并實現(xiàn)了一個自定義擴(kuò)展,測試用例編寫者可以使用它來創(chuàng)建和執(zhí)行故事。讀者可以從GitHub上獲取代碼,并研究如何使用Jupiter擴(kuò)展模型及其API來實現(xiàn)自定義擴(kuò)展。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。