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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

七大罪过与如何避免

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

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

本文旨在提供示例測試代碼,這些示例可以是:

  • 難以閱讀
  • 難以維護

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

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

1.通用測試名稱

您可能已經看到了如下命名的測試

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

現在這是非常通用的,不會通知代碼的讀者測試實際上正在測試什么。 Translator可能有多種方法,我們如何知道測試中正在使用哪種方法? 通過查看測試名稱并不清楚,這意味著我們必須查看測試本身才能看到。

我們可以做得更好,因此可以看到以下內容:

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

從上面我們可以看到,它在解釋該測試實際上在做什么方面做得更好。 此外,如果你的名字你的測試文件類似TranslatorShould你可以當你把測試文件和單個測試名稱形成在你心目中是合理的一句話: Translator should translate from number to word 。

2.測試設置中的變異

在測試中很有可能會希望將測試中使用的對象構造為處于特定狀態。 有不同的方法,下面顯示了一種這樣的方法。 在此代碼段中,我們基于該對象中包含的信息來確定某個字符是否實際上是“ 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”,此后發生的事涉及相當比例的突變。 它繼續在隨后的行中設置名稱,父母身份和職業。 當然,這忽略了與我們的朋友“達斯·維達”發生的類似事情。

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

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

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

  • 構造對象
  • 使其處于某種狀態

這是不必要的,我們可以避免。 可能有人建議,為了避免發生突變,我們可以簡單地將所有內容都移植并轉儲到構造函數中,以確保我們以給定狀態構造對象,從而避免發生突變:

@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); }

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

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

但是,從這種情況的測試中還不清楚,我們必須檢查Character的內部以了解這些參數的用途(或者您的IDE會告訴您)。

我們可以做的更好,我們可以利用Builder模式在所需狀態下構造一個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); }

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

3.斷言瘋狂

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

@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)); }

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

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

@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)); }

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

4.神奇的價值觀

您是否曾經看過數字或字符串并想知道它代表什么? 我已經過了,那些不得不解析代碼行的寶貴時間可以很快加起來。 我們在下面有這樣的代碼示例。

@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.")); }

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

  • 17是什么意思?
  • 21在構造函數中是什么意思?

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

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)); }

現在,當我們看以上內容時,我們知道:

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

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

5.難以閱讀的測試名稱

@Test void testingNumberOneAndNumberTwoCanBeAddedTogetherToProduceNumberThree() {... }

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

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

@Test void twoNumbersCanBeAdded() {... }

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

@Test void two_numbers_can_be_added() {... }

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

6.依賴注入的設置器

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

@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的模擬,因此我們可以驗證服務和存儲庫之間是否發生了正確的協作。

類似于圍繞突變的觀點,這里我們對ProductService進行突變,而不是將對象構造為所需的狀態。 可以通過將協作者注入構造函數中來避免這種情況:

@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); }

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

我們已經從

service.setRepository(repository);

ProductService service = new ProductService(repository);

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

@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()方法記錄協作者注入的情況下改變ProductService 。

7.非描述性驗證

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

@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); }

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

但是,這意味著您的測試有所不同。 我們如何努力使其更加清晰。

@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"); }

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

結束語

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

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

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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。