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

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

使用 Spring Boot 和 @DataJpaTest 測(cè)試 JPA 查詢

2021-10-28 19:50 作者:信碼由韁  | 我要投稿

【注】本文譯自:?https://reflectoring.io/spring-boot-data-jpa-test/


除了單元測(cè)試,集成測(cè)試在生產(chǎn)高質(zhì)量的軟件中起著至關(guān)重要的作用。一種特殊的集成測(cè)試處理我們的代碼和數(shù)據(jù)庫(kù)之間的集成。

通過(guò)?@DataJpaTest 注釋,Spring Boot 提供了一種便捷的方法來(lái)設(shè)置一個(gè)具有嵌入式數(shù)據(jù)庫(kù)的環(huán)境,以測(cè)試我們的數(shù)據(jù)庫(kù)查詢。

在本教程中,我們將首先討論哪些類型的查詢值得測(cè)試,然后討論創(chuàng)建用于測(cè)試的數(shù)據(jù)庫(kù)模式和數(shù)據(jù)庫(kù)狀態(tài)的不同方法。

? 代碼示例

本文附有?GitHub 上(https://github.com/thombergs/code-examples/tree/master/spring-boot/spring-boot-testing)的工作代碼示例

依賴

在本教程中,除了通常的 Spring Boot 依賴項(xiàng)之外,我們使用 JUnit Jupiter 作為我們的測(cè)試框架,使用 H2 作為內(nèi)存數(shù)據(jù)庫(kù)。


dependencies {

? compile('org.springframework.boot:spring-boot-starter-data-jpa')

? compile('org.springframework.boot:spring-boot-starter-web')

? runtime('com.h2database:h2')

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

? testCompile('org.junit.jupiter:junit-jupiter-engine:5.2.0')

}

測(cè)試什么?

首先要回答我們自己的問(wèn)題是我們需要測(cè)試什么。 讓我們考慮一個(gè)負(fù)責(zé)?UserEntity 對(duì)象的 Spring Data 存儲(chǔ)庫(kù):


interface UserRepository extends CrudRepository<UserEntity, Long> {

? ? // query methods

}

我們有不同的選項(xiàng)來(lái)創(chuàng)建查詢。讓我們?cè)敿?xì)看看其中的一些,以確定我們是否應(yīng)該用測(cè)試來(lái)覆蓋它們。

推斷查詢

第一個(gè)選項(xiàng)是創(chuàng)建一個(gè)推斷查詢:

UserEntity findByName(String name);

我們不需要告訴 Spring Data 要做什么,因?yàn)樗鼤?huì)自動(dòng)從方法名稱的名稱推斷 SQL 查詢。

這個(gè)特性的好處是 Spring Data 還會(huì)在啟動(dòng)時(shí)自動(dòng)檢查查詢是否有效。如果我們將方法重命名為?findByFoo() 并且?UserEntity 沒(méi)有屬性?foo ,Spring Data 會(huì)向我們拋出一個(gè)異常來(lái)指出這一點(diǎn):


org.springframework.data.mapping.PropertyReferenceException:

? No property foo found for type UserEntity!

因此,只要我們至少有一個(gè)測(cè)試嘗試在我們的代碼庫(kù)中啟動(dòng) Spring 應(yīng)用程序上下文,我們就不需要為我們的推斷查詢編寫額外的測(cè)試。

請(qǐng)注意,對(duì)于從?findByNameAndRegistrationDateBeforeAndEmailIsNotNull() 等長(zhǎng)方法名稱推斷出的查詢,情況并非如此。這個(gè)方法名很難掌握,也很容易出錯(cuò),所以我們應(yīng)該測(cè)試它是否真的符合我們的預(yù)期。

話雖如此,將此類方法重命名為更短、更有意義的名稱并添加 @Query?注釋以提供自定義 JPQL 查詢是一種很好的做法。

使用?@Query 自定義 JPQL 查詢

如果查詢變得更復(fù)雜,提供自定義 JPQL 查詢是有意義的:


@Query("select u from UserEntity u where u.name = :name")

UserEntity findByNameCustomQuery(@Param("name") String name);

與推斷查詢類似,我們可以免費(fèi)對(duì)這些 JPQL 查詢進(jìn)行有效性檢查。使用 Hibernate 作為我們的 JPA 提供者,如果發(fā)現(xiàn)無(wú)效查詢,我們將在啟動(dòng)時(shí)得到一個(gè) QuerySyntaxException:


org.hibernate.hql.internal.ast.QuerySyntaxException:

unexpected token: foo near line 1, column 64 [select u from ...]

但是,自定義查詢比通過(guò)單個(gè)屬性查找條目要復(fù)雜得多。例如,它們可能包括與其他表的連接或返回復(fù)雜的 DTO 而不是實(shí)體。

那么,我們應(yīng)該為自定義查詢編寫測(cè)試嗎?令人不滿意的答案是,我們必須自己決定查詢是否復(fù)雜到需要測(cè)試。

使用?@Query 的本地查詢

另一種方法是使用本地查詢:


@Query(

? value = "select * from user as u where u.name = :name",

? nativeQuery = true)

UserEntity findByNameNativeQuery(@Param("name") String name);

我們沒(méi)有指定 JPQL 查詢(它是對(duì) SQL 的抽象),而是直接指定一個(gè) SQL 查詢。此查詢可能使用特定數(shù)據(jù)庫(kù)的 SQL 方言。

需要注意的是,Hibernate 和 Spring Data 都不會(huì)在啟動(dòng)時(shí)驗(yàn)證本地查詢。由于查詢可能包含特定于數(shù)據(jù)庫(kù)的 SQL,因此 Spring Data 或 Hibernate 無(wú)法知道要檢查什么。

因此,本地查詢是集成測(cè)試的主要候選者。但是,如果他們真的使用特定數(shù)據(jù)庫(kù)的 SQL,那么這些測(cè)試可能不適用于嵌入式內(nèi)存數(shù)據(jù)庫(kù),因此我們必須在后臺(tái)提供一個(gè)真實(shí)的數(shù)據(jù)庫(kù)(比如,在持續(xù)集成管道中按需設(shè)置的 docker 容器中)。

@DataJpaTest?簡(jiǎn)介

為了測(cè)試 Spring Data JPA 存儲(chǔ)庫(kù)或任何其他與 JPA 相關(guān)的組件,Spring Boot 提供了?@DataJpaTest 注解。我們可以將它添加到單元測(cè)試中,它將設(shè)置一個(gè) Spring 應(yīng)用程序上下文:


@ExtendWith(SpringExtension.class)

@DataJpaTest

class UserEntityRepositoryTest {

? @Autowired private DataSource dataSource;

? @Autowired private JdbcTemplate jdbcTemplate;

? @Autowired private EntityManager entityManager;

? @Autowired private UserRepository userRepository;

? @Test

? void injectedComponentsAreNotNull(){

? ? assertThat(dataSource).isNotNull();

? ? assertThat(jdbcTemplate).isNotNull();

? ? assertThat(entityManager).isNotNull();

? ? assertThat(userRepository).isNotNull();

? }

}

@ExtendWith

本教程中的代碼示例使用? @ExtendWith 注解告訴 JUnit 5 啟用 Spring 支持。從 Spring Boot 2.1 開(kāi)始,我們不再需要加載 SpringExtension,因?yàn)樗鳛樵⒔獍?Spring Boot 測(cè)試注解中,例如 @DataJpaTest、@WebMvcTest 和 @SpringBootTest。

這樣創(chuàng)建的應(yīng)用程序上下文將不包含我們的 Spring Boot 應(yīng)用程序所需的整個(gè)上下文,而只是它的一個(gè)“切片”,其中包含初始化任何 JPA 相關(guān)組件(如我們的 Spring Data 存儲(chǔ)庫(kù))所需的組件。

例如,如果需要,我們可以將 DataSource、@JdbcTemplate 或?@EntityManage 注入我們的測(cè)試類。此外,我們可以從我們的應(yīng)用程序中注入任何 Spring Data 存儲(chǔ)庫(kù)。上述所有組件將自動(dòng)配置為指向嵌入式內(nèi)存數(shù)據(jù)庫(kù),而不是我們可能在?application.properties 或? application.yml 文件中配置的“真實(shí)”數(shù)據(jù)庫(kù)。

請(qǐng)注意,默認(rèn)情況下,包含所有這些組件(包括內(nèi)存數(shù)據(jù)庫(kù))的應(yīng)用程序上下文在所有?@DataJpaTest 注解的測(cè)試類中的所有測(cè)試方法之間共享。

這就是為什么在默認(rèn)情況下每個(gè)測(cè)試方法都在自己的事務(wù)中運(yùn)行的原因,該事務(wù)在方法執(zhí)行后回滾。這樣,數(shù)據(jù)庫(kù)狀態(tài)在測(cè)試之間保持原始狀態(tài),并且測(cè)試保持相互獨(dú)立。

創(chuàng)建數(shù)據(jù)庫(kù)模式

在我們可以測(cè)試對(duì)數(shù)據(jù)庫(kù)的任何查詢之前,我們需要?jiǎng)?chuàng)建一個(gè) SQL 模式來(lái)使用。讓我們看看一些不同的方法來(lái)做到這一點(diǎn)。

使用 Hibernate?ddl-auto

默認(rèn)情況下,@DataJpaTest 會(huì)配置 Hibernate 為我們自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)模式。對(duì)此負(fù)責(zé)的屬性是 spring.jpa.hibernate.ddl-auto,Spring Boot 默認(rèn)將其設(shè)置為 create-drop,這意味著模式在運(yùn)行測(cè)試之前創(chuàng)建并在測(cè)試執(zhí)行后刪除。

因此,如果我們對(duì) Hibernate 為我們創(chuàng)建模式感到滿意,我們就不必做任何事情。

使用?schema.sql

Spring Boot?支持在應(yīng)用程序啟動(dòng)時(shí)執(zhí)行自定義?schema.sql?文件。.

如果 Spring 在類路徑中找到?schema.sql 文件,則將針對(duì)數(shù)據(jù)源執(zhí)行該文件。 這會(huì)覆蓋上面討論的 Hibernate 的?ddl-auto 配置。

我們可以使用屬性?spring.datasource.initialization-mode 控制是否應(yīng)該執(zhí)行 schema.sql。默認(rèn)值是嵌入的,這意味著它只會(huì)對(duì)嵌入的數(shù)據(jù)庫(kù)執(zhí)行(即在我們的測(cè)試中)。如果我們將其設(shè)置為始終,它將始終執(zhí)行。

以下日志輸出確認(rèn)文件已被執(zhí)行:

Executing SQL script from URL [file:.../out/production/resources/schema.sql]

設(shè)置 Hibernate 的?ddl-auto 配置以在使用腳本初始化架構(gòu)時(shí)進(jìn)行驗(yàn)證是有意義的,以便 Hibernate 在啟動(dòng)時(shí)檢查創(chuàng)建的模式是否與實(shí)體類匹配:


@ExtendWith(SpringExtension.class)

@DataJpaTest

@TestPropertySource(properties = {

? ? ? ? "spring.jpa.hibernate.ddl-auto=validate"

})

class SchemaSqlTest {

? ...

}

使用 Flyway

Flyway 是一種?數(shù)據(jù)庫(kù)遷移工具,允許指定多個(gè) SQL 腳本來(lái)創(chuàng)建數(shù)據(jù)庫(kù)模式。它會(huì)跟蹤目標(biāo)數(shù)據(jù)庫(kù)上已經(jīng)執(zhí)行了這些腳本中的哪些腳本,以便只執(zhí)行之前沒(méi)有執(zhí)行過(guò)的腳本。

要激活 Flyway,我們只需要將依賴項(xiàng)放入我們的?build.gradle 文件中(如果我們使用 Maven,則類似):

compile('org.flywaydb:flyway-core')

如果我們沒(méi)有專門配置 Hibernate 的?ddl-auto 配置,它會(huì)自動(dòng)退出,因此 Flyway 具有優(yōu)先權(quán),并且默認(rèn)情況下會(huì)針對(duì)我們的內(nèi)存測(cè)試執(zhí)行它在文件夾?src/main/resources/db/migration 中找到的所有 SQL 腳本 數(shù)據(jù)庫(kù)。

同樣,將?ddl-auto 設(shè)置為 validate?是有意義的,讓 Hibernate 檢查 Flyway 生成的模式是否符合我們的 Hibernate 實(shí)體的期望:


@ExtendWith(SpringExtension.class)

@DataJpaTest

@TestPropertySource(properties = {

? ? ? ? "spring.jpa.hibernate.ddl-auto=validate"

})

class FlywayTest {

? ...

}

在測(cè)試中使用 Flyway 的價(jià)值

如果我們?cè)谏a(chǎn)中使用 Flyway,也能在上面描述的那樣在 JPA 測(cè)試中使用它,那就太好了。只有這樣我們才能在測(cè)試時(shí)知道 flyway 腳本按預(yù)期工作。

但是,這僅適用于腳本包含在生產(chǎn)數(shù)據(jù)庫(kù)和測(cè)試中使用的內(nèi)存數(shù)據(jù)庫(kù)(我們的示例中為 H2 數(shù)據(jù)庫(kù))上都有效的 SQL。如果不是這種情況,我們必須在我們的測(cè)試中禁用 Flyway,方法是將?spring.flyway.enabled 屬性設(shè)置為 false,并將?spring.jpa.hibernate.ddl-auto 屬性設(shè)置為?create-drop 以讓 Hibernate 生成模式。

無(wú)論如何,讓我們確保將?ddl-auto 屬性在生產(chǎn)配置文件中設(shè)置為?validate!這是我們抵御 Flyway 腳本錯(cuò)誤的最后一道防線!

使用 Liquibase

Liquibase 是另一種數(shù)據(jù)庫(kù)遷移工具,其工作方式類似于 Flyway,但支持除 SQL 之外的其他輸入格式。例如,我們可以提供定義數(shù)據(jù)庫(kù)架構(gòu)的 YAML 或 XML 文件。

我們只需添加依賴項(xiàng)即可激活它:

compile('org.liquibase:liquibase-core')

默認(rèn)情況下,Liquibase 將自動(dòng)創(chuàng)建在?src/main/resources/db/changelog/db.changelog-master.yaml 中定義的模式。

同樣,設(shè)置?ddl-auto?為 validate?是有意義的:


@ExtendWith(SpringExtension.class)

@DataJpaTest

@TestPropertySource(properties = {

? ? ? ? "spring.jpa.hibernate.ddl-auto=validate"

})

class LiquibaseTest {

? ...

}

在測(cè)試中使用 Liquibase 的價(jià)值

由于 Liquibase 允許多種輸入格式充當(dāng) SQL 上的抽象層,因此即使它們的 SQL 方言不同,也可以跨多個(gè)數(shù)據(jù)庫(kù)使用相同的腳本。這使得在我們的測(cè)試和生產(chǎn)中使用相同的 Liquibase 腳本成為可能。

不過(guò),YAML 格式非常敏感,而且我最近在維護(hù)大型 YAML 文件集合時(shí)遇到了麻煩。這一點(diǎn),以及盡管我們實(shí)際上必須為不同的數(shù)據(jù)庫(kù)編輯這些文件的抽象,最終導(dǎo)致轉(zhuǎn)向 Flyway。

填充數(shù)據(jù)庫(kù)

現(xiàn)在我們已經(jīng)為我們的測(cè)試創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)模式,我們終于可以開(kāi)始實(shí)際的測(cè)試了。在數(shù)據(jù)庫(kù)查詢測(cè)試中,我們通常會(huì)向數(shù)據(jù)庫(kù)添加一些數(shù)據(jù),然后驗(yàn)證我們的查詢是否返回正確的結(jié)果。

同樣,有多種方法可以將數(shù)據(jù)添加到我們的內(nèi)存數(shù)據(jù)庫(kù)中,所以讓我們逐一討論。

使用 data.sql

與?schema.sql 類似,我們可以使用包含插入語(yǔ)句的?data.sql 文件來(lái)填充我們的數(shù)據(jù)庫(kù)。上述規(guī)則同樣適用。

可維護(hù)性

data.sql?文件迫使我們將所有?insert 語(yǔ)句放在一個(gè)地方。每一個(gè)測(cè)試都將依賴于這個(gè)腳本來(lái)設(shè)置數(shù)據(jù)庫(kù)狀態(tài)。這個(gè)腳本很快就會(huì)變得非常大并且難以維護(hù)。如果有需要沖突數(shù)據(jù)庫(kù)狀態(tài)的測(cè)試怎么辦?

因此,應(yīng)謹(jǐn)慎考慮這種方法。

手動(dòng)插入實(shí)體

為每個(gè)測(cè)試創(chuàng)建特定數(shù)據(jù)庫(kù)狀態(tài)的最簡(jiǎn)單方法是在運(yùn)行被測(cè)查詢之前在測(cè)試中保存一些實(shí)體:


@Test

void whenSaved_thenFindsByName() {

? userRepository.save(new UserEntity(

? ? ? ? ? "Zaphod Beeblebrox",

? ? ? ? ? "zaphod@galaxy.net"));

? assertThat(userRepository.findByName("Zaphod Beeblebrox")).isNotNull();

}

這對(duì)于上面示例中的簡(jiǎn)單實(shí)體來(lái)說(shuō)很容易。但在實(shí)際項(xiàng)目中,這些實(shí)體的構(gòu)建和與其他實(shí)體的關(guān)系通常要復(fù)雜得多。此外,如果我們想測(cè)試比?findByName 更復(fù)雜的查詢,很可能我們需要?jiǎng)?chuàng)建比單個(gè)實(shí)體更多的數(shù)據(jù)。這很快變得非常令人厭煩。

控制這種復(fù)雜性的一種方法是創(chuàng)建工廠方法,可能結(jié)合?Objectmother 和 Builder 模式。

在 Java 代碼中“手動(dòng)”對(duì)數(shù)據(jù)庫(kù)進(jìn)行編程的方法比其他方法有很大的優(yōu)勢(shì),因?yàn)?strong>它是重構(gòu)安全的。代碼庫(kù)中的更改會(huì)導(dǎo)致我們的測(cè)試代碼中出現(xiàn)編譯錯(cuò)誤。在所有其他方法中,我們必須運(yùn)行測(cè)試才能收到有關(guān)重構(gòu)導(dǎo)致的潛在錯(cuò)誤的通知。

使用 Spring DBUnit

DBUnit 是一個(gè)支持將數(shù)據(jù)庫(kù)設(shè)置為某種狀態(tài)的庫(kù)。Spring DBUnit ?將 DBUnit 與 Spring 集成在一起,因此它可以自動(dòng)與 Spring 的事務(wù)等一起工作。

要使用它,我們需要向 Spring DBUnit 和 DBUnit 添加依賴項(xiàng):


compile('com.github.springtestdbunit:spring-test-dbunit:1.3.0')

compile('org.dbunit:dbunit:2.6.0')

然后,對(duì)于每個(gè)測(cè)試,我們可以創(chuàng)建一個(gè)包含所需數(shù)據(jù)庫(kù)狀態(tài)的自定義 XML 文件:


<?xml version="1.0" encoding="UTF-8"?>

<dataset>

? ? <user

? ? ? ? id="1"

? ? ? ? name="Zaphod Beeblebrox"

? ? ? ? email="zaphod@galaxy.net"

? ? />

</dataset>

默認(rèn)情況下,XML 文件(我們將其命名為 createUser.xml)位于測(cè)試類旁邊的類路徑中。

在測(cè)試類中,我們需要添加兩個(gè)?TestExecutionListeners 來(lái)啟用 DBUnit 支持。要設(shè)置某個(gè)數(shù)據(jù)庫(kù)狀態(tài),我們可以在測(cè)試方法上使用 @DatabaseSetup:


@ExtendWith(SpringExtension.class)

@DataJpaTest

@TestExecutionListeners({

? ? ? ? DependencyInjectionTestExecutionListener.class,

? ? ? ? TransactionDbUnitTestExecutionListener.class

})

class SpringDbUnitTest {

? @Autowired

? private UserRepository userRepository;

? @Test

? @DatabaseSetup("createUser.xml")

? void whenInitializedByDbUnit_thenFindsByName() {

? ? UserEntity user = userRepository.findByName("Zaphod Beeblebrox");

? ? assertThat(user).isNotNull();

? }

}

對(duì)于更改數(shù)據(jù)庫(kù)狀態(tài)的測(cè)試查詢,我們甚至可以使用?@ExpectedDatabase 來(lái)定義數(shù)據(jù)庫(kù)在測(cè)試后預(yù)期處于的狀態(tài)。

但是請(qǐng)注意,自 2016 年以來(lái),Spring DBUnit 沒(méi)有再維護(hù)。

@DatabaseSetup 不起作用?

在我的測(cè)試中,我遇到了?@DatabaseSetup 注釋被默默忽略的問(wèn)題。原來(lái)有一個(gè)?ClassNotFoundException 因?yàn)檎也坏侥承?DBUnit 類。不過(guò),這個(gè)異常被吞了。

原因是我忘記包含對(duì) DBUnit 的依賴,因?yàn)槲艺J(rèn)為 Spring Test DBUnit 可遞進(jìn)地含它。因此,如果您遇到相同的問(wèn)題,請(qǐng)檢查您是否包含了這兩個(gè)依賴項(xiàng)。

使用 @Sql

一個(gè)非常相似的方法是使用 Spring 的 @Sql 注釋。我們沒(méi)有使用 XML 來(lái)描述數(shù)據(jù)庫(kù)狀態(tài),而是直接使用 SQL:


INSERT INTO USER

? ? ? ? ? ? (id,

? ? ? ? ? ? ?NAME,

? ? ? ? ? ? ?email)

VALUES ? ? ?(1,

? ? ? ? ? ? ?'Zaphod Beeblebrox',

? ? ? ? ? ? ?'zaphod@galaxy.net');

在我們的測(cè)試中,我們可以簡(jiǎn)單地使用?@Sql 注解來(lái)引用 SQL 文件來(lái)填充數(shù)據(jù)庫(kù):


@ExtendWith(SpringExtension.class)

@DataJpaTest

class SqlTest {

? @Autowired

? private UserRepository userRepository;

? @Test

? @Sql("createUser.sql")

? void whenInitializedByDbUnit_thenFindsByName() {

? ? UserEntity user = userRepository.findByName("Zaphod Beeblebrox");

? ? assertThat(user).isNotNull();

? }

}

如果我們需要多個(gè)腳本,我們可以使用?@SqlGroup 來(lái)組合它們。

結(jié)論

為了測(cè)試數(shù)據(jù)庫(kù)查詢,我們需要?jiǎng)?chuàng)建模式并用一些數(shù)據(jù)填充它的方法。由于測(cè)試應(yīng)該相互獨(dú)立,因此最好對(duì)每個(gè)測(cè)試分別執(zhí)行此操作。

對(duì)于簡(jiǎn)單的測(cè)試和簡(jiǎn)單的數(shù)據(jù)庫(kù)實(shí)體,通過(guò)創(chuàng)建和保存 JPA 實(shí)體手動(dòng)創(chuàng)建狀態(tài)就足夠了。對(duì)于更復(fù)雜的場(chǎng)景,@DatabaseSetup 和?@Sq 提供了一種在 XML 或 SQL 文件中外部化數(shù)據(jù)庫(kù)狀態(tài)的方法。


使用 Spring Boot 和 @DataJpaTest 測(cè)試 JPA 查詢的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
金川县| 体育| 平度市| 方城县| 含山县| 中西区| 舒兰市| 台州市| 且末县| 高雄县| 西贡区| 紫云| 谢通门县| 张掖市| 华蓥市| 东方市| 双柏县| 惠来县| 霍邱县| 班戈县| 彭山县| 胶州市| 独山县| 衡山县| 靖安县| 自贡市| 绍兴县| 沭阳县| 湖州市| 哈尔滨市| 潜山县| 嵊泗县| 岳阳市| 太仆寺旗| 玉树县| 五莲县| 囊谦县| 安义县| 洪泽县| 阳泉市| 万载县|