日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

编写干净的测试–分而治之

發(fā)布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编写干净的测试–分而治之 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

好的單元測試應(yīng)該僅出于一個原因而失敗。 這意味著適當(dāng)?shù)膯卧獪y試僅測試一個邏輯概念。

如果我們要編寫干凈的測試,則必須識別這些邏輯概念,并且每個邏輯概念僅編寫一個測試用例。

這篇博客文章描述了我們?nèi)绾巫R別從測試中發(fā)現(xiàn)的邏輯概念,以及如何將現(xiàn)有的單元測試分成多個單元測試。

干凈還不夠好

讓我們先看一下單元測試的源代碼,該源代碼確保當(dāng)使用唯一的電子郵件地址和社交登錄提供者創(chuàng)建新用戶帳戶時, RepositoryUserService類的registerNewUserAccount(RegistrationForm userAccountData)方法能夠按預(yù)期工作。

該單元測試的源代碼如下所示:

import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.springframework.security.crypto.password.PasswordEncoder;import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class) public class RepositoryUserServiceTest {private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";private static final String REGISTRATION_FIRST_NAME = "John";private static final String REGISTRATION_LAST_NAME = "Smith";private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;private RepositoryUserService registrationService;@Mockprivate PasswordEncoder passwordEncoder;@Mockprivate UserRepository repository;@Beforepublic void setUp() {registrationService = new RepositoryUserService(passwordEncoder, repository);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {@Overridepublic User answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();return (User) arguments[0];}});User createdUserAccount = registrationService.registerNewUserAccount(registration);assertThat(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);verify(repository, times(1)).save(createdUserAccount);verifyNoMoreInteractions(repository);verifyZeroInteractions(passwordEncoder);} }

這個單元測試非常干凈。 畢竟,我們的測試類,測試方法以及在測試方法內(nèi)部創(chuàng)建的局部變量具有描述性名稱。 我們還用常數(shù)替換了幻數(shù),并創(chuàng)建了特定領(lǐng)域的語言來創(chuàng)建新對象和編寫斷言。

但是, 我們可以使這項測試更好

這個單元測試的問題是它可能由于多種原因而失敗。 如果發(fā)生以下情況,它將失敗:

  • 我們的服務(wù)方法不會檢查是否從我們的數(shù)據(jù)庫中找不到輸入到注冊表中的電子郵件地址。
  • 持久化的User對象的信息與在注冊表中輸入的信息不匹配。
  • 返回的User對象的信息不正確。
  • 我們的服務(wù)方法通過使用PasswordEncoder對象為用戶創(chuàng)建密碼。
  • 換句話說,此單元測試測試了四個不同的邏輯概念,這導(dǎo)致以下問題:

    • 如果此測試失敗,我們不一定知道為什么失敗。 這意味著我們必須閱讀單元測試的源代碼。
    • 單元測試有點長,這使得閱讀起來有些困難。
    • 很難描述預(yù)期的行為。 這意味著很難為我們的測試方法找到好名字。

    通過確定該單元測試將失敗的情況,我們可以確定單個單元測試所涵蓋的邏輯概念。

    這就是為什么我們需要將此測試分為四個單元測試。

    一測試,一故障

    下一步是將單元測試分成四個新的單元測試,并確保每個單元測試都測試一個邏輯概念。 我們可以通過編寫以下單元測試來做到這一點:

  • 我們需要確保我們的服務(wù)方法檢查用戶提供的電子郵件地址是否唯一。
  • 我們需要驗證持久性User對象的信息是否正確。
  • 我們需要確保返回的User對象的信息正確。
  • 我們需要驗證我們的服務(wù)方法沒有為使用社交登錄提供商的用戶創(chuàng)建編碼密碼。
  • 編寫完這些單元測試之后,測試類的源代碼如下所示:

    import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.springframework.security.crypto.password.PasswordEncoder;import static net.petrikainulainen.spring.social.signinmvc.user.model.UserAssert.assertThat; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class) public class RepositoryUserServiceTest {private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";private static final String REGISTRATION_FIRST_NAME = "John";private static final String REGISTRATION_LAST_NAME = "Smith";private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;private RepositoryUserService registrationService;@Mockprivate PasswordEncoder passwordEncoder;@Mockprivate UserRepository repository;@Beforepublic void setUp() {registrationService = new RepositoryUserService(passwordEncoder, repository);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCheckThatEmailIsUnique() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);registrationService.registerNewUserAccount(registration);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);registrationService.registerNewUserAccount(registration);ArgumentCaptor<User> userAccountArgument = ArgumentCaptor.forClass(User.class);verify(repository, times(1)).save(userAccountArgument.capture());User createdUserAccount = userAccountArgument.getValue();assertThat(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {@Overridepublic User answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();return (User) arguments[0];}});User createdUserAccount = registrationService.registerNewUserAccount(registration);assertThat(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}@Testpublic void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);registrationService.registerNewUserAccount(registration);verifyZeroInteractions(passwordEncoder);} }

    編寫僅測試一個邏輯概念的單元測試的明顯好處是,很容易知道為什么測試失敗。 但是,此方法還有其他兩個好處:

    • 指定期望的行為很容易。 這意味著更容易為我們的測試方法找出好名字。
    • 由于這些單元測試比原始單元測試要短得多,因此更容易弄清測試方法/組件的要求。 這有助于我們將測試轉(zhuǎn)換為可執(zhí)行規(guī)范。

    讓我們繼續(xù)并總結(jié)從這篇博客文章中學(xué)到的知識。

    摘要

    現(xiàn)在,我們已經(jīng)成功地將單元測試分為四個較小的單元測試,它們測試了一個邏輯概念。 這篇博客文章教會了我們兩件事:

    • 我們了解到,通過確定測試失敗的情況,我們可以確定單個單元測試所涵蓋的邏輯概念。
    • 我們了解到,編寫僅測試一個邏輯概念的單元測試有助于我們將測試用例編寫成可執(zhí)行的規(guī)范,從而確定測試方法/組件的要求。

    翻譯自: https://www.javacodegeeks.com/2014/06/writing-clean-tests-divide-and-conquer.html

    總結(jié)

    以上是生活随笔為你收集整理的编写干净的测试–分而治之的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。