真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JUnit5中擴(kuò)展模型的深入理解

什么是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 5是一個模塊化和可擴(kuò)展的測試框架,支持Java 8及更高版本。
  • JUnit 5由三個部分組成——一個基礎(chǔ)平臺、一個新的編程和擴(kuò)展模型Jupiter,以及一個名為Vintage的向后兼容的測試引擎。
  • JUnit 5 Jupiter的擴(kuò)展模型可用于向JUnit中添加自定義功能。
  • 擴(kuò)展模型API測試生命周期提供了鉤子和注入自定義參數(shù)的方法(即依賴注入)。

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í)行故事和行為:

  • 用于裝飾測試類和測試方法的注解
  • JUnit 5 Jupiter的生命周期回調(diào)
  • 動態(tài)參數(shù)解析

注解

示例中的“@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ò)展:

JUnit 5中擴(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。我們將這個類命名為“StoryDe​​tails”。以下代碼片段演示了創(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)建的“StoryDe​​tails”實例保存到這個存儲中。

自定義擴(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í)行上下文的存儲中獲取StoryDe​​tails實例,然后從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)的支持。


網(wǎng)頁標(biāo)題:JUnit5中擴(kuò)展模型的深入理解
文章源于:http://weahome.cn/article/jjcoej.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部