编写干净的测试–验证或不验证
在編寫(xiě)使用模擬對(duì)象的單元測(cè)試時(shí),請(qǐng)遵循以下步驟:
第三步的描述實(shí)際上有點(diǎn)誤導(dǎo),因?yàn)橥ǔN覀冏罱K會(huì)驗(yàn)證是否調(diào)用了正確的方法以及未調(diào)用模擬對(duì)象的其他方法。
每個(gè)人都知道,如果我們要編寫(xiě)無(wú)錯(cuò)誤的軟件,我們必須驗(yàn)證這兩種情況或不良情況的發(fā)生。
對(duì)?
讓我們驗(yàn)證一切
讓我們首先來(lái)看一下用于向數(shù)據(jù)庫(kù)添加新用戶帳戶的服務(wù)方法的實(shí)現(xiàn)。
此服務(wù)方法的要求是:
- 如果注冊(cè)用戶帳戶的電子郵件地址不是唯一的,我們的服務(wù)方法必須拋出異常。
- 如果注冊(cè)的用戶帳戶具有唯一的電子郵件地址,則我們的服務(wù)方法必須將新的用戶帳戶添加到數(shù)據(jù)庫(kù)中。
- 如果注冊(cè)的用戶帳戶具有唯一的電子郵件地址,并且是使用常規(guī)登錄創(chuàng)建的,則我們的服務(wù)方法必須先對(duì)用戶密碼進(jìn)行編碼,然后再將其保存到數(shù)據(jù)庫(kù)中。
- 如果注冊(cè)的用戶帳戶具有唯一的電子郵件地址,并且是使用社交登錄創(chuàng)建的,則我們的服務(wù)方法必須保存使用的社交登錄提供商。
- 通過(guò)使用社交登錄創(chuàng)建的用戶帳戶必須沒(méi)有密碼。
- 我們的服務(wù)方法必須返回創(chuàng)建的用戶帳戶的信息。
如果要了解如何指定服務(wù)方法的要求,則應(yīng)閱讀以下博客文章:
- 從上到下:Web應(yīng)用程序的TDD
- 從構(gòu)思到代碼:敏捷規(guī)范的生命周期
通過(guò)執(zhí)行以下步驟來(lái)實(shí)現(xiàn)此服務(wù)方法:
RepositoryUserService類(lèi)的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;@Service public class RepositoryUserService implements UserService {private PasswordEncoder passwordEncoder;private UserRepository repository;@Autowiredpublic RepositoryUserService(PasswordEncoder passwordEncoder, UserRepository repository) {this.passwordEncoder = passwordEncoder;this.repository = repository;}@Transactional@Overridepublic User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {if (emailExist(userAccountData.getEmail())) {throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");}String encodedPassword = encodePassword(userAccountData);User registered = User.getBuilder().email(userAccountData.getEmail()).firstName(userAccountData.getFirstName()).lastName(userAccountData.getLastName()).password(encodedPassword).signInProvider(userAccountData.getSignInProvider()).build();return repository.save(registered);}private boolean emailExist(String email) {User user = repository.findByEmail(email);if (user != null) {return true;}return false;}private String encodePassword(RegistrationForm dto) {String encodedPassword = null;if (dto.isNormalRegistration()) {encodedPassword = passwordEncoder.encode(dto.getPassword());}return encodedPassword;} }如果我們要編寫(xiě)單元測(cè)試以確保當(dāng)用戶通過(guò)使用社交登錄注冊(cè)新用戶帳戶時(shí)我們的服務(wù)方法能夠正常工作,并且我們要驗(yàn)證我們的服務(wù)方法與模擬對(duì)象之間的每一次交互,我們必須編寫(xiě)八個(gè)對(duì)其進(jìn)行單元測(cè)試。
我們必須確保:
- 當(dāng)提供重復(fù)的電子郵件地址時(shí),服務(wù)方法將檢查電子郵件地址是否唯一。
- 給定重復(fù)的電子郵件地址時(shí),將引發(fā)DuplicateEmailException 。
- 給定重復(fù)的電子郵件地址時(shí),service方法不會(huì)將新帳戶保存到數(shù)據(jù)庫(kù)中。
- 如果提供重復(fù)的電子郵件地址,我們的服務(wù)方法不會(huì)對(duì)用戶的密碼進(jìn)行編碼。
- 當(dāng)提供唯一的電子郵件地址時(shí),我們的服務(wù)方法會(huì)檢查電子郵件地址是否唯一。
- 當(dāng)給出唯一的電子郵件地址時(shí),我們的服務(wù)方法將創(chuàng)建一個(gè)包含正確信息的新User對(duì)象,并將創(chuàng)建的User對(duì)象的信息保存到數(shù)據(jù)庫(kù)中。
- 當(dāng)給出唯一的電子郵件地址時(shí),我們的服務(wù)方法將返回創(chuàng)建的用戶帳戶的信息。
- 當(dāng)指定唯一的電子郵件地址并使用社交登錄名時(shí),我們的服務(wù)方法不得設(shè)置創(chuàng)建的用戶帳戶的密碼(或?qū)ζ溥M(jìn)行編碼)。
我們的測(cè)試類(lèi)的源代碼如下所示:
import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationForm; import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationFormBuilder; import net.petrikainulainen.spring.social.signinmvc.user.model.SocialMediaService; import net.petrikainulainen.spring.social.signinmvc.user.model.User; import net.petrikainulainen.spring.social.signinmvc.user.repository.UserRepository; 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 com.googlecode.catchexception.CatchException.catchException; import static com.googlecode.catchexception.CatchException.caughtException; import static net.petrikainulainen.spring.social.signinmvc.user.model.UserAssert.assertThatUser; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.never; 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_SocialSignInAndDuplicateEmail_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(new User());catchException(registrationService).registerNewUserAccount(registration);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException() 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(new User());catchException(registrationService).registerNewUserAccount(registration);assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() 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(new User());catchException(registrationService).registerNewUserAccount(registration);verify(repository, never()).save(isA(User.class));}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_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(new User());catchException(registrationService).registerNewUserAccount(registration);verifyZeroInteractions(passwordEncoder);}@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();assertThatUser(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);assertThatUser(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);} }這些單元測(cè)試是按照本教程前面部分中給出的說(shuō)明編寫(xiě)的。
該課程有很多單元測(cè)試。 我們確定他們每個(gè)人都是真的必要嗎?
或者可能不是
一個(gè)明顯的問(wèn)題是,我們編寫(xiě)了兩個(gè)單元測(cè)試,兩個(gè)單元測(cè)試都驗(yàn)證我們的服務(wù)方法檢查了用戶提供的電子郵件地址是否唯一。 我們可以通過(guò)將這些測(cè)試合并為一個(gè)單元測(cè)試來(lái)解決此問(wèn)題。 畢竟,一項(xiàng)測(cè)試應(yīng)該使我們相信,我們的服務(wù)方法會(huì)在創(chuàng)建新用戶帳戶之前驗(yàn)證用戶提供的電子郵件地址是否唯一。
但是,如果這樣做,我們將找不到更有趣的問(wèn)題的答案。 這個(gè)問(wèn)題是:
我們是否應(yīng)該真的驗(yàn)證測(cè)試代碼和模擬對(duì)象之間的每一次交互?
幾個(gè)月前,我碰到了James Coplien撰寫(xiě)的標(biāo)題為: 為什么大多數(shù)單元測(cè)試都是浪費(fèi)的文章。 本文提出了幾點(diǎn)要點(diǎn),但其中之一非常適合這種情況。 詹姆斯·科普林(James Coplien)認(rèn)為,對(duì)于測(cè)試套件中的每個(gè)測(cè)試,我們應(yīng)該提出一個(gè)問(wèn)題:
如果該測(cè)試失敗,那么將損害哪些業(yè)務(wù)要求?
他還解釋了為什么這是一個(gè)如此重要的問(wèn)題:
在大多數(shù)情況下,答案是“我不知道”。 如果您不知道測(cè)試的價(jià)值,那么從理論上講,測(cè)試的商業(yè)價(jià)值可能為零。 測(cè)試確實(shí)要付出代價(jià):維護(hù),計(jì)算時(shí)間,管理等。 這意味著測(cè)試可能具有凈負(fù)值。 這是要?jiǎng)h除的第四類(lèi)測(cè)試。
讓我們找出使用此問(wèn)題評(píng)估單元測(cè)試時(shí)會(huì)發(fā)生什么。
彈出問(wèn)題
當(dāng)問(wèn)一個(gè)問(wèn)題時(shí):“如果該測(cè)試失敗,將危及到哪些業(yè)務(wù)需求?” 關(guān)于測(cè)試類(lèi)的每個(gè)單元測(cè)試,我們得到以下答案:
- 當(dāng)提供重復(fù)的電子郵件地址時(shí),服務(wù)方法將檢查電子郵件地址是否唯一。
- 用戶必須具有唯一的電子郵件地址。
- 給定重復(fù)的電子郵件地址時(shí),將引發(fā)DuplicateEmailException 。
- 用戶必須具有唯一的電子郵件地址。
- 給定重復(fù)的電子郵件地址時(shí),service方法不會(huì)將新帳戶保存到數(shù)據(jù)庫(kù)中。
- 用戶必須具有唯一的電子郵件地址。
- 如果提供重復(fù)的電子郵件地址,我們的服務(wù)方法不會(huì)對(duì)用戶的密碼進(jìn)行編碼。
- –
- 當(dāng)提供唯一的電子郵件地址時(shí),我們的服務(wù)方法會(huì)檢查電子郵件地址是否唯一。
- 用戶必須具有唯一的電子郵件地址。
- 給定唯一的電子郵件地址后,我們的服務(wù)方法將創(chuàng)建一個(gè)包含正確信息的新User對(duì)象,并將創(chuàng)建的User對(duì)象的信息保存到使用的數(shù)據(jù)庫(kù)中。
- 如果注冊(cè)的用戶帳戶具有唯一的電子郵件地址,則必須將其保存到數(shù)據(jù)庫(kù)中。
- 當(dāng)給出唯一的電子郵件地址時(shí),我們的服務(wù)方法將返回創(chuàng)建的用戶帳戶的信息。
- 我們的服務(wù)方法必須返回創(chuàng)建的用戶帳戶的信息。
- 當(dāng)指定唯一的電子郵件地址并使用社交登錄名時(shí),我們的服務(wù)方法不得設(shè)置創(chuàng)建的用戶帳戶的密碼(或?qū)ζ溥M(jìn)行編碼)。
- 使用社交登錄創(chuàng)建的用戶帳戶沒(méi)有密碼。
乍一看,我們的測(cè)試類(lèi)似乎只有一個(gè)沒(méi)有業(yè)務(wù)價(jià)值(或可能有負(fù)凈值)的單元測(cè)試。 此單元測(cè)試可確保當(dāng)用戶嘗試使用重復(fù)的電子郵件地址創(chuàng)建新的用戶帳戶時(shí),我們的代碼與PasswordEncoder模擬之間沒(méi)有任何交互。
很明顯,我們必須刪除此單元測(cè)試,但這不是唯一必須刪除的單元測(cè)試。
兔子洞比預(yù)期的深
早些時(shí)候我們注意到我們的測(cè)試類(lèi)包含兩個(gè)單元測(cè)試,兩個(gè)單元測(cè)試都驗(yàn)證是否調(diào)用了UserRepository接口的findByEmail()方法。 當(dāng)我們仔細(xì)查看測(cè)試的服務(wù)方法的實(shí)現(xiàn)時(shí),我們注意到:
- 當(dāng)UserRepository接口的findByEmail()方法返回User對(duì)象時(shí),我們的服務(wù)方法將引發(fā)DuplicateEmailException 。
- 當(dāng)UserRepository接口的findByEmail()方法返回null時(shí),我們的服務(wù)方法將創(chuàng)建一個(gè)新的用戶帳戶。
經(jīng)過(guò)測(cè)試的服務(wù)方法的相關(guān)部分如下所示:
public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {if (emailExist(userAccountData.getEmail())) {//If the PersonRepository returns a Person object, an exception is thrown.throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");}//If the PersonRepository returns null, the execution of this method continues. }private boolean emailExist(String email) {User user = repository.findByEmail(email);if (user != null) {return true;}return false; }我認(rèn)為我們應(yīng)該刪除這兩個(gè)單元測(cè)試,原因有二:
- 只要我們正確配置了PersonRepository模擬,我們就知道它的findByEmail()方法是通過(guò)使用正確的方法參數(shù)調(diào)用的。 盡管我們可以將這些測(cè)試用例鏈接到業(yè)務(wù)需求(用戶的電子郵件地址必須是唯一的),但是我們不需要它們來(lái)驗(yàn)證此業(yè)務(wù)需求沒(méi)有受到損害。
- 這些單元測(cè)試未記錄我們服務(wù)方法的API。 他們記錄了它的實(shí)現(xiàn)。 像這樣的測(cè)試是有害的,因?yàn)樗鼈兪刮覀兊臏y(cè)試套件變得無(wú)關(guān)緊要,并且使重構(gòu)更加困難。
如果我們不配置模擬對(duì)象,它們將返回“ nice”值。
Mockito常見(jiàn)問(wèn)題解答指出:
為了透明和不干擾,默認(rèn)情況下,所有Mockito模擬都返回“ nice”值。 例如:零,假,空集合或空。 請(qǐng)參閱有關(guān)存根的javadocs,以了解確切地返回了默認(rèn)值。
這就是為什么我們應(yīng)該始終配置相關(guān)的模擬對(duì)象的原因! 如果我們不這樣做,我們的測(cè)試可能就沒(méi)有用了。
讓我們繼續(xù)清理這個(gè)爛攤子。
清理混亂
從測(cè)試類(lèi)中刪除這些單元測(cè)試后,其源代碼如下所示:
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 com.googlecode.catchexception.CatchException.catchException; import static com.googlecode.catchexception.CatchException.caughtException; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.never; 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_SocialSignInAndDuplicateEmail_ShouldThrowException() 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(new User());catchException(registrationService).registerNewUserAccount(registration);assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() 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(new User());catchException(registrationService).registerNewUserAccount(registration);verify(repository, never()).save(isA(User.class));}@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();assertThatUser(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);assertThatUser(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);} }我們從測(cè)試班級(jí)中刪除了三個(gè)單元測(cè)試,因此,我們可以享受以下好處:
- 我們的測(cè)試班的單元測(cè)試較少 。 這似乎是一個(gè)奇怪的好處,因?yàn)橥ǔ=ㄗh我們編寫(xiě)盡可能多的單元測(cè)試。 但是,如果考慮到這一點(diǎn),則減少單元測(cè)試是有意義的,因?yàn)槲覀冃枰S護(hù)的測(cè)試較少。 這以及每個(gè)單元只能測(cè)試一件事的事實(shí)使我們的代碼更易于維護(hù)和重構(gòu)。
- 我們已經(jīng)提高了文檔的質(zhì)量 。 刪除的單元測(cè)試未記錄測(cè)試服務(wù)方法的公共API。 他們記錄了它的實(shí)施。 由于這些測(cè)試已刪除,因此更容易弄清測(cè)試服務(wù)方法的要求。
摘要
這篇博客文章教會(huì)了我們?nèi)?#xff1a;
- 如果我們無(wú)法確定在單元測(cè)試失敗的情況下受到損害的業(yè)務(wù)需求,則不應(yīng)編寫(xiě)該測(cè)試。
- 我們不應(yīng)該編寫(xiě)沒(méi)有記錄測(cè)試方法的公共API的單元測(cè)試,因?yàn)檫@些測(cè)試使我們的代碼(和測(cè)試)更加難以維護(hù)和重構(gòu)。
- 如果發(fā)現(xiàn)現(xiàn)有的單元測(cè)試違反了這兩個(gè)規(guī)則,則應(yīng)將其刪除。
在本教程中,我們?nèi)〉昧撕芏喑删汀?您認(rèn)為可以使這些單元測(cè)試變得更好嗎?
如果您想了解有關(guān)編寫(xiě)干凈測(cè)試的更多信息,請(qǐng)閱讀我的編寫(xiě)干凈測(cè)試教程的所有部分 。
翻譯自: https://www.javacodegeeks.com/2014/08/writing-clean-tests-to-verify-or-not-to-verify.html
總結(jié)
以上是生活随笔為你收集整理的编写干净的测试–验证或不验证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JavaFX技巧11:更新只读属性
- 下一篇: 一个在自己的线程中运行测试的JUnit规