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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

编写干净的测试–天堂中的麻烦

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

如果我們的代碼有明顯的錯(cuò)誤,我們很有動(dòng)力進(jìn)行改進(jìn)。 但是,在某些時(shí)候,我們認(rèn)為我們的代碼“足夠好”并繼續(xù)前進(jìn)。

通常,當(dāng)我們認(rèn)為改進(jìn)現(xiàn)有代碼的好處小于所需的工作時(shí),就會(huì)發(fā)生這種情況。 當(dāng)然,如果我們低估了投資的回報(bào),我們可能會(huì)打錯(cuò)電話,這會(huì)傷害我們。

這就是發(fā)生在我身上的事情,我決定寫這篇文章,以便您避免犯同樣的錯(cuò)誤。

編寫“良好”單元測(cè)試

如果我們要編寫“好的”單元測(cè)試,則必須編寫以下單元測(cè)試:

  • 只測(cè)試一件事 。 好的單元測(cè)試只能因一個(gè)原因而失敗,并且只能斷言一件事。
  • 被正確命名 。 測(cè)試方法的名稱必須顯示測(cè)試失敗的原因。
  • 模擬外部依賴關(guān)系(和狀態(tài)) 。 如果單元測(cè)試失敗,我們將確切知道問題出在哪里。

補(bǔ)充閱讀:

  • 單元測(cè)試只能測(cè)試一件事情
  • 編寫干凈的測(cè)試:命名問題
  • 編寫干凈的測(cè)試:分而治之
  • 編寫干凈的測(cè)試:驗(yàn)證或不驗(yàn)證

如果我們編寫滿足這些條件的單元測(cè)試,我們將編寫好的單元測(cè)試。 對(duì)?

我曾經(jīng)這樣認(rèn)為。 現(xiàn)在我對(duì)此表示懷疑

善意鋪平地獄之路

我從未見過決定編寫糟糕的單元測(cè)試的軟件開發(fā)人員。 如果開發(fā)人員正在編寫單元測(cè)試,則他/她很有可能要編寫好的單元測(cè)試。 但是,這并不意味著該開發(fā)人員編寫的單元測(cè)試是好的。

我想編寫既易于閱讀又易于維護(hù)的單元測(cè)試。 我什至寫了一個(gè)教程,描述了如何編寫干凈的測(cè)試 。 問題在于,本教程中給出的建議還不夠好(尚未)。 它可以幫助我們?nèi)腴T,但是并沒有顯示出兔子洞的真正深度。

我的教程中描述的方法存在兩個(gè)主要問題:

命名標(biāo)準(zhǔn)是FTW嗎?

如果我們使用Roy Osherove引入的“命名標(biāo)準(zhǔn)”,則會(huì)注意到很難描述被測(cè)狀態(tài)和預(yù)期行為。

當(dāng)我們?yōu)楹唵螆鼍熬帉憸y(cè)試時(shí),此命名標(biāo)準(zhǔn)非常有效。 問題在于,真正的軟件并不簡單。 通常,我們最終使用以下兩個(gè)選項(xiàng)之一來命名測(cè)試方法:

首先 ,如果我們嘗試盡可能具體,則測(cè)試方法的方法名稱會(huì)變得太過糟糕。 最后,我們必須承認(rèn)我們不能像我們想要的那樣具體,因?yàn)榉椒Q會(huì)占用太多空間。

其次 ,如果我們嘗試使方法名稱盡可能短,則方法名稱將不會(huì)真正描述測(cè)試狀態(tài)和預(yù)期行為。

選擇哪個(gè)選項(xiàng)實(shí)際上并不重要,因?yàn)闊o論如何我們都會(huì)遇到以下問題:

  • 如果測(cè)試失敗,則方法名稱不一定描述要出錯(cuò)的方法。 我們可以使用自定義斷言來解決此問題,但是它們不是免費(fèi)的。
  • 很難對(duì)我們的測(cè)試涵蓋的場景進(jìn)行簡要概述。

以下是我們?cè)凇?編寫干凈測(cè)試”教程中編寫的測(cè)試方法的名稱:

  • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException()
  • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount()
  • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider()
  • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount()
  • registerNewUserAccount_SocialSignInAnquequeEmail_ShouldNotCreateEncodedPasswordForUser()

這些方法的名稱不是很長,但是我們必須記住,編寫這些單元測(cè)試是為了測(cè)試一種簡單的注冊(cè)方法。 當(dāng)我使用這種命名約定為現(xiàn)實(shí)生活中的軟件項(xiàng)目編寫自動(dòng)化測(cè)試時(shí),最長的方法名稱是我們最長的示例名稱的兩倍。

那不是很干凈或可讀。 我們可以做得更好

沒有通用配置

在本教程中,我們使單元測(cè)試變得更好了 。 盡管如此,他們?nèi)匀辉馐苓@樣的事實(shí),即沒有“自然的”方式在不同的單元測(cè)試之間共享配置。

這意味著我們的單元測(cè)試包含許多重復(fù)的代碼,這些代碼配置了我們的模擬對(duì)象并創(chuàng)建了在單元測(cè)試中使用的其他對(duì)象。

另外,由于沒有“自然”的方式表明某些常量僅與特定的測(cè)試方法相關(guān),因此我們必須將所有常量添加到測(cè)試類的開頭。

我們的測(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);} }

一些開發(fā)人員認(rèn)為看起來像上面示例的單元測(cè)試足夠干凈。 我理解這種情緒,因?yàn)槲以?jīng)是其中之一。 但是,這些單元測(cè)試存在三個(gè)問題:

  • 該案的實(shí)質(zhì)并沒有那么清楚 。 因?yàn)槊糠N測(cè)試方法在調(diào)用被測(cè)試方法并驗(yàn)證預(yù)期結(jié)果之前都會(huì)進(jìn)行自我配置,所以我們的測(cè)試方法變得比必要的更長。 這意味著我們不能只看一眼隨機(jī)測(cè)試方法并弄清楚它要測(cè)試什么。
  • 編寫新的單元測(cè)試很慢 。 因?yàn)槊總€(gè)單元測(cè)試都必須自行配置,所以向我們的測(cè)試套件中添加新的單元測(cè)試比它可能要慢得多。 另一個(gè)“意外”的缺點(diǎn)是,這種單元測(cè)試鼓勵(lì)人們練習(xí)復(fù)制和粘貼編程 。
  • 維持這些單元測(cè)試是一件痛苦的事情 。 如果我們向注冊(cè)表單添加新的必填字段,或者更改registerNewUserAccount()方法的實(shí)現(xiàn),則必須對(duì)每個(gè)單元測(cè)試進(jìn)行更改。 這些單元測(cè)試太脆弱了。
  • 換句話說,這些單元測(cè)試很難閱讀,很難編寫和維護(hù)。 我們必須做得更好

    摘要

    這篇博客文章教會(huì)了我們四件事:

    • 即使我們認(rèn)為我們正在編寫好的單元測(cè)試,也不一定是正確的。
    • 如果由于必須更改許多單元測(cè)試而導(dǎo)致更改現(xiàn)有功能的速度很慢,那么我們就不會(huì)編寫好的單元測(cè)試。
    • 如果添加新功能的速度很慢,因?yàn)槲覀儽仨毾騿卧獪y(cè)試中添加大量重復(fù)的代碼,那么我們就不會(huì)編寫好的單元測(cè)試。
    • 如果我們看不到單元測(cè)試所涵蓋的情況,那么我們就沒有編寫好的單元測(cè)試。

    本教程的下一部分將回答這個(gè)非常相關(guān)的問題:

    如果現(xiàn)有的單元測(cè)試很爛,我們?cè)撊绾谓鉀Q?

    如果要編寫干凈的測(cè)試,則應(yīng)閱讀我的“ 編寫干凈的測(cè)試”教程 。

    翻譯自: https://www.javacodegeeks.com/2015/03/writing-clean-tests-trouble-in-paradise.html

    總結(jié)

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

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