怎样编写测试类测试分支_编写干净的测试–天堂中的麻烦
怎樣編寫測試類測試分支
如果我們的代碼有明顯的錯誤,我們很有動力對其進行改進。 但是,在某些時候,我們認為我們的代碼“足夠好”并繼續(xù)前進。
通常,當我們認為改進現(xiàn)有代碼的好處小于所需的工作時,就會發(fā)生這種情況。 當然,如果我們低估了投資回報,我們可能會打錯電話,這會傷害我們。
這就是發(fā)生在我身上的事情,因此我決定寫這篇文章,以便您避免犯同樣的錯誤。
編寫“良好”單元測試
如果我們要編寫“好的”單元測試,則必須編寫以下單元測試:
- 只測試一件事 。 好的單元測試只能因一個原因而失敗,并且只能斷言一件事。
- 被正確命名 。 測試方法的名稱必須揭示測試失敗的原因。
- 模擬外部依賴關系(和狀態(tài)) 。 如果單元測試失敗,我們將確切知道問題出在哪里。
補充閱讀:
- 單元測試只能測試一件事情
- 編寫干凈的測試:命名問題
- 編寫干凈的測試:分而治之
- 編寫干凈的測試:驗證或不驗證
如果我們編寫滿足這些條件的單元測試,我們將編寫好的單元測試。 對?
我曾經(jīng)這樣認為。 現(xiàn)在我對此表示懷疑 。
善意鋪平地獄之路
我從未見過一個決定編寫糟糕的單元測試的軟件開發(fā)人員。 如果開發(fā)人員正在編寫單元測試,則他/她很有可能要編寫好的單元測試。 但是,這并不意味著該開發(fā)人員編寫的單元測試是好的。
我想編寫既易于閱讀又易于維護的單元測試。 我什至寫了一個教程,描述了如何編寫干凈的測試 。 問題在于,本教程中給出的建議還不夠好(尚未)。 它可以幫助我們?nèi)腴T,但是并沒有顯示出兔子洞的真正深度。
我的教程中描述的方法存在兩個主要問題:
命名標準FTW?
如果我們使用Roy Osherove引入的“命名標準” ,我們注意到很難描述被測狀態(tài)和預期行為。
當我們?yōu)楹唵螆鼍熬帉憸y試時,此命名標準非常有效。 問題在于,真正的軟件并不簡單。 通常,我們最終使用以下兩個選項之一來命名測試方法:
首先 ,如果我們嘗試盡可能具體,那么我們的測試方法的方法名稱就顯得太過looooooooong。 最后,我們必須承認我們不能像我們想要的那樣具體,因為方法名稱會占用太多空間。
其次 ,如果我們嘗試使方法名稱盡可能短,則方法名稱將不會真正描述測試狀態(tài)和預期行為。
選擇哪個選項并不重要,因為無論如何我們都會遇到以下問題:
- 如果測試失敗,則方法名稱不一定表示要出錯。 我們可以使用自定義斷言來解決此問題,但是它們不是免費的。
- 很難對我們的測試涵蓋的場景進行簡要概述。
這是我們在“ 編寫干凈測試”教程期間編寫的測試方法的名稱:
- registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException()
- registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount()
- registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider()
- registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount()
- registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser()
這些方法的名稱不是很長,但是我們必須記住,編寫這些單元測試是為了測試一種簡單的注冊方法。 當我使用這種命名約定為現(xiàn)實生活中的軟件項目編寫自動化測試時,最長的方法名稱是我們最長的示例名稱的兩倍。
那不是很干凈或可讀。 我們可以做得更好 。
沒有通用配置
在本教程中,我們使單元測試變得更好了 。 然而,他們?nèi)匀辉馐苓@樣的事實,即沒有“自然的”方式在不同的單元測試之間共享配置。
這意味著我們的單元測試包含許多重復的代碼,這些代碼配置了我們的模擬對象并創(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 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);} }一些開發(fā)人員認為看起來像上面示例的單元測試足夠干凈。 我理解這種情緒,因為我曾經(jīng)是其中之一。 但是,這些單元測試有三個問題:
換句話說,這些單元測試很難閱讀,很難編寫和維護。 我們必須做得更好 。
摘要
這篇博客文章教會了我們四件事:
- 即使我們認為我們正在編寫好的單元測試,也不一定是正確的。
- 如果由于必須更改許多單元測試而導致更改現(xiàn)有功能的速度很慢,那么我們就不會編寫好的單元測試。
- 如果添加新功能的速度很慢,因為我們必須向單元測試中添加大量重復的代碼,那么我們就不會編寫好的單元測試。
- 如果我們看不到單元測試所涵蓋的情況,那么我們就沒有編寫好的單元測試。
本教程的下一部分將回答這個非常相關的問題:
如果我們現(xiàn)有的單元測試很爛,我們該如何解決呢?
如果要編寫干凈的測試,則應閱讀我的“ 編寫干凈的測試”教程 。
翻譯自: https://www.javacodegeeks.com/2015/03/writing-clean-tests-trouble-in-paradise.html
怎樣編寫測試類測試分支
總結
以上是生活随笔為你收集整理的怎样编写测试类测试分支_编写干净的测试–天堂中的麻烦的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何处理ddos攻击(怎么处理ddos攻
- 下一篇: DDOS攻击检测(内网ddos攻击检测)