国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring Boot 單元測試二三事

xbynet / 1279人閱讀

摘要:但是,一個好的單元測試應該是毫秒級的,否則這會影響的工作方式,這也就是測試驅動開發(fā)的思想。在單元測試中,我們可以像這樣來構建一個實例。所以,我們在寫單元測試的時候,應該以一種更簡單的方式去構建。

本文翻譯自:https://reflectoring.io/unit-...

原文作者:Tom Hombergs

譯文原地址:https://weyunx.com/2019/02/04...

寫好單元測試是一門技術活,不過好在我們現(xiàn)在有很多框架來幫助我們學習。

本文就為您介紹這些框架,同時詳細介紹編寫優(yōu)秀的 Sping Boot 單元測試所必需的技術細節(jié),

我們將了解如何以可測試的方式創(chuàng)建 Spring bean,然后討論 Mockito 和 AssertJ 的使用,這兩個庫在默認情況下都集成在 Spring Boot 里。

需要注意的是本文只討論單元測試,組裝測試、web 層測試和持久層測試會在后面的文章里討論。

依賴

在本文中,我們將使用 JUnit Jupiter (JUnit 5), Mockito, and AssertJ,同時還會引入 Lombok 來省去一些繁復的工作。

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")

spring-boot-starter-test 默認引入了 Mockito and AssertJ,對于 Lombok 則需要我們自己手工引入。

不要使用 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();
  }

}

我們去執(zhí)行這個測試類,花了大概 4.5 秒的時間,原因僅僅是因為計算機要為它去運行一個空的 Spring 項目。

但是,一個好的單元測試應該是毫秒級的,否則這會影響「test / code / test」的工作方式,這也就是測試驅動開發(fā)的思想 (TDD)。即使我們不做 TDD,在編寫測試上花了太多時間也會影響我們的開發(fā)思路。

其實,上面的測試方法實際執(zhí)行只花費了幾毫秒,剩下的 4.5 秒全部花費在了 @SpringBootRun 上,因為 Spring Boot 需要啟動整個 Spring Boot 應用。

也就是說,我們啟動整個應用,耗費了大量資源,僅僅是去為了測試一個方法,當我們的應用未來越來越大的時候,那將耗費更久的時間去啟動。

所以,為什么不要用 Spring Boot 來做單元測試呢?接下來,本文會討論如何不用 Spring Boot 來進行單元測試。

創(chuàng)建測試類

通常,我們可以有如下方法來讓我們的 Spring beans 更容易進行測試。

不要注入

首先我們先看一個錯誤的例子:

@Service
public class RegisterUseCase {

  @Autowired
  private UserRepository userRepository;

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

}

然而這個類還是必須通過 Spring 才能執(zhí)行,因為我們無法繞過 UserRepository 這個實例。就像前面提到的,我們必須換一種方法,不使用 @Autowired 來注入 UserRepository

知識點:不要注入

寫一個構造器

我們看一下不使用 @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 實例。在單元測試中,我們可以像這樣來構建一個實例。

Spring 會自動的使用構造器來實例化一個 RegisterUseCase 對象。需要注意的是,在 Spring 5 之前,我們需要@Autowired 注解來讓構造器生效。

同樣需要注意的是 UserRepository 字段現(xiàn)在是 final,這樣在整個應用的生命周期里,它都將是個常量,這可以避免編碼錯誤,因為我們如果忘記初始化字段,編譯的時候就會報錯。

減少繁復的代碼

使用 Lombok 的 @RequiredArgsConstructor 注解,可以讓構造器的寫法更簡潔:

@Service
@RequiredArgsConstructor
public class RegisterUseCase {

  private final UserRepository userRepository;

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

}

現(xiàn)在我們的測試類就很簡潔,沒有冗余繁復的代碼:

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 實例,因為我們不想去真正的去執(zhí)行,因為它可能需要去連接數據庫。

使用 Mockito

現(xiàn)行的標準模擬庫是 Mockito,它提供了至少兩種方式來模擬 UserRepository

直接調用

第一種方法就是直接使用 Mockito:

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

這個創(chuàng)建一個對象,看起來和 UserRepository 一樣。默認的情況下,這個類什么也不會做,如果調用有返回值的方法,也只會返回 null。

我們的測試現(xiàn)在會是失敗,在 assertThat(savedUser.getRegistrationDate()).isNotNull() 這兒報 NullPointerException 空指針異常,因為 userRepository.save(user) 只會返回 null

所以,我們需要告訴 Mockito,當 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 具有一整套的測試方案,可以用來模擬、匹配參數以及識別方法的調用,更多資料可以參考這里。

使用 @Mock

此外還可以用 @Mock 注解來模擬對象,它需要和 MockitoExtension 組合使用。

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

  @Mock
  private UserRepository userRepository;

  private RegisterUseCase registerUseCase;

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

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

}

@Mock 注解會指定字段將被注入到 mock 對象,@MockitoExtension 會告訴 Mockito 去掃描 @Mock 注解,因為 JUnit 不會自動去執(zhí)行。

這其實和直接手工執(zhí)行 Mockito.mock() 的結果一樣,只是使用習慣的區(qū)別。不過使用 MockitoExtension 我們的測試就可以綁定到測試框架里。

需要說明的是我們可以在 registerUseCase 字段上使用 @InjectMocks 注解來替代手工構造一個 RegisterUseCase 對象,Mockito 會幫我們自動構造對象,如:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

  @Mock
  private UserRepository userRepository;

  @InjectMocks
  private RegisterUseCase registerUseCase;

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

}
讓斷言更直白

另一個 Spring Boot 自帶的測試支持庫是 AssertJ,上面的例子里,在實現(xiàn)斷言的時候已經用到了:

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

不過我們想讓寫法變得更直白好理解,比如:

assertThat(savedUser).hasRegistrationDate();

通常,我們可以做小改動就可以讓代碼變得更容易理解,所以我們新建一個自定義的斷言對象:

public class UserAssert extends AbstractAssert {

  public UserAssert(User user) {
    super(user, UserAssert.class);
  }

  public static UserAssert assertThat(User actual) {
    return new UserAssert(actual);
  }

  public UserAssert hasRegistrationDate() {
    isNotNull();
    if (actual.getRegistrationDate() == null) {
      failWithMessage("Expected user to have a registration date, but it was null");
    }
    return this;
  }
}

這樣,我們調用 UserAssert 類的 assertThat 方法,而不是直接從 Assertj 庫里調用。

創(chuàng)建自定義的斷言看起來需要很多的工作量,但其實也就是幾分鐘的事。我相信這幾分鐘的工作,絕對是值得的,即使是讓代碼看起來更直白容易理解。測試代碼我們只會寫一次,然后其他人(包括我在以后)都只是去讀這段代碼,然后是反反復復的去修改這段代碼,直到產品消亡。

如果還有疑問,可以參考 Assertions Generator。

結論

我們可能有種種的理由在 Spring 里進行測試,但是對于一個普通的單元測試,可以這么做,但是沒有必要。隨著以后應用越來越龐大,啟動時間越來越長,可能還會帶來問題。所以,我們在寫單元測試的時候,應該以一種更簡單的方式去構建 Sprnig bean。

Spring Boot Test Starter 附帶了 Mockito 和 AssertJ 作為測試依賴庫,所以盡可能的使用這些測試庫來做更好的單元測試吧。

所有的代碼可以在這里找到。

如果發(fā)現(xiàn)譯文存在錯誤或其他需要改進的地方,歡迎斧正。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73468.html

相關文章

  • 應用開發(fā)者必須了解的Kubernetes網絡三事

    摘要:在容器領域內,已毋庸置疑成為了容器編排和管理的社區(qū)標準。客戶端無需連接到每個的,而是直接連接負載均衡器的地址。通過這樣的操作,使用持續(xù)交付和部署方法論的快速開發(fā)和部署周期將會成為常態(tài)。 在容器領域內,Kubernetes已毋庸置疑成為了容器編排和管理的社區(qū)標準。如果你希望你所搭建的應用程序能充分利用多云(multi-cloud)的優(yōu)勢,有一些與Kubernetes網絡相關的基本內容是你...

    劉明 評論0 收藏0
  • 應用開發(fā)者必須了解的Kubernetes網絡三事

    摘要:在容器領域內,已毋庸置疑成為了容器編排和管理的社區(qū)標準。客戶端無需連接到每個的,而是直接連接負載均衡器的地址。通過這樣的操作,使用持續(xù)交付和部署方法論的快速開發(fā)和部署周期將會成為常態(tài)。 在容器領域內,Kubernetes已毋庸置疑成為了容器編排和管理的社區(qū)標準。如果你希望你所搭建的應用程序能充分利用多云(multi-cloud)的優(yōu)勢,有一些與Kubernetes網絡相關的基本內容是你...

    HelKyle 評論0 收藏0
  • 應用開發(fā)者必須了解的Kubernetes網絡三事

    摘要:在容器領域內,已毋庸置疑成為了容器編排和管理的社區(qū)標準。客戶端無需連接到每個的,而是直接連接負載均衡器的地址。通過這樣的操作,使用持續(xù)交付和部署方法論的快速開發(fā)和部署周期將會成為常態(tài)。 在容器領域內,Kubernetes已毋庸置疑成為了容器編排和管理的社區(qū)標準。如果你希望你所搭建的應用程序能充分利用多云(multi-cloud)的優(yōu)勢,有一些與Kubernetes網絡相關的基本內容是你...

    Backache 評論0 收藏0

發(fā)表評論

0條評論

xbynet

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<