測試驅動陷阱,第2部分

單元測試中單元的故事

在本文的上半部分 ,您可能會看到一些不好但很流行的測試示例。 但是我不是一個專業評論家(也被稱為“巨魔”或“仇恨者”),沒有任何建設性的話就抱怨。 多年的TDD教給我的不僅僅是事情會變得多么糟糕。 有許多簡單但有效的技巧,可以使您的測試生活變得更加輕松。

想象一下:您有一家小公司中一間小型會議室的預訂系統。 由于某些奇怪的原因,它必須處理離線預訂。 人們將他們的預訂請求發布到某個前端,每周一次,您會獲得一個文本文件,其中包含公司的工作時間以及所有的預訂(在當天,多長時間,由誰,在什么時間點提交)訂購。 您的系統應根據一些業務規則(先到先得,僅在辦公室工作時間內提供此類服務)為房間生成日歷。

作為分析的一部分,我們提供了明確定義的輸入數據和預期結果,并帶有示例。 確實,TDD的情況很好。 可悲的是,在現實生活中從來沒有發生過這樣的事情。

我們的示例測試數據如下所示:

class TestData {static final String INPUT_FIRST_LINE = '0900 1730\n';static final String FIRST_BOOKING    = '2011-03-17 10:17:06 EMP001\n' +'2011-03-21 09:00 2\n';static final String SECOND_BOOKING   = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 09:00 2\n';static final String THIRD_BOOKING    = '2011-03-16 09:28:23 EMP003\n' +'2011-03-22 14:00 2\n';static final String FOURTH_BOOKING   = '2011-03-17 10:17:06 EMP004\n' +'2011-03-22 16:00 1\n';static final String FIFTH_BOOKING    = '2011-03-15 17:29:12 EMP005\n' +'2011-03-21 16:00 3';static final String INPUT_BOOKING_LINES =FIRST_BOOKING +SECOND_BOOKING +THIRD_BOOKING +FOURTH_BOOKING +FIFTH_BOOKING;static final String CORRECT_INPUT = INPUT_FIRST_LINE + INPUT_BOOKING_LINES;static final String CORRECT_OUTPUT = '2011-03-21\n' +'09:00 11:00 EMP002\n' +'2011-03-22\n' +'14:00 16:00 EMP003\n' +'16:00 17:00 EMP004\n' +'';
}

現在,我們從一個積極的測試開始:

BookingCalendarGenerator bookingCalendarGenerator =  new BookingCalendarGenerator();@Test
public void shouldPrepareBookingCalendar() {//whenString calendar = bookingCalendarGenerator.generate(TestData.CORRECT_INPUT);//thenassertEquals(TestData.CORRECT_OUTPUT, calendar);
}

看起來我們已經使用“生成”方法設計了BookingCalendarGenerator。 很公平。 讓我們添加更多測試。 測試業務規則。 我們得到這樣的東西:

@Testpublic void noPartOfMeetingMayFallOutsideOfficeHours() {//givenString tooEarlyBooking = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 06:00 2\n';String tooLateBooking = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 20:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + tooEarlyBooking + tooLateBooking);//thenassertTrue(calendar.isEmpty());}@Testpublic void meetingsMayNotOverlap() {//givenString firstMeeting = '2011-03-10 12:34:56 EMP002\n' +'2011-03-21 16:00 1\n';String secondMeeting = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 15:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + firstMeeting + secondMeeting);//thenassertEquals('2011-03-21\n' +'16:00 17:00 EMP002\n', calendar);}@Testpublic void bookingsMustBeProcessedInSubmitOrder() {//givenString firstMeeting = '2011-03-17 12:34:56 EMP002\n' +'2011-03-21 16:00 1\n';String secondMeeting = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 15:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + firstMeeting + secondMeeting);//thenassertEquals('2011-03-21\n15:00 17:00 EMP002\n', calendar);}@Testpublic void orderingOfBookingSubmissionShouldNotAffectOutcome() {//givenList<String> shuffledBookings = newArrayList(TestData.FIRST_BOOKING, TestData.SECOND_BOOKING,TestData.THIRD_BOOKING, TestData.FOURTH_BOOKING, TestData.FIFTH_BOOKING);shuffle(shuffledBookings);String inputBookingLines = Joiner.on('\n').join(shuffledBookings);//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + inputBookingLines);//thenassertEquals(TestData.CORRECT_OUTPUT, calendar);}

僅此而已。 但是,如果我們得到一些垃圾作為輸入怎么辦。 還是如果我們得到一個空字符串? 讓我們為此設計:

@Test(expected = IllegalArgumentException.class)public void rubbishInputDataShouldEndWithException() {//whenString calendar = bookingCalendarGenerator.generate('rubbish');//then exception is thrown}@Test(expected = IllegalArgumentException.class)public void emptyInputDataShouldEndWithException() {//whenString calendar = bookingCalendarGenerator.generate('');//then exception is thrown}

IllegalArgumentException很公平。 我們不需要再花哨的方式處理它。 我們已經完成了。 最后,讓我們在測試下編寫該類:BookingCalendarGenerator。

因此,我們做到了。 結果表明,對于一個方法來說,整個過程有點大。 因此,我們使用了“提取方法”模式的強大功能。 我們將代碼片段分為不同的方法。 我們將進行操作的方法和數據分組為類。 我們使用面向對象編程的功能,使用單一職責原理,使用合成(確切地說是分解),最后得到一個像這樣的包:

我們有一個公共類,和幾個包作用域類。 這些包范圍類顯然屬于公共類。 這是一個清晰的類圖:

那些不是愚蠢的數據對象。 這些是成熟的課程。 具有行為,責任感和封裝感。 這是我們測試驅動者可能想到的一件事:我們沒有針對這些類的測試。 我們只有公共班級。 不好,對吧? 沒有測試一定是不好的。 很壞。 對?

錯誤。

我們有測試。 我們啟動了代碼覆蓋率工具,然后看到:100%方法和類。 95%的線。 不錯(在下一篇文章中,我將達到不確定性的5%)。

但是我們只有一個單元測試類。 這樣好嗎

好吧,讓我強調一下,指出答案:

這是一個UNIT測試。 有理由將其稱為UNIT測試!

該單元不必是一個單一的類。 該單元不必是單個包裝。 由您決定單位。 這是一個通用名稱,因為您的理智和常識應該告訴您停止的地方。

因此,我們有六個班級作為一個單元,有什么大不了的? 除了其余部分,有人是否要使用其中一個類呢? 他不會對此進行測試,對嗎?

錯誤。 除了在測試中實際調用的類之外??,這些類都是包范圍的。 這個包裹范圍的事情告訴您:“退后一步。 不要碰我,我屬于這個包裹。 不要試圖單獨使用我,我是設計在這里!”。

因此,是的,如果程序員將其中之一刪除或公開,則他可能會知道,所有保證都將失效。 寫你自己的測試,伙計。

我被問到是否有人要向其中一個類添加某些行為呢? 他怎么會知道他沒有破壞什么?

好吧,他將從測試開始,對嗎? 是TDD,對不對? 如果您有需求變更,則可以將此變更編碼為測試,然后,直到那時,您才開始弄亂代碼。 因此,您是安全的。

我看到人們盲目地寫每堂課的測試,卻沒有思考,這讓我哭了。 我最近做了很多配對編程,您知道我發現了什么嗎? Java程序員通常不使用package-scope。 Java程序員通常不知道,受保護的意思是:對我來說,我的所有后代和每個人都在同一軟件包中。 沒錯,受保護不僅僅是包范圍,更不是一點點。 因此,如果Java程序員不知道包范圍實際上是什么,并且與Groovy相反,那是默認的,那么他們如何理解單元是什么?

我能得到多高?

現在,有一個有趣的想法:如果我們可以對一個包進行單個測試,那么我們可以對一個包樹進行單個測試。 你知道,像這樣:

我們都知道Java包不是真正的樹狀包,唯一具有目錄結構的是按照非常古老的約定,而且我們知道目錄結構僅用于解決名稱沖突問題,盡管如此,我們還是傾向于使用包,就像name.after.the.dot有意義一樣。 就像我們可以將一個包裹隱藏在另一個包裹中一樣。 或與他們建立千層面的烤寬面條。

那么可以為一個樹形樹使用一個測試類嗎?

是的。

但是,如果是這樣,到哪里結束? 我們可以從包樹一直到應用程序的入口點嗎? 那些……也許是集成測試或功能測試。 我們能做到嗎? 那會好嗎?

答案是:可以。 在一個完美的世界中,那會很好。 在我們那骯臟的,懸掛在刀刃上的世界里,那簡直是瘋了。 為什么? 因為功能性的端到端測試很慢。 太慢了。 太慢了,以至于您想將它們扔掉,然后到某個地方,而不必總是等待某些東西。 一個充滿創造力,持續反饋和閃電般快速安全的地方。

您將返回單元測試。

甚至還有更多原因。 一種是,很難對應用程序的所有流進行端到端測試。 您可能應該對所有主要流程都執行此操作,但是對于錯誤,連接不良以及所有可能在某一點或另一點拋出的棘手邏輯部分呢? 不,有時候像這樣為集成測試設置環境會太困難了,所以最終還是要用單元測試來測試它。

第二個原因是,盡管功能測試并未在代碼上澆注具體的內容,但不會通過在測試用例中重復執行算法來抑制您的創造力,但是它們也沒有提供重構的安全性。 當您擁有一個帶有單個公共類的程序包時,很明顯有人可以安全地做什么,而他不能做什么。 當您將某些內容包含在庫或插件中時,它仍然很明顯。 但是,如果您有成千上萬個公共類,并且要實現一個新功能,則可能要使用其中一些,并且您想知道它們很好。

因此,不,在我們的世界中,僅進行功能測試是沒有意義的。 抱歉。 但是為每個類創建測試也沒有意義。 出于某種原因,它稱為UNIT測試。 用那個

祝您編程愉快,別忘了分享!

參考:“ 測試驅動陷阱”,來自我們JCG合作伙伴 Jakub Nabrdalik的第2部分 ,在Solid Craft博客上。


翻譯自: https://www.javacodegeeks.com/2012/09/test-driven-traps-part-2.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/371163.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/371163.shtml
英文地址,請注明出處:http://en.pswp.cn/news/371163.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java 代碼 設置環境變量_Java 配置環境變量教程

【聲明】歡迎轉載&#xff0c;但請保留文章原始出處→_→【正文】1、安裝JDK開發環境開始安裝JDK&#xff1a;修改安裝目錄如下&#xff1a;確定之后&#xff0c;單擊“下一步”。注&#xff1a;當提示安裝JRE時&#xff0c;可以選擇不要安裝。2、配置環境變量&#xff1a;對于…

組合數據類型練習,英文詞頻統計實例上(2017.9.22)

字典實例&#xff1a;建立學生學號成績字典&#xff0c;做增刪改查遍歷操作。 sno[33號,34號,35號,36號] grade[100,90,80,120] d{33號:100,34號:90,35號:80,36號:120} print(d) print(每個學號對應分數:,d.items()) print(彈出35號的分數:,d.pop(35號)) print(獲取學號:,d.key…

java 代碼中設置 臨時 環境變量

System.setProperty("hadoop.home.dir", "D:\\software\\software_install\\dev_install\\hadoop-2.4.1"); 轉載于:https://www.cnblogs.com/zychengzhiit1/p/6662376.html

什么是快速開發框架

什么是快速開發框架 前言 做為一個程序員&#xff0c;在開發的過程中會發現&#xff0c;有框架同無框架&#xff0c;做起事來是完全不同的概念&#xff0c;關系到開發的效率、程序的健壯、性能、團隊協作、后續功能維護、擴展......等方方面面的事情。很多朋友在學習搭建自己…

java中的math.abs_Java.math.BigDecimal.abs()方法

全屏Java.math.BigDecimal.abs()方法java.math.BigDecimal.abs()返回一個BigDecimal&#xff0c;其值是此BigDecimal的絕對值&#xff0c;其標度是this.scale()。聲明以下是java.math.BigDecimal.abs()方法的聲明public BigDecimal abs()參數NA返回值此方法返回的名為value&…

我需要多少內存

什么是保留堆&#xff1f; 我需要多少內存&#xff1f; 在構建解決方案&#xff0c;創建數據結構或選擇算法時&#xff0c;您可能會問自己&#xff08;或其他人&#xff09;這個問題。 如果此圖包含1,000,000條邊并且我使用HashMap進行存儲&#xff0c;此圖是否適合我的3G堆&am…

C語言程序設計預報作業

1閱讀鄒欣老師的博客--師生關系,針對文中的幾種師生關系談談你的看法&#xff0c;你期望的師生關系是什么樣的&#xff1f; 答&#xff1a;我認為文中的師生關系都存在一些缺陷&#xff0c;第一種師生關系是建立在病態關系上的&#xff0c;學生不是植物自然有自己的思想。所以我…

淺談23種設計模式

淺談23種設計模式 類之間的關聯關系&#xff1a;在使用Java、C#和C等編程語言實現關聯關系時&#xff0c;通常將一個類作為另一個類的屬性。   (1)雙向關聯&#xff0c;兩個類互相為各自的屬性&#xff0c;比如顧客類Customer和商品類Product&#xff0c;顧客擁有商品&#x…

網頁布局基礎

1、盒子模型的第一層到第五層&#xff1a; border、padding content、background-image、background-color、margin 2、清除浮動。對受到浮動影響的標簽作以下操作&#xff1a; 1、clear: both; 2、clear: right; clear: left; 3、設置寬度width: 100%(或者固定寬度) overflow…

mysql與串口通信_虛擬機串口與主機串口通信·小程序(下)

上次說到的&#xff0c;不能做到實時通信。那么開兩個進程就可以了&#xff0c;一個用來監聽是否有消息傳來&#xff0c;一個用來等待用戶輸入。那么&#xff0c;先來復習一下進程的相關概念。進程結構linux中進程包含PCB(進程控制塊)、程序以及程序所操縱的數據結構集&#xf…

淺談我所見的CSS命名風格

在兩年工作中&#xff0c;總結一下我所見的css命名風格。 1.單一class命名 .header {width: 500px; } .item {text-indent: 20%; } 優點&#xff1a;簡單&#xff0c;渲染效率高。 缺點&#xff1a;零散&#xff0c;沒有模塊化。 2. 后代選擇器class命名 .header .item a {font…

Java規范請求中的數字

你們都了解Java社區流程 &#xff08;JCP&#xff09;&#xff0c;不是嗎&#xff1f; JCP是為Java技術開發標準技術規范的機制。 任何人都可以注冊該站點并參與對Java規范請求&#xff08;JSR&#xff09;的審查和提供反饋&#xff0c;并且任何人都可以注冊成為JCP成員&#x…

ORACLE MOS 翻譯

http://blog.csdn.net/msdnchina/article/details/53174196轉載于:https://www.cnblogs.com/zengkefu/p/6665950.html

自從我這樣擼代碼以后,公司網頁的瀏覽量提高了107%!

歡迎大家前往騰訊云 社區&#xff0c;獲取更多騰訊海量技術實踐干貨哦~ 本文由騰訊IVWEB團隊發表于云 社區專欄 作者&#xff1a;yangchunwen HTTP協議是前端性能乃至安全中一個非常重要的話題&#xff0c;最近在看《web性能權威指南(High Performance Browser Networking)》&a…

python數列分段_按范圍分段的Python數組

首先&#xff0c;定義你的“極”數第二&#xff0c;根據這些“極”數生成間隔第三&#xff0c;定義盡可能多的列表。在然后&#xff0c;對于每個間隔&#xff0c;掃描列表并在相關列表中添加屬于該間隔的項代碼&#xff1a;source [1, 4, 7, 9, 2, 10, 5, 8]poles (0,3,6,25)…

51nod 1278 相離的圓

基準時間限制&#xff1a;1 秒 空間限制&#xff1a;131072 KB 分值: 10 難度&#xff1a;2級算法題 平面上有N個圓&#xff0c;他們的圓心都在X軸上&#xff0c;給出所有圓的圓心和半徑&#xff0c;求有多少對圓是相離的。例如&#xff1a;4個圓分別位于1, 2, 3, 4的位置&…

讓我們將包變成模塊系統!

使用構建系統將許多項目分為模塊/子項目&#xff08; Maven &#xff0c; Gradle &#xff0c; SBT …&#xff09;&#xff1b; 編寫模塊化代碼通常是一件好事。 將代碼分為構建模塊主要用于&#xff1a; 隔離代碼部分&#xff08;減少耦合&#xff09; api / impl拆分 僅將…

R語言日期的表示和運算(詳細總結)

1、取出當前日期 Sys.Date() [1] "2014-10-29" date() #注意&#xff1a;這種方法返回的是字符串類型 [1] "Wed Oct 29 20:36:07 2014" 2、在R中日期實際是double類型&#xff0c;是從1970年1月1日以來的天數 typeof(Sys.Date()) [1] "double" …

html高度塌陷問題解決

高度塌陷的問題&#xff1a; 當開啟元素的BFC以后&#xff0c;元素將會有如下的特性 1 父元素的垂直外邊距不會和子元素重疊 開啟BFC的元素不會被浮動元素所覆蓋 開啟BFC的元素可以包含浮動的子元素 如何開啟元素的BFC 設置元素浮動 設置元素絕對定位 …

java空格鍵_Java KeyPressed-如果其他鍵也太舊,則無法檢測是否按下了空格鍵

如標題所示&#xff0c;在我的Java游戲中&#xff0c;無法檢測是否同時按下空格鍵和其他鍵。例如&#xff0c;空格鍵是射擊鍵&#xff0c;而箭頭鍵則使玩家移動。如果我按下向上箭頭鍵&#xff0c;向左箭頭鍵和空格鍵&#xff0c;那么它應該向左上方發射子彈。但是&#xff0c;…