最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

使用 Spring Boot 進行單元測試

2021-10-26 12:13 作者:信碼由韁  | 我要投稿

【注】本文譯自: Unit Testing with Spring Boot - Reflectoring

編寫好的單元測試可以被認(rèn)為是一門難以掌握的藝術(shù)。但好消息是支持它的機制很容易學(xué)習(xí)。

本教程為您提供了這些機制,并詳細(xì)介紹了編寫良好的單元測試所必需的技術(shù)細(xì)節(jié),重點是 Spring Boot 應(yīng)用程序。

我們將看看如何以可測試的方式創(chuàng)建 Spring bean,然后討論 Mockito 和 AssertJ 的用法,這兩個庫默認(rèn)包含在 Spring Boot 中用于測試。

請注意,本文僅討論單元測試。集成測試、Web 層測試和持久層測試將在本系列的后續(xù)文章中討論。

? 代碼示例

本文附有?GitHub 上?的工作代碼示例。

依賴關(guān)系

對于本教程中的單元測試,我們將使用 JUnit Jupiter (JUnit 5)、Mockito 和 AssertJ。我們還將包括 Lombok 以減少一些樣板代碼:

dependencies {

? ? compileOnly('org.projectlombok:lombok')

? ? testCompile('org.springframework.boot:spring-boot-starter-test')

? ? testCompile('org.junit.jupiter:junit-jupiter:5.4.0')

? ? testCompile('org.mockito:mockito-junit-jupiter:2.23.0')

}

Mockito 和 AssertJ 是使用?spring-boot-starter-test 依賴項自動導(dǎo)入的,但我們必須自己包含 Lombok。

不要在單元測試中使用 Spring

如果你以前用 Spring 或 Spring Boot 寫過測試,你可能會說我們不需要 Spring 來寫單元測試。這是為什么?

考慮以下測試?RegisterUseCase 類的單個方法的“單元”測試:

@ExtendWith(SpringExtension.class)

@SpringBootTest

class RegisterUseCaseTest {

? ? @Autowired

? ? private RegisterUseCase registerUseCase;

? ? @Test

? ? void savedUserHasRegistrationDate() {

? ? ? ? User user = new User("zaphod", "zaphod@mail.com");

? ? ? ? User savedUser = registerUseCase.registerUser(user);

? ? ? ? assertThat(savedUser.getRegistrationDate()).isNotNull();

? ? }

}

這個測試在我電腦上的一個空 Spring 項目上運行大約需要 4.5 秒。

但是一個好的單元測試只需要幾毫秒。否則它會阻礙由測試驅(qū)動開發(fā)(TDD)思想推動的“測試/代碼/測試”流程。但即使我們不采用 TDD,等待太長時間的測試也會破壞我們的注意力。

執(zhí)行上面的測試方法實際上只需要幾毫秒。 剩下的 4.5 秒是由于?@SpringBootRun 告訴 Spring Boot 設(shè)置整個 Spring Boot 應(yīng)用程序上下文。

所以我們啟動了整個應(yīng)用程序只是為了將?RegisterUseCase 實例自動裝配到我們的測試中。一旦應(yīng)用程序變大并且 Spring 不得不將越來越多的 bean 加載到應(yīng)用程序上下文中,它將花費更長的時間。

那么,為什么我們不應(yīng)該在單元測試中使用 Spring Boot 呢?老實說,本教程的大部分內(nèi)容都是關(guān)于在沒有 Spring Boot 的情況下編寫單元測試。

創(chuàng)建可測試的 Spring Bean

然而,我們可以做一些事情來提高 Spring bean 的可測試性。

字段注入是不可取的

讓我們從一個不好的例子開始??紤]以下類:

@Service

public class RegisterUseCase {

? ? @Autowired

? ? private UserRepository userRepository;

? ? public User registerUser(User user) {

? ? ? ? return userRepository.save(user);

? ? }

}

這個類不能在沒有 Spring 的情況下進行單元測試,因為它沒有提供傳遞?UserRepository 實例的方法。那么,我們需要按照上一節(jié)中討論的方式編寫測試,讓 Spring 創(chuàng)建一個?UserRepository 實例并將其注入到用?@Autowired 注解的字段中。

這里的教訓(xùn)是不要使用字段注入。

提供構(gòu)造函數(shù)

實際上,我們根本不要使用?@Autowired 注解:

@Service

public class RegisterUseCase {

? ? private final UserRepository userRepository;

? ? public RegisterUseCase(UserRepository userRepository) {

? ? ? ? this.userRepository = userRepository;

? ? }

? ? public User registerUser(User user) {

? ? ? ? return userRepository.save(user);

? ? }

}

這個版本通過提供允許傳入?UserRepository 實例的構(gòu)造函數(shù)來允許構(gòu)造函數(shù)注入。在單元測試中,我們現(xiàn)在可以創(chuàng)建這樣一個實例(可能是我們稍后討論的模擬實例)并將其傳遞給構(gòu)造函數(shù)。

在創(chuàng)建生產(chǎn)應(yīng)用程序上下文時,Spring 將自動使用此構(gòu)造函數(shù)來實例化?RegisterUseCase 對象。注意,在 Spring 5 之前,我們需要在構(gòu)造函數(shù)中添加?@Autowired 注解,以便 Spring 找到構(gòu)造函數(shù)。

還要注意?UserRepository 字段現(xiàn)在是 final。這是有道理的,因為字段內(nèi)容在應(yīng)用程序的生命周期內(nèi)永遠不會改變。它還有助于避免編程錯誤,因為如果我們忘記初始化字段,編譯器會報錯。

減少樣板代碼

使用 Lombok 的? @RequiredArgsConstructor?注解,我們可以讓構(gòu)造函數(shù)自動生成:

@Service

@RequiredArgsConstructor

public class RegisterUseCase {

? ? private final UserRepository userRepository;

? ? public User registerUser(User user) {

? ? ? ? user.setRegistrationDate(LocalDateTime.now());

? ? ? ? return userRepository.save(user);

? ? }

}

現(xiàn)在,我們有一個非常簡潔的類,沒有樣板代碼,可以在普通的 java 測試用例中輕松實例化:

class RegisterUseCaseTest {

? ? private UserRepository userRepository = ...;

? ? private RegisterUseCase registerUseCase;

? ? @BeforeEach

? ? void initUseCase() {

? ? ? ? registerUseCase = new RegisterUseCase(userRepository);

? ? }

? ? @Test

? ? void savedUserHasRegistrationDate() {

? ? ? ? User user = new User("zaphod", "zaphod@mail.com");

? ? ? ? User savedUser = registerUseCase.registerUser(user);

? ? ? ? assertThat(savedUser.getRegistrationDate()).isNotNull();

? ? }

}

然而,還缺少一點,那就是如何模擬我們被測類所依賴的?UserRepository 實例,因為我們不想依賴真實的東西,它可能需要連接到數(shù)據(jù)庫。

使用 Mockito 來模擬依賴

現(xiàn)在事實上的標(biāo)準(zhǔn)模擬庫是?Mockito。它至少提供了兩種方法來創(chuàng)建模擬的?UserRepository 以填補前面代碼示例中的空白。

使用普通 Mockito 模擬依賴項

第一種方法是以編程方式使用 Mockito:

private UserRepository userRepository = Mockito.mock(UserRepository.class);

這將創(chuàng)建一個從外部看起來像?UserRepository 的對象。默認(rèn)情況下,當(dāng)一個方法被調(diào)用時它什么都不做,如果該方法有返回值則返回 null。

我們的測試現(xiàn)在將在?assertThat(savedUser.getRegistrationDate()).isNotNull() 處以?NullPointerException 失敗,因為?userRepository.save(user) 現(xiàn)在返回 null。

所以,我們必須告訴 Mockito 在調(diào)用?userRepository.save() 時返回一些東西。我們使用靜態(tài)?when 方法來做到這一點:

? ? @Test

? ? void savedUserHasRegistrationDate() {

? ? ? ? User user = new User("zaphod", "zaphod@mail.com");

? ? ? ? when(userRepository.save(any(User.class))).then(returnsFirstArg());

? ? ? ? User savedUser = registerUseCase.registerUser(user);

? ? ? ? assertThat(savedUser.getRegistrationDate()).isNotNull();

? ? }

這將使?userRepository.save() 返回傳遞給方法的相同用戶對象。

Mockito 具有更多功能,可以進行模擬、匹配參數(shù)和驗證方法調(diào)用。有關(guān)更多信息,請查看參考文檔。

使用 Mockito 的?@Mock 注解模擬依賴項

創(chuàng)建模擬對象的另一種方法是 Mockito 的?@Mock 注解與 JUnit Jupiter 的?MockitoExtension 相結(jié)合:

@ExtendWith(MockitoExtension.class)

class RegisterUseCaseTest {

? ? @Mock

? ? private UserRepository userRepository;

? ? private RegisterUseCase registerUseCase;

? ? @BeforeEach

? ? void initUseCase() {

? ? ? ? registerUseCase = new RegisterUseCase(userRepository);

? ? }

? ? @Test

? ? void savedUserHasRegistrationDate() {

? ? ? ? // ...

? ? }

}

@Mock?注解指定了 Mockito 應(yīng)該注入模擬對象的字段。?@MockitoExtension 告訴 Mockito 評估那些?@Mock 注解,因為 JUnit 不會自動執(zhí)行此操作。

結(jié)果和手動調(diào)用?Mockito.mock()?一樣,選擇使用哪種方式是品味問題。 但是請注意,通過使用?MockitoExtension?將我們的測試綁定到測試框架。

請注意,我們也可以在?registerUseCase?字段上使用?@InjectMocks 注解,而不是手動構(gòu)造?RegisterUseCase 對象。然后 Mockito 會按照指定的算法為我們創(chuàng)建一個實例:

@ExtendWith(MockitoExtension.class)

class RegisterUseCaseTest {

? ? @Mock

? ? private UserRepository userRepository;

? ? @InjectMocks

? ? private RegisterUseCase registerUseCase;

? ? @Test

? ? void savedUserHasRegistrationDate() {

? ? ? ? // ...

? ? }

}

使用 AssertJ 創(chuàng)建可讀斷言

Spring Boot 測試支持自動附帶的另一個庫是?AssertJ。我們已經(jīng)在上面使用它來實現(xiàn)我們的斷言:

assertThat(savedUser.getRegistrationDate()).isNotNull();

然而,讓斷言更具可讀性不是更好嗎?例如:

assertThat(savedUser).hasRegistrationDate();

在很多情況下,像這樣的小改動會使測試更容易理解。因此,讓我們在測試源文件夾中創(chuàng)建我們自己的自定義斷言:

class UserAssert extends AbstractAssert<UserAssert, User> {

? ? UserAssert(User user) {

? ? ? ? super(user, UserAssert.class);

? ? }

? ? static UserAssert assertThat(User actual) {

? ? ? ? return new UserAssert(actual);

? ? }

? ? UserAssert hasRegistrationDate() {

? ? ? ? isNotNull();

? ? ? ? if (actual.getRegistrationDate() == null) {

? ? ? ? ? ? failWithMessage(

? ? ? ? ? ? ? ? ? ? "Expected user to have a registration date, but it was null"

? ? ? ? ? ? );

? ? ? ? }

? ? ? ? return this;

? ? }

}

現(xiàn)在,如果我們從新的?UserAssert 類而不是從 AssertJ 庫導(dǎo)入?assertThat 方法,我們就可以使用新的、更易于閱讀的斷言。

創(chuàng)建像這樣的自定義斷言似乎需要很多工作,但實際上只需幾分鐘即可完成。我堅信投入這些時間來創(chuàng)建可讀的測試代碼是值得的,即使之后它的可讀性只是稍微好一點。畢竟,我們只編寫一次測試代碼,其他人(包括“未來的我”)必須在軟件的生命周期中多次閱讀、理解和操作代碼。

如果仍然覺得工作量太大,請查看 AssertJ 的斷言生成器。

結(jié)論

在測試中啟動 Spring 應(yīng)用程序是有原因的,但對于普通的單元測試來說,這是沒有必要的。由于更長的周轉(zhuǎn)時間,它甚至是有害的。相反,我們應(yīng)該以一種易于支持為其編寫簡單單元測試的方式構(gòu)建我們的 Spring bean。

Spring Boot Test Starter?附帶 Mockito 和 AssertJ 作為測試庫。

讓我們利用這些測試庫來創(chuàng)建富有表現(xiàn)力的單元測試!

最終形式的代碼示例可在?github 上?找到。


使用 Spring Boot 進行單元測試的評論 (共 條)

分享到微博請遵守國家法律
于田县| 平顶山市| 吉林省| 金平| 崇义县| 老河口市| 邛崃市| 安图县| 土默特左旗| 信丰县| 山阴县| 黄骅市| 柘荣县| 桂东县| 嘉善县| 麻栗坡县| 洛浦县| 黑龙江省| 乐都县| 郸城县| 辽宁省| 和硕县| 莱西市| 广德县| 太康县| 乌鲁木齐县| 建始县| 澄江县| 秭归县| 隆昌县| 庐江县| 开阳县| 新疆| 仙桃市| 嘉禾县| 宁南县| 嘉祥县| 临海市| 安泽县| 兰坪| 永川市|