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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

开发罪过_七大罪过与如何避免

發(fā)布時間:2023/12/3 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 开发罪过_七大罪过与如何避免 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

開發(fā)罪過

在整個本文中,我將在代碼片段中使用Java,同時還將使用JUnit和Mockito 。

本文旨在提供以下測試代碼示例:

  • 難以閱讀
  • 難以維護

在這些示例之后,本文將嘗試提供替代方法,這些替代方法可用于增強測試的可讀性,從而有助于使其在將來更易于維護。

創(chuàng)建良好的示例具有挑戰(zhàn)性,因此,作為讀者,我鼓勵您將示例僅用作了解本文基本信息的工具,以力求實現(xiàn)可讀的測試代碼。

1.通用測試名稱

您可能已經(jīng)看到了如下所示的測試

@Test void testTranslator() {String word = new Translator().wordFrom(1);assertThat(word, is("one")); }

現(xiàn)在這是非常通用的,不會通知代碼的讀者該測試實際在測試什么。 Translator可能有多種方法,我們?nèi)绾沃罍y試中正在使用哪種方法? 通過查看測試名稱并不清楚,這意味著我們必須查看測試本身才能看到。

我們可以做得更好,因此可以看到以下內(nèi)容:

@Test void translate_from_number_to_word() {String word = new Translator().wordFrom(1);assertThat(word, is("one")); }

從上面的內(nèi)容可以看出,它在解釋此測試的實際作用方面做得更好。 此外,如果您將測試文件命名為TranslatorShould那么在將測試文件和單個測試名稱組合在一起時,您應該在頭腦中形成一個合理的句子: Translator should translate from number to word 。

2.測試設置中的變異

在測試中,您很有可能希望將測試中使用的對象構造為處于特定狀態(tài)。 有不同的方法,下面顯示了一種這樣的方法。 在此代碼段中,我們基于該對象中包含的信息來確定某個字符是否實際上是“ Luke Skywalker”(想象這就是isLuke()方法的作用):

@Test void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character luke = new Character();luke.setName("Luke Skywalker");Character vader = new Character();vader.setName("Darth Vader");luke.setFather(vader);luke.setProfession(PROFESSION.JEDI);boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke); }

上面的代碼構造了一個Character對象來表示“ Luke Skywalker”,此后發(fā)生的事涉及相當比例的突變。 它繼續(xù)在隨后的行中設置名稱,父母身份和職業(yè)。 當然,這忽略了與我們的朋友“達斯·維達”發(fā)生的類似事情。

這種突變水平分散了測試中正在發(fā)生的事情。 如果我們再回顧一下我先前的句子:

在測試中很有可能您希望將測試中使用的對象構造為處于特定狀態(tài)

但是,上述測試實際上發(fā)生了兩個階段:

  • 構造對象
  • 使其處于某種狀態(tài)

這是不必要的,我們可以避免。 可能有人建議,為了避免發(fā)生突變,我們可以簡單地將所有內(nèi)容都移植并轉(zhuǎn)儲到構造函數(shù)中,以確保我們以給定的狀態(tài)構造對象,避免發(fā)生突變:

@Test void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character vader = new Character("Darth Vader");Character luke = new Character("Luke Skywalker", vader, PROFESSION.JEDI);boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke); }

從上面可以看到,我們減少了代碼行的數(shù)量以及對象的變異。 但是,在此過程中,我們已經(jīng)失去了Character (現(xiàn)在為Character參數(shù))在測試中表示的含義。 為了使isLuke()方法返回true,我們傳入的Character對象必須具有以下內(nèi)容:

  • “盧克·天行者”的名字
  • 有一個父親叫“達斯·維達”
  • 成為絕地武士

但是,從這種情況的測試中尚不清楚,我們必須檢查Character的內(nèi)部以了解這些參數(shù)的用途(否則您的IDE會告訴您)。

我們可以做得更好,可以利用Builder模式在所需狀態(tài)下構造一個Character對象,同時還可以保持測試的可讀性:

@Test void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character luke = CharacterBuilder().aCharacter().withNameOf("Luke Skywalker").sonOf(new Character("Darth Vader")).employedAsA(PROFESSION.JEDI).build();boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke); }

通過上面的內(nèi)容,可能還會有幾行內(nèi)容,但是它試圖解釋測試中的重要內(nèi)容。

3.斷言瘋狂

在測試期間,您將斷言/驗證系統(tǒng)中是否發(fā)生了某些事情(通常位于每次測試結束時)。 這是測試中非常重要的一步,可能很想添加許多斷言,例如斷言返回對象的值。

@Test void successfully_upgrades_user() {UserService service = new UserService();User someBasicUser = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.BASIC).build();User upgradedUser = service.upgrade(someBasicUser);assertThat(upgradedUser.name(), is("Basic Bob"));assertThat(upgradedUser.type(), is(UserType.SUPER_USER));assertThat(upgradedUser.age(), is(23)); }

(在上面的示例中,我向構建器提供了其他信息,例如名稱和年齡,但是,如果對測試不重要,則通常不會包含此信息,請在構建器中使用明智的默認值)

如我們所見,存在三個斷言,在更極端的示例中,我們談論的是數(shù)十行斷言。 我們不一定需要執(zhí)行三個斷言,有時我們可以合而為一:

@Test void successfully_upgrades_user() {UserService service = new UserService();User someBasicUser = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.BASIC).build();User expectedUserAfterUpgrading = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.SUPER_USER).build();User upgradedUser = service.upgrade(someBasicUser);assertThat(upgradedUser, is(expectedUserAfterUpgrading)); }

現(xiàn)在,我們將升級后的用戶與我們期望對象在升級后的外觀進行比較。 為此,您將需要比較的對象( User )具有覆蓋的equals和hashCode 。

4.神奇的價值觀

您是否曾經(jīng)看過數(shù)字或字符串并想知道它代表什么? 我已經(jīng)擁有了那些不得不解析代碼行的寶貴時間,這些時間很快就會開始累加起來。 我們在下面有一個這樣的代碼示例。

@Test void denies_entry_for_someone_who_is_not_old_enough() {Person youngPerson = PersonBuilder.aPerson().withAgeOf(17).build();NightclubService service = new NightclubService(21);String decision = service.entryDecisionFor(youngPerson);assertThat(decision, is("No entry. They are not old enough.")); }

閱讀以上內(nèi)容,您可能會遇到一些問題,例如:

  • 17是什么意思?
  • 21在構造函數(shù)中是什么意思?

如果我們可以向代碼讀者表示它們的含義,那不是很好,那么他們不必考慮太多嗎? 幸運的是,我們可以:

private static final int SEVENTEEN_YEARS = 17; private static final int MINIMUM_AGE_FOR_ENTRY = 21; private static final String NO_ENTRY_MESSAGE = "No entry. They are not old enough.";@Test void denies_entry_for_someone_who_is_not_old_enough() {Person youngPerson = PersonBuilder.aPerson().withAgeOf(SEVENTEEN_YEARS).build();NightclubService service = new NightclubService(MINIMUM_AGE_FOR_ENTRY);String decision = service.entryDecisionFor(youngPerson);assertThat(decision, is(NO_ENTRY_MESSAGE)); }

現(xiàn)在,當我們看以上內(nèi)容時,我們知道:

  • SEVENTEEN_YEARS是用來表示17年的值,毫無疑問,我們已經(jīng)在讀者的腦海中留下了疑問。 不是秒或分鐘,而是年。
  • MINIMUM_AGE_FOR_ENTRY是必須允許某人進入夜總會的值。 讀者甚至不必關心此值是什么,只需了解測試上下文中的含義即可。
  • NO_ENTRY_MESSAGE是返回的值,表示不允許某人進入夜總會。 從本質(zhì)上講,字符串通常具有更好的描述性,但是請始終檢查您的代碼以找出可以改進的地方。

這里的關鍵是減少代碼閱讀器嘗試解析代碼行所花費的時間。

5.難以讀取的測試名稱

@Test void testingNumberOneAndNumberTwoCanBeAddedTogetherToProduceNumberThree() {... }

您花了多長時間閱讀以上內(nèi)容? 它易于閱讀嗎?您能快速了解一下此處正在測試的內(nèi)容嗎?還是需要解析許多字符?

幸運的是,我們可以嘗試以更好的方式命名測試,方法是將測試減少到實際測試的水平,并刪除試圖添加的華夫餅:

@Test void twoNumbersCanBeAdded() {... }

它的閱讀效果更好嗎? 我們減少了這里的單詞數(shù)量,更易于解析。 如果我們可以更進一步,問我們是否可以放棄使用駱駝箱怎么辦:

@Test void two_numbers_can_be_added() {... }

這是一個優(yōu)先事項,應該由對給定代碼庫做出貢獻的人員同意。 使用蛇形小寫字母(如上所述)可以幫助提高測試名稱的可讀性,因為您更可能打算模仿書面句子。 因此,蛇形格的使用緊隨普通書面句子中存在的物理空間。 但是,Java不允許在方法名稱中使用空格,這是我們所擁有的最好的方法,缺少使用Spock之類的東西。

6.依賴項注入的設置器

通常,對于測試,您希望能夠為給定對象(也稱為“協(xié)作對象”或簡稱為“協(xié)作者”)注入依賴關系。 為了達到這個目的,您可能已經(jīng)看到了類似以下內(nèi)容的內(nèi)容:

@Test void save_a_product() {ProductService service = new ProductService();TestableProductRepository repository = mock(TestableProductRepository.class);service.setRepository(repository);Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct); }

上面使用了setter方法,即setRepository() ,以便注入TestableProductRepository的模擬,因此我們可以驗證服務和存儲庫之間是否發(fā)生了正確的協(xié)作。

與圍繞突變的點類似,這里我們對ProductService進行突變,而不是將其構造為所需狀態(tài)。 可以通過將協(xié)作者注入構造函數(shù)中來避免這種情況:

@Test void save_a_product() {TestableProductRepository repository = mock(TestableProductRepository.class);ProductService service = new ProductService(repository);Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct); }

因此,現(xiàn)在我們將協(xié)作者注入了構造函數(shù)中,現(xiàn)在我們在構造時就知道對象將處于什么狀態(tài)。但是,您可能會問“在此過程中我們是否沒有丟失某些上下文?”。

我們已經(jīng)從

service.setRepository(repository);

ProductService service = new ProductService(repository);

前者更具描述性。 因此,如果您不喜歡這種上下文丟失的情況,則可以選擇類似構建器的內(nèi)容,而創(chuàng)建以下內(nèi)容:

@Test void save_a_product() {TestableProductRepository repository = mock(TestableProductRepository.class);ProductService service = ProductServiceBuilder.aProductService().withRepository(repository).build();Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct); }

該解決方案使我們能夠避免在使用withRepository()方法記錄協(xié)作者注入的情況下改變ProductService 。

7.非描述性驗證

如前所述,您的測試通常會包含驗證語句。 不用自己動手,您通常會利用庫來執(zhí)行此操作。 但是,您必須注意不要掩蓋驗證的意圖。 要了解我在說什么,請看以下示例。

@Test void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verifyZeroInteractions(component); }

現(xiàn)在,如果您看上面的內(nèi)容,您是否立即知道該斷言表明沒有錯誤顯示給用戶? 可能是因為它是測試的名稱,但是您可能不將該代碼行與測試名稱相關聯(lián) 。 這是因為它是Mockito的代碼,并且通用以適應許多不同的用例。 它按照它說的做,檢查與UIComponent的模擬是否沒有交互。

但是,這意味著您的測試有所不同。 我們?nèi)绾卧O法使其更加清晰。

@Test void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verify(component, times(0)).addErrorMessage("Invalid user"); }

這樣會更好一些,因為此代碼的讀者有很大的潛力可以快速了解此行的工作。 但是,在某些情況下,可能仍然很難閱讀。 在這種情況下,請按照以下說明提取一種方法,以更好地解釋您的驗證。

@Test void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verifyNoErrorMessageIsAddedTo(component); }private void verifyNoErrorMessageIsAddedTo(UIComponent component) {verify(component, times(0)).addErrorMessage("Invalid user"); }

上面的代碼并不完美,但是在當前測試的范圍內(nèi),它肯定可以提供我們正在驗證的內(nèi)容的高級概述。

結束語

我希望您喜歡這篇文章,并且下次您完成編寫測試時將花費一兩個重構步驟。 在下一次之前,我給你以下報價:

“必須編寫程序供人們閱讀,并且只能偶然地使機器執(zhí)行。” ― Harold Abelson,計算機程序的結構和解釋

翻譯自: https://www.javacodegeeks.com/2019/08/seven-testing-sins-and-how-to-avoid-them.html

開發(fā)罪過

總結

以上是生活随笔為你收集整理的开发罪过_七大罪过与如何避免的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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