文章數(288) 回應數(1024) 引用數(0)
2011/6/3 23:40 | 閱讀數 : 4866 | 10 人推薦 我要推薦 | Add Comment | 文章分類 : 測試 重構之路系列 | 訂閱
前言 還記得在重構第一篇[ASP.NET]重構之路系列v1 – UI, Business logic, Data access概念分開的時候,我們提到了要重構,第一步應該先建立好測試,才能確保重構的結果正確與否。沒錯,現在就是那個moment了,讓我們為我們現在的程式開始建立好我們的單元測試,沒這麼困難,跟著做就對了。 這邊要介紹的單元測試,只是第一步,也是從沒有到有最重要的一步。實際要讓整個系統單元測試run的順利,有很多不同的門檻,如果有機會,我會再開闢另一個系列是測試系列之路,屆時在那邊再針對測試的方式、策略、工具、可測試性等等進行較詳細的說明。 需求說明 這次不是可惡的PM提的問題了,請PM可以在旁邊坐著休息一下。當其他部門的同事,使用我們之前建立的Service,結果網頁出現『帳號不存在』的錯誤。原service程式碼如下:
public VerifyStatus VerifyPasswordById(string id, string password) { DataTable dt = MyAuthenticationDao.QueryPasswordById(id); if (dt.Rows.Count > 0) { if (password == dt.Rows[0]["Password"].ToString()) { return VerifyStatus.Passed; } else { return VerifyStatus.Failed; } } else { return VerifyStatus.NoExist; } }
這時候公說公有理,婆說婆有理,大家都不認為自己的程式有錯。怎麼辦?該相信誰? 答案是:誰都不要相信,相信你的測試程式,相信被測試程式測過的程式。 設計步驟 在動手之前,先聲明一下,我這系列的文章會盡量順便帶到IDE可以幫助到我們的功能,有可能您的Visual Studio不支援這樣的功能,也沒有關係,因為沒有這樣的工具,我們也可以手動的做到一樣的效果。如果大家手上用的工具,明明就有這麼方便的功能,卻一直被埋在IDE裡面,沒有時間研究,每次都重新打造,那真的就可惜了,善用工具,也是一個Professional的表現。 步驟一: 在我們要測試的方法上(方法內任何一行都可以),按下滑鼠右鍵,選擇『建立單元測試』。 給定測試專案的名稱後,會看到該有的參考,該有的測試類別,以及測試方法需要的參數、要測試的目標、回傳的型別、預設要測試的狀況、Assert的防呆,Visual Studio都幫我們建立好了。(補充說明,當method上頭有[TestMethod()]時,這個方法才會被測試唷) 步驟二: 我們先將測試方法的名字,改成我們預計要測試的情況。接著將原本的測試程式,加上3A原則的註解來區分開,沒有特殊的原因,而是寫起來舒服,看起來爽。 3A:
如果你跟我一樣是急性子,迫不及待的就先跑測試下去,恭喜你,你會得到下面這個結果。當然失敗囉,不過別怕,當你很熟悉單元測試的時候,你會發現,看到單元測試失敗,是一件很爽的事。都成功,才需要驚驚。單元測試失敗後,改到成功,就代表你程式的品質更上一層樓了。 看不懂錯誤訊息,沒關係,讓我們double click看一下詳細的測試報告: double click進去後,發現:靠,這哪裡有錯!明明就沒錯啊。沒關係,眼見為憑,偵錯給它開下去。 偵錯時發現,Visual Studio是對的,我們的MyAuthenticationDao是null,的確是NullReferenceException。 稍微修改一下我們的程式,new一個Dao的instance,assign給我們的target。並將期望結果改為NoExist。 再跑一次,發現還是測試結果還是錯的。我要再強調一次,錯,是好事。我們來找原因在哪。 沒錯,這什麼鬼!!因為這是Sample Code啊…這個方法根本就沒有寫完,跟只有一行throw new NotImplementedException();是一樣的意思的。但是,我是寫Service的人啊,Dao撈資料錯了,干我屁事。沒錯!請那一位同事去找寫Dao的人,但是,我們也還沒證明我們的Service是對的,接下來就是單元測試很酷的地方了。 單元測試重要的宗旨:測試目標的方法,應該與外界類別隔離,把我們的注意力focus在我們方法的邏輯上,外面的方法錯,是他們家的事。我們要care的是,當外部類別如同預期的給我們對應的資料,我們的程式,就可以如預期的回傳我們要的結果。 測試我們的service,為什麼要連DB? 難道不能連DB,我service的測試方法失敗,就等於我的程式有錯?這比扯鈴還扯。 讓我們來證明,我們的程式是對的吧! 步驟三: 我們要跟外部class無關,首先就要手動的刻一個我們能決定回傳結果的Dao。 先來規劃一下我們要怎麼做,看一下我們的class diagram: 接下來,就讓我們來寫我們的StubDao吧,還記得我們的老招嗎?就先把code寫下去,再自動產生就好啦。 很好,到這邊,我們已經打造好我們的StubDao了,接下來,我們只要定義,QueryPasswordById要回傳什麼資料就可以了。既然我們的測試方法是要測找不到資料的情況,那我們就直接回傳一個空的DataTable即可。 接著我們偵錯看看,測試程式會怎麼走。 果然,dt.Rows.Count是0,(廢話,new DataTable()當然是0)。 噹噹噹!大功告成!我就說我的程式在Dao沒資料的時候,結果會回傳NoExist嘛!事實證明的確如此! (同事迷之音:啊我是要問,為什麼我id給joey,密碼給79979,我DB明明就有資料,為什麼結果你的service是回傳NoExist咧?!) 步驟四: 為了讓那位毛很多的同事閉嘴,我們就根據他的描述設計我們的測試程式,id給joey,密碼給79979,預計DB會回傳一筆資料,密碼是79979,而這位同事的期望結果為Passed。 這個時候,請用我們的步驟一,直接在我們的方法上面,再按一次滑鼠右鍵,選擇『建立單元測試』,直接建立在我們已經存在的測試專案中,這樣才會快咩! 接下來,我們會發現一個問題,我的Dao該給什麼?給剛剛那一個JoeyDao,再把QueryPasswordById的回傳改成我們要的資料嗎?答案是No!!這樣我們原本測試空資料的方法不就失敗了? 難道,又要寫一個JoeyDao2,來回傳對應的資料?哇咧,這樣寫測試程式真的好花時間啊,難怪大家都說單元測試要花很多成本。 來來來,介紹你好藥,這個時候我們就要靠mock framework來輔助我們。
步驟五: 先來看我們的class diagarm如下: 這邊我範例中使用的mock framework為Rhino.Mocks(下載點),寫法很簡單,照著做就對了: 將Rhino.Mocks.dll加入測試專案參考中,記得using Rhino.Mocks。 實際的mock程式其實只有兩行:
該寫的寫完了,接著就讓我們來看實際執行的結果。可以看到,mock framework幫我們產生的stub物件型別是一串落落長的名字,其實從名字可以看出來,Rhino.Mocks使用的dynamic proxy framework是Castle DynamicProxy framework。(Dynamic Proxy也可以應用在AOP的設計,可以參考:[Spring.Net]Aop introduction–以performance log為例) 我自己是習慣會全部的測試都再跑一次,以確定這一連串的修改,不會影響到其他測試程式預期的執行結果: OK!現在可以拿著你的測試報告,去跟那一位同事大聲的說:我的service沒有問題,如果DB回來的資料是這一筆,那我Service回傳的一定是Passed! 結論
最後再提醒大家一次,要相信誰的程式?production上的程式?版本庫裡面的程式?明星工程師寫出來的程式?大師寫出來的程式?都不是,請相信通過測試程式的程式! Sample Code:RefactoringSample-v5.zip
↑ Grab this Headline Animator
[ASP.NET]重構之路系列v6 –抽象來看程式是否符合DRY原則
[單元測試]沒封裝成DLL也可以撰寫單元測試
[ASP.NET]重構之路系列v4 – 簡單使用interface之『你也會IoC』
[ASP.NET]重構之路系列v3 – 跨專案使用類別庫
標題 *
名稱 *
Email(將不會被顯示)
Url
記住我? 登入後使用進階評論