說(shuō)了那么多,讓我們用示例看看,系統(tǒng)重構(gòu)是應(yīng)該怎樣做自動(dòng)化測(cè)試的。還是回到前面那個(gè)HelloWorld的例子(詳見(jiàn) 3.3 小步快跑是這樣玩的),該類(lèi)中有一個(gè)sayHello()方法,只要我們輸入當(dāng)前的時(shí)間與用戶(hù)名,就返回對(duì)該用戶(hù)的問(wèn)候語(yǔ)。如果當(dāng)前時(shí)間是上午,則返回“Hi, XXX. Good morning!”;如果是下午,則返回“Hi, XXX. Good afternoon!”;如果是晚上,則返回“Hi, XXX. Good Night!”,這是HelloWorld這個(gè)程序?qū)崿F(xiàn)的功能。
然后我們開(kāi)始為這段程序編寫(xiě)測(cè)試代碼(如果采用測(cè)試驅(qū)動(dòng)開(kāi)發(fā),應(yīng)當(dāng)先寫(xiě)測(cè)試代碼再寫(xiě)程序)。我們首先建立一個(gè)test源程序目錄,然后建立與被測(cè)程序?qū)?yīng)的包和測(cè)試程序。這就是說(shuō),如果被測(cè)程序在“org.refactoring.helloWorld.resource”包中,則測(cè)試程序應(yīng)當(dāng)建立“test.org.refactoring.helloWorld.resource”包與之對(duì)應(yīng);如果被測(cè)程序叫“HelloWorld”,則建立“HelloWorldTest”類(lèi)與之對(duì)應(yīng),這個(gè)類(lèi)是一個(gè)JUnit測(cè)試程序。
下面就是編寫(xiě)這個(gè)測(cè)試程序執(zhí)行測(cè)試了。由于被測(cè)程序有三個(gè)分支,即當(dāng)前時(shí)間是上午、下午、晚上,因此我們分別為之建立了三個(gè)測(cè)試用例,測(cè)試程序如下:
這段程序采用的是JUnit4編寫(xiě)的,其中assertThat(result, is("Hi, IT攻城獅. Good night!"));,第一個(gè)參數(shù)是被測(cè)程序執(zhí)行的結(jié)果,而第二個(gè)參數(shù)是根據(jù)期望結(jié)果進(jìn)行驗(yàn)證。如果執(zhí)行結(jié)果與預(yù)期結(jié)果相同,則測(cè)試通過(guò),否則測(cè)試失敗。
隨后我們運(yùn)行該測(cè)試程序,得到如下結(jié)果:
三項(xiàng)測(cè)試用例全部通過(guò),測(cè)試成功!
現(xiàn)在我們?yōu)樵绦蚓帉?xiě)了測(cè)試用例并全部測(cè)試通過(guò),我們?yōu)橹貥?gòu)所做的準(zhǔn)備工作就一切就緒了。然后,我們開(kāi)始進(jìn)行第一次重構(gòu)。如前面所述,第一次重構(gòu)我們調(diào)整了程序的順序,進(jìn)行了分段,增加了注釋?zhuān)⑿薷牧讼鄳?yīng)的變量,使其更加利于閱讀。這是一個(gè)小步快跑的過(guò)程,我們完成此次重構(gòu)只花費(fèi)了3、5分鐘。當(dāng)重構(gòu)完成,程序重新回到可編譯運(yùn)行狀態(tài)時(shí),我們執(zhí)行它的這個(gè)測(cè)試程序,測(cè)試通過(guò)。測(cè)試通過(guò)意味著,雖然程序內(nèi)部的代碼有所修改,但程序?qū)ν獾墓δ軟](méi)有變化,即程序的外部行為沒(méi)有變化,則重構(gòu)成功,我們可以繼續(xù)后面的工作。
第二次重構(gòu),我們運(yùn)用“抽取方法”,從sayHello()函數(shù)中抽取出了getFirstGreeting(), getSecondGreeting(), getHour()三個(gè)方法。之后我們?cè)俅螆?zhí)行測(cè)試程序,測(cè)試通過(guò)。
第三次重構(gòu),我們運(yùn)用“抽取類(lèi)”,將getFirstGreeting()與getSecondGreeting()分別抽取出來(lái)形成了GreetingToUser和GreetingAboutTime。完成之后執(zhí)行測(cè)試通過(guò)。
第四次重構(gòu),我們的需求發(fā)生了變化,問(wèn)候語(yǔ)不僅隨一天中的上午、下午、晚上等進(jìn)行變化,還需要根據(jù)不同的日期判斷是否是節(jié)日。在這種情況下,我們采用“兩頂帽子”的方式進(jìn)行開(kāi)發(fā):首先不引入新的需求,僅僅修改原程序,使之適應(yīng)新需求。為此我們從GreetingAboutTime類(lèi)中提煉出DateUtil,使之不僅有g(shù)etHour(),還有g(shù)etMonth()與getDate()。完成重構(gòu)以后測(cè)試通過(guò)。
關(guān)于“兩頂帽子”的設(shè)計(jì)方式,也是系統(tǒng)重構(gòu)中另一個(gè)不同以往的地方,我們還將在后面詳細(xì)地進(jìn)行討論。隨后我們開(kāi)始添加新需求,使GreetingAboutTime中的getGreeting()寫(xiě)成這樣:
之后我們的測(cè)試不能通過(guò):
為什么testSayHelloAtNight測(cè)試不能通過(guò)呢?仔細(xì)查看被測(cè)程序,我們發(fā)現(xiàn)它的功能發(fā)生了變化,變?yōu)椋喝绻?dāng)前時(shí)間是1月1日,則返回“Hi, XXX. Happy new year!”;如果是1月14日,則返回“Hi, XXX. Happy valentine's day!”……如果當(dāng)前時(shí)間都不是這些節(jié)日,如果是上午則返回“Hi, XXX. Good morning!”,是中午則返回“Hi, XXX. Good noon!”,是下午則返回“Hi, XXX. Good afternoon!”,是傍晚則返回“Hi, XXX. Good evening!”,否則才返回“Hi, XXX. Good night!”。正因?yàn)槿绱耍覀冃枰{(diào)整我們的測(cè)試程序,為每一個(gè)分支編寫(xiě)測(cè)試用例。測(cè)試修改好后,最后測(cè)試通過(guò)。
大話重構(gòu)連載首頁(yè): http://fangang.iteye.com/blog/2081995
特別說(shuō)明:希望網(wǎng)友們?cè)谵D(zhuǎn)載本文時(shí),應(yīng)當(dāng)注明作者或出處,以示對(duì)作者的尊重,謝謝!
然后我們開(kāi)始為這段程序編寫(xiě)測(cè)試代碼(如果采用測(cè)試驅(qū)動(dòng)開(kāi)發(fā),應(yīng)當(dāng)先寫(xiě)測(cè)試代碼再寫(xiě)程序)。我們首先建立一個(gè)test源程序目錄,然后建立與被測(cè)程序?qū)?yīng)的包和測(cè)試程序。這就是說(shuō),如果被測(cè)程序在“org.refactoring.helloWorld.resource”包中,則測(cè)試程序應(yīng)當(dāng)建立“test.org.refactoring.helloWorld.resource”包與之對(duì)應(yīng);如果被測(cè)程序叫“HelloWorld”,則建立“HelloWorldTest”類(lèi)與之對(duì)應(yīng),這個(gè)類(lèi)是一個(gè)JUnit測(cè)試程序。
下面就是編寫(xiě)這個(gè)測(cè)試程序執(zhí)行測(cè)試了。由于被測(cè)程序有三個(gè)分支,即當(dāng)前時(shí)間是上午、下午、晚上,因此我們分別為之建立了三個(gè)測(cè)試用例,測(cè)試程序如下:
/** * Test for {@link org.refactoring.helloWorld.resource.HelloWorld} * @author fangang */ public class HelloWorldTest { private HelloWorld helloWorld = null; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { helloWorld = new HelloWorld(); } /** * @throws java.lang.Exception */ @After public void tearDown() throws Exception { helloWorld = null; } /** * Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}. */ @Test public void testSayHelloInTheMorning() { Date now = DateUtil.createDate(2013, 9, 7, 9, 23, 11); String user = "鮑曉妹"; String result = ""; result = helloWorld.sayHello(now, user); assertThat(result, is("Hi, 鮑曉妹. Good morning!")); } /** * Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}. */ @Test public void testSayHelloInTheAfternoon() { Date now = DateUtil.createDate(2013, 9, 7, 15, 7, 10); String user = "關(guān)二鍋"; String result = ""; result = helloWorld.sayHello(now, user); assertThat(result, is("Hi, 關(guān)二鍋. Good afternoon!")); } /** * Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}. */ @Test public void testSayHelloAtNight() { Date now = DateUtil.createDate(2013, 9, 7, 21, 30, 10); String user = "IT攻城獅"; String result = ""; result = helloWorld.sayHello(now, user); assertThat(result, is("Hi, IT攻城獅. Good night!")); } }
這段程序采用的是JUnit4編寫(xiě)的,其中assertThat(result, is("Hi, IT攻城獅. Good night!"));,第一個(gè)參數(shù)是被測(cè)程序執(zhí)行的結(jié)果,而第二個(gè)參數(shù)是根據(jù)期望結(jié)果進(jìn)行驗(yàn)證。如果執(zhí)行結(jié)果與預(yù)期結(jié)果相同,則測(cè)試通過(guò),否則測(cè)試失敗。
隨后我們運(yùn)行該測(cè)試程序,得到如下結(jié)果:

圖4.1 JUnit測(cè)試結(jié)果
三項(xiàng)測(cè)試用例全部通過(guò),測(cè)試成功!
現(xiàn)在我們?yōu)樵绦蚓帉?xiě)了測(cè)試用例并全部測(cè)試通過(guò),我們?yōu)橹貥?gòu)所做的準(zhǔn)備工作就一切就緒了。然后,我們開(kāi)始進(jìn)行第一次重構(gòu)。如前面所述,第一次重構(gòu)我們調(diào)整了程序的順序,進(jìn)行了分段,增加了注釋?zhuān)⑿薷牧讼鄳?yīng)的變量,使其更加利于閱讀。這是一個(gè)小步快跑的過(guò)程,我們完成此次重構(gòu)只花費(fèi)了3、5分鐘。當(dāng)重構(gòu)完成,程序重新回到可編譯運(yùn)行狀態(tài)時(shí),我們執(zhí)行它的這個(gè)測(cè)試程序,測(cè)試通過(guò)。測(cè)試通過(guò)意味著,雖然程序內(nèi)部的代碼有所修改,但程序?qū)ν獾墓δ軟](méi)有變化,即程序的外部行為沒(méi)有變化,則重構(gòu)成功,我們可以繼續(xù)后面的工作。
第二次重構(gòu),我們運(yùn)用“抽取方法”,從sayHello()函數(shù)中抽取出了getFirstGreeting(), getSecondGreeting(), getHour()三個(gè)方法。之后我們?cè)俅螆?zhí)行測(cè)試程序,測(cè)試通過(guò)。
第三次重構(gòu),我們運(yùn)用“抽取類(lèi)”,將getFirstGreeting()與getSecondGreeting()分別抽取出來(lái)形成了GreetingToUser和GreetingAboutTime。完成之后執(zhí)行測(cè)試通過(guò)。
第四次重構(gòu),我們的需求發(fā)生了變化,問(wèn)候語(yǔ)不僅隨一天中的上午、下午、晚上等進(jìn)行變化,還需要根據(jù)不同的日期判斷是否是節(jié)日。在這種情況下,我們采用“兩頂帽子”的方式進(jìn)行開(kāi)發(fā):首先不引入新的需求,僅僅修改原程序,使之適應(yīng)新需求。為此我們從GreetingAboutTime類(lèi)中提煉出DateUtil,使之不僅有g(shù)etHour(),還有g(shù)etMonth()與getDate()。完成重構(gòu)以后測(cè)試通過(guò)。
關(guān)于“兩頂帽子”的設(shè)計(jì)方式,也是系統(tǒng)重構(gòu)中另一個(gè)不同以往的地方,我們還將在后面詳細(xì)地進(jìn)行討論。隨后我們開(kāi)始添加新需求,使GreetingAboutTime中的getGreeting()寫(xiě)成這樣:
/** * @return the greeting about time */ public String getGreeting(){ DateUtil dateUtil = new DateUtil(date); int month = dateUtil.getMonth(); int day = dateUtil.getDay(); int hour = dateUtil.getHour(); if(month==1 && day==1) return "Happy new year! "; if(month==1 && day==14) return "Happy valentine's day! "; if(month==3 && day==8) return "Happy women's day! "; if(month==5 && day==1) return "Happy Labor day! "; ...... if(hour>=6 && hour<12) return "Good morning!"; if(hour==12) return "Good noon! "; if(hour>=12 && hour<19) return "Good afternoon! "; if(hour>=19 && hour<22) return "Good evening! "; return "Good night! "; }
之后我們的測(cè)試不能通過(guò):

圖4.2 測(cè)試用例不能通過(guò)
為什么testSayHelloAtNight測(cè)試不能通過(guò)呢?仔細(xì)查看被測(cè)程序,我們發(fā)現(xiàn)它的功能發(fā)生了變化,變?yōu)椋喝绻?dāng)前時(shí)間是1月1日,則返回“Hi, XXX. Happy new year!”;如果是1月14日,則返回“Hi, XXX. Happy valentine's day!”……如果當(dāng)前時(shí)間都不是這些節(jié)日,如果是上午則返回“Hi, XXX. Good morning!”,是中午則返回“Hi, XXX. Good noon!”,是下午則返回“Hi, XXX. Good afternoon!”,是傍晚則返回“Hi, XXX. Good evening!”,否則才返回“Hi, XXX. Good night!”。正因?yàn)槿绱耍覀冃枰{(diào)整我們的測(cè)試程序,為每一個(gè)分支編寫(xiě)測(cè)試用例。測(cè)試修改好后,最后測(cè)試通過(guò)。
大話重構(gòu)連載首頁(yè): http://fangang.iteye.com/blog/2081995
特別說(shuō)明:希望網(wǎng)友們?cè)谵D(zhuǎn)載本文時(shí),應(yīng)當(dāng)注明作者或出處,以示對(duì)作者的尊重,謝謝!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
