最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Spring?Boot進行單元測試詳情

 更新時間:2022年09月22日 15:40:00   作者:六七十三  
這篇文章主要介紹了使用Spring?Boot進行單元測試詳情,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

前言

本文給你提供在Spring Boot 應用程序中編寫好的單元測試的機制,并且深入技術細節(jié)。

我們將帶你學習如何以可測試的方式創(chuàng)建Spring Bean實例,然后討論如何使用MockitoAssertJ,這兩個包在Spring Boot中都為了測試默認引用了。

本文只討論單元測試。至于集成測試,測試web層和測試持久層將會在接下來的系列文章中進行討論。

使用 Spring Boot 進行測試系列文章

這個教程是一個系列:

  • 使用 Spring Boot 進行單元測試(本文)
  • 使用 Spring Boot 和 @WebMvcTest 測試SpringMVC controller層
  • 使用 Spring Boot 和 @DataJpaTest 測試JPA持久層查詢
  • 通過 @SpringBootTest 進行集成測試

如果你喜歡看視頻教程,可以看看Philip的課程:測試Spring Boot應用程序課程

依賴項

本文中,為了進行單元測試,我們會使用JUnit Jupiter(Junit 5),MockitoAssertJ。此外,我們會引用Lombok來減少一些模板代碼:

dependencies{
  compileOnly('org.projectlombok:lombok')
  testCompile('org.springframework.boot:spring-boot-starter-test')
  testCompile 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
  testCompile('org.mockito:mockito-junit-jupiter:2.23.0')
}

MockitoAssertJ會在spring-boot-test依賴中自動引用,但是我們需要自己引用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();
  }

}

這個測試類在我的電腦上需要大概4.5秒來執(zhí)行一個空的Spring項目。

但是一個好的單元測試僅僅需要幾毫秒。否則就會阻礙TDD(測試驅動開發(fā))流程,這個流程倡導“測試/開發(fā)/測試”。

但是就算我們不使用TDD,等待一個單元測試太久也會破壞我們的注意力。

執(zhí)行上述的測試方法事實上僅需要幾毫秒。剩下的4.5秒是因為@SpringBootTest告訴了 Spring Boot 要啟動整個Spring Boot 應用程序上下文。

所以我們啟動整個應用程序僅僅是因為要把RegisterUseCase實例注入到我們的測試類中。啟動整個應用程序可能耗時更久,假設應用程序更大、Spring需要加載更多的實例到應用程序上下文中。

所以,這就是為什么不要在單元測試中使用Spring。坦白說,大部分編寫單元測試的教程都沒有使用Spring Boot。

創(chuàng)建一個可測試的類實例

然后,為了讓Spring實例有更好的測試性,有幾件事是我們可以做的。

屬性注入是不好的

讓我們以一個反例開始??紤]下述類:

@Service
public class RegisterUseCase {

  @Autowired
  private UserRepository userRepository;

  public User registerUser(User user) {
    return userRepository.save(user);
  }

}

這個類如果沒有Spring沒法進行單元測試,因為它沒有提供方法傳遞UserRepository實例。因此我們只能用文章之前討論的方式-讓Spring創(chuàng)建UserRepository實例,并通過@Autowired注解注入進去。

這里的教訓是:不要用屬性注入。

提供一個構造函數(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實例參數(shù)的構造函數(shù)來允許構造函數(shù)注入。在這個單元測試中,我們現(xiàn)在可以創(chuàng)建這樣一個實例(或者我們之后要討論的Mock實例)并通過構造函數(shù)注入了。

當創(chuàng)建生成應用上下文的時候,Spring會自動使用這個構造函數(shù)來初始化RegisterUseCase對象。注意,在Spring 5 之前,我們需要在構造函數(shù)上增加@Autowired注解,以便讓Spring找到這個構造函數(shù)。

還要注意的是,現(xiàn)在UserRepository屬性是final修飾的。這很重要,因為這樣的話,應用程序生命周期時間內這個屬性內容不會再變化。此外,它還可以幫我們避免變成錯誤,因為如果我們忘記初始化該屬性的話,編譯器就報錯。

減少模板代碼

通過使用Lombok@RequiredArgsConstructor注解,我們可以讓構造函數(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();
  }

}

還有部分確實,就是如何模擬測試類所依賴的UserReposity實例,我們不想依賴真實的類,因為這個類需要一個數(shù)據(jù)庫連接。

使用Mockito來模擬依賴項

現(xiàn)在事實上的標準模擬庫是 Mockito。它提供至少兩種方式來創(chuàng)建一個模擬UserRepository實例,來填補前述代碼的空白。

使用普通Mockito來模擬依賴

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

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

這會從外界創(chuàng)建一個看起來像UserRepository的對象。默認情況下,方法被調用時不會做任何事情,如果方法有返回值,會返回null。

因為userRepository.save(user)返回null,現(xiàn)在我們的測試代碼assertThat(savedUser.getRegistrationDate()).isNotNull()會報空指針異常(NullPointerException)。

所以我們需要告訴Mockito,當userRepository.save(user)調用的時候返回一些東西。我們可以用靜態(tài)的when方法實現(xiàn):

@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ù)以及驗證方法調用,提供了非常多的特性。想看更多,文檔

通過Mockito@Mock注解模擬對象

創(chuàng)建一個模擬對象的第二種方式是使用Mockito@Mock注解結合 JUnit Jupiter的MockitoExtension一起使用:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

  @Mock
  private UserRepository userRepository;

  private RegisterUseCase registerUseCase;

  @BeforeEach
  void initUseCase() {
    registerUseCase = new RegisterUseCase(userRepository);
  }

  @Test
  void savedUserHasRegistrationDate() {
    // ...
  }

}

@Mock注解指明那些屬性需要Mockito注入模擬對象。由于JUnit不會自動實現(xiàn),MockitoExtension則告訴Mockito來評估這些@Mock注解。

這個結果和調用Mockito.mock()方法一樣,憑個人品味選擇即可。但是請注意,通過使用 MockitoExtension,我們的測試用例被綁定到測試框架。

我們可以在RegisterUseCase屬性上使用@InjectMocks注解來注入實例,而不是手動通過構造函數(shù)構造。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)用到它進行斷言:

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

然而,有沒有可能讓斷言可讀性更強呢?像這樣,例子:

assertThat(savedUser).hasRegistrationDate();

有很多測試用例,只需要像這樣進行很小的改動就能大大提高可理解性。所以,讓我們在test/sources中創(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)在,如果我們不是從AssertJ庫直接導入,而是從我們自定義斷言類UserAssert引入assertThat方法的話,我們就可以使用新的、更可讀的斷言。

創(chuàng)建一個這樣自定義的斷言類看起來很費時間,但是其實幾分鐘就完成了。我相信,將這些時間投入到創(chuàng)建可讀性強的測試代碼中是值得的,即使之后它的可讀性只有一點點提高。我們編寫測試代碼就一次,但是之后,很多其他人(包括未來的我)在軟件生命周期中,需要閱讀、理解然后操作這些代碼很多次。

如果你還是覺得很費事,可以看看斷言生成器

結論

盡管在測試中啟動Spring應用程序也有些理由,但是對于一般的單元測試,它不必要。有時甚至有害,因為更長的周轉時間。換言之,我們應該使用更容易支持編寫普通單元測試的方式構建Spring實例。

Spring Boot Test Starter附帶MockitoAssertJ作為測試庫。讓我們利用這些測試庫來創(chuàng)建富有表現(xiàn)力的單元測試!

到此這篇關于使用Spring Boot進行單元測試詳情的文章就介紹到這了,更多相關Spring Boot單元測試內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java實現(xiàn)桌球游戲

    java實現(xiàn)桌球游戲

    這篇文章主要為大家詳細介紹了java實現(xiàn)桌球游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • SpringCloud Feign的使用簡介

    SpringCloud Feign的使用簡介

    這篇文章主要介紹了SpringCloud Feign的使用簡介,幫助大家更好的理解和學習使用SpringCloud,感興趣的朋友可以了解下
    2021-04-04
  • java中url任意跳轉漏洞的解決

    java中url任意跳轉漏洞的解決

    URL任意跳轉漏洞是一種安全漏洞,它發(fā)生在應用程序沒有正確地驗證目標URL,允許攻擊者將用戶重定向到惡意網(wǎng)站,本文就來介紹一下漏洞的解決修復方案,感興趣的可以了解一下
    2024-06-06
  • springcloud項目改名的操作方法

    springcloud項目改名的操作方法

    這篇文章主要介紹了springcloud項目改名的操作方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • RabbitMQ中的延遲隊列機制詳解

    RabbitMQ中的延遲隊列機制詳解

    這篇文章主要介紹了RabbitMQ中的延遲隊列機制詳解,延時隊列內部是有序的,最重要的特性就體現(xiàn)在它的延時屬性上,延時隊列中的元素是希望,在指定時間到了以后或之前取出和處理,簡單來說,延時隊列就是用來存放需要在指定時間被處理的元素的隊列,需要的朋友可以參考下
    2023-09-09
  • Java多線程之如何確定線程數(shù)的方法

    Java多線程之如何確定線程數(shù)的方法

    創(chuàng)建線程和銷毀線程都是比較耗時的操作,如果每個任務都創(chuàng)建一個線程去處理,這樣線程會越來越多,那么應該如何確定線程的數(shù)量,本文就詳細的介紹一下,感興趣的可以了解一下
    2022-03-03
  • 使用@ConditionalOnProperty控制是否加載的操作

    使用@ConditionalOnProperty控制是否加載的操作

    這篇文章主要介紹了使用@ConditionalOnProperty控制是否加載的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringAop @Around執(zhí)行兩次的原因及解決

    SpringAop @Around執(zhí)行兩次的原因及解決

    這篇文章主要介紹了SpringAop @Around執(zhí)行兩次的原因及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • MyBatis接口的簡單實現(xiàn)原理分析

    MyBatis接口的簡單實現(xiàn)原理分析

    這里僅僅舉個簡單例子來說明原理,不是完全針對MyBatis的,這種思想我們也可以應用在其他地方。地mybatis接口實現(xiàn)原理感興趣的朋友一起看看吧
    2017-07-07
  • Spring?Boot使用Schedule實現(xiàn)定時任務的方法

    Spring?Boot使用Schedule實現(xiàn)定時任務的方法

    這篇文章主要介紹了Spring?Boot使用Schedule實現(xiàn)定時任務,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03

最新評論

达拉特旗| 和田市| 正宁县| 兴义市| 东丽区| 临清市| 固镇县| 长宁区| 花莲县| 越西县| 灌阳县| 察哈| 荣成市| 吴桥县| 长春市| 大埔县| 沛县| 南召县| 叶城县| 尖扎县| 苍溪县| 康乐县| 漠河县| 莲花县| 巴东县| 东乡| 慈利县| 芜湖县| 星座| 调兵山市| 大悟县| 荣昌县| 林周县| 洛宁县| 武强县| 陵水| 冀州市| 格尔木市| 清镇市| 丽水市| 通化县|