iT邦幫忙

DAY 3
13

30天快速上手TDD系列 第 3

[Day 3]動手寫Unit Test

上一篇文章介紹了單元測試的5W,這一篇則是要介紹How,怎麼開始動手寫我們第一個Unit Test。(終於可以寫程式了,笑...)

本篇文章會以Visual Studio為開發工具,以MSTest為Testing framework。

介紹如何從目標物件的方法,建立對應的單元測試。

也會介紹如何從測試程式,來撰寫對應的目標物件。

最後則會說明怎麼透過Visual Studio來觀看程式碼覆蓋率。

上一篇文章:[Day 2]Unit Testing 簡介
本系列文章專區
@既有程式產生單元測試(VS2010)
首先,先建立一個Library專案,以最一般好懂的例子,裡面有一個Calculator的類別,一個Add的公開方法。程式碼如下所示:

        public int Add(int firstNumber, int secondNumber)
        {
            return firstNumber + secondNumber;
        }
  1. 在類別或方法內容中,按滑鼠右鍵,叫出選單。
  2. 點選「建立單元測試」的選項。
  3. VS2010會跳出畫面,詢問你要針對哪一個目標類別,以及哪一些方法建立單元測試。(若在類別上,則預設所有方法會被勾選。若在某一個方法上,則只有該方法會被勾選)
  4. 可選擇將單元測試程式加入已經存在的測試專案,或由VS2010幫你自動建立一個測試專案。

接著VS2010會把畫面直接帶到測試專案,你的測試類別上。(如果測試類別已經存在,新的測試方法會append在最下面)程式碼如下所示:

        /// <summary>
        ///Add 的測試
        ///</summary>
        [TestMethod()]
        public void AddTest()
        {
            Calculator target = new Calculator(); // TODO: 初始化為適當值
            int firstNumber = 0; // TODO: 初始化為適當值
            int secondNumber = 0; // TODO: 初始化為適當值
            int expected = 0; // TODO: 初始化為適當值
            int actual;
            actual = target.Add(firstNumber, secondNumber);
            Assert.AreEqual(expected, actual);
            Assert.Inconclusive("驗證這個測試方法的正確性。");
        }

可以看到上面的程式碼,貼心的VS2010已經幫我們把測試程式的殼都建好了。我們剛剛選取要測試的方法,是Calculator類別的Add方法,而Add方法,需要兩個int的參數,並回傳一個int的結果。

所以,測試程式上有哪些東西呢?

  1. 測試程式中會先初始化一個目標物件,也就是new一個Calculator,採預設的建構式。
  2. 並依據測試方法上的簽章,自動幫我們建立所需要的參數,連變數命名都是按照方法簽章上的定義來宣告。
  3. 若方法有回傳值,也會定義一個預期的回傳結果,變數命名為expected,代表預期結果。
  4. 方法有回傳值,還會定義一個變數為actual,為測試目標物件的實際回傳結果。
  5. 測試程式實際呼叫目標物件,欲測試的方法 (其實這個測試程式,即模擬外部如何使用目標物件)
  6. 驗證實際結果與預期結果,是否吻合

很簡單吧,這邊不得不提,Visual Studio更貼心的部分是幫你把需要改的部分,加上了//TODO註解,當設定好之後,別忘了把todo註解移除唷。而最後一行Assert.Inconclusive()則是VS2010在自動產生完測試程式後,替開發人員防呆用的。所以寫好測試程式後,執行測試前記得移除Assert.Inconclusive()這一行。

假設外部的使用情境(也就是測試案例),是傳入1與2,並期望Calculator的Add方法回傳為3,那測試程式碼如下所示:

        [TestMethod()]
        public void AddTest()
        {
            Calculator target = new Calculator();
            int firstNumber = 1;
            int secondNumber = 2;
            int expected = 3;
            int actual;
            actual = target.Add(firstNumber, secondNumber);
            Assert.AreEqual(expected, actual);
        }

執行測試也很簡單,在測試方法上,滑鼠右鍵即有「執行測試」的選項。但因為執行測試很常使用,而且絕大部分執行的時機點,都不是在測試專案上,而是寫完任一段落的production code。因此建議一定要熟記熱鍵,預設熱鍵組合如下:

  1. Ctrl+R, T: 執行單一測試
  2. Ctrl+R, A: 執行所有測試(開發時最常使用)
  3. Ctrl+R, Ctrl+T: 偵錯單一測試(測試失敗時,最常使用)
  4. Ctrl+R, Ctrl+A: 偵錯所有測試

在測試結果視窗,就能看到各測試方法的結果,以及測試失敗的錯誤訊息跟call stack。

在撰寫單元測試的程式碼時,有個3A原則,來輔助設計測試程式,可以讓測試程式更好懂。3A原則如下:

  1. Arrange: 初始化目標物件、相依物件、方法參數、預期結果,或是預期與相依物件的互動方式
  2. Act: 呼叫目標物件的方法
  3. Assert: 驗證是否符合預期

程式碼上只需要加上註解,可讀性就會提升一些,如下所示:

        [TestMethod()]
        public void Add_Input_First_1_Second_2_Return_3()
        {
            //arrange
            Calculator target = new Calculator();
            int firstNumber = 1;
            int secondNumber = 2;
            int expected = 3;

            //act
            int actual;
            actual = target.Add(firstNumber, secondNumber);

            //assert
            Assert.AreEqual(expected, actual);
        }

額外補充一下,要記得改的通常還有一個地方,就是測試方法的名稱。因為當測試失敗時,應該要能迅速的由測試方法名稱判定,是哪一個方法或哪一種情境下,目標物件行為不符合預期。

[註1]感覺可以直接產生對應的測試方法,很過癮吧!但這個功能在VS2012被移除了,其中一個原因應該也是希望開發人員是使用TDD的方式進行開發,而不是寫完程式才回過頭來補測試程式。

[註2]在VS2010,可以針對非public的方法進行單元測試,VS2010會透過reflection幫忙產生一個測試目標的Accessor物件。不過這個功能在VS2012也移除了,其中一個原因應該是因為這樣的測試方式,並不符合物件設計原則。針對這一點,後續我會再用一篇文章來進行說明。

@由測試方法產生目標物件行為
假設我們需要Calculator提供一個減法的功能,傳入3,2,則回傳結果應為1。測試程式如下:

        [TestMethod()]
        public void Minus_Input_First_3_Second_2_Return_1()
        {
            //arrange
            Calculator target = new Calculator();
            int firstNumber = 3;
            int secondNumber = 2;
            int expected = 1;

            //act
            int actual;
            actual = target.Minus(firstNumber, secondNumber);

            //assert
            Assert.AreEqual(expected, actual);
        }

這時因為Calculator類別上,並沒有Minus方法,所以會建置失敗。這時只需要透過滑鼠右鍵,或是在Minus上,選「產生」(預設熱鍵為Ctrl+.),Visual Studio即會在Calculator上產生Minus的方法。

程式碼如下:

        public int Minus(int firstNumber, int secondNumber)
        {
            throw new NotImplementedException();
        }

沒錯,由測試程式所產生的production code,也會依據測試程式所給予的變數名稱,來當作方法簽章。當然,這個產生程式的方式,不僅限於測試程式產生production code,而是只要沒有這個類別或這個方法,就都可以透過產生的方式,來產生class/interface/enum,或是property/function。

這時建置已經可以成功,但執行測試時,肯定會跳紅燈。你問我為什麼?因為我還沒發功啊...預設產生的方法內容是throw new NotImplementedException(); 所以執行測試時,就會接到這個exception。

但請相信我,這是好事。到這步驟,您TDD的起手式已經完成,這是紅燈->綠燈->重構循環的第一步:紅燈。

接著,只需要撰寫Minus方法,讓這個紅燈可以變成綠燈即可。任何方式都可以,包括直接return 1;,或許您會覺得我怎麼可能直接return -1呢?但TDD講究的是,滿足測試案例,即代表功能符合預期。當需要滿足其他需求,請增加測試案例。不斷的紅燈、綠燈、重構,與增加測試案例,就代表目標物件越來越符合外部需求,也代表品質在不同場景下,出錯機率越來越低。而且不必再擔心重構時,把程式改壞了,因為每次修改,都有越來越多的測試案例保護,改完馬上就會知道有沒那個地方冒煙了...

當漸漸熟悉這樣的方式之後,就不需要每次都從hard-code開始撰寫,但這個用最簡單的方式滿足測試案例,有幾個好處:

  1. 當還沒有什麼idea時,至少可以先滿足第一個測試案例。(內心就會覺得有產出,跨出一步了)
  2. 一個紅燈一直在那,會壓抑自己過度設計的慾望。紅燈代表要馬上解決,這就是我們眼前的目標。
  3. 紅燈、綠燈、重構,會是一個讓開發人員很愉悅的節奏。就跟跳恰恰一樣美好。

@測試覆蓋率
測試覆蓋率,或程式碼覆蓋率,指的是執行完測試程式後,所有production code被執行到的比率。相關詳細的介紹,請參考小弟去年鐵人賽的文章:[如何提升系統品質-Day24]測試 - Code Coverage

當在Visual Studio中,建立測試專案後,方案底下會有個Local.testsettings檔,點開後選擇「資料和診斷」,啟用「程式碼涵蓋範圍」。如下圖所示:

讀者可能以為這樣就可以看到code coverage,但其實還有個小地方要設定。針對程式碼涵蓋範圍,double click,會跳出期望要算出code coverage的組件,選擇剛剛的Library,選擇套用,這才設定完畢。

設定完執行一次全部的測試,在測試結果的視窗上,可以點選「顯示程式碼涵蓋範圍結果」,即可觀看程式碼涵蓋範圍,展開細節之後,可直接double click方法,即可移至該方法內容上。當有勾選要計算程式碼覆蓋率時,預設跑完測試後,程式碼就會被上色。有執行到的是淺藍色,沒被執行到的則是紅色。如圖所示:

視窗上有個按鈕,可以開關著色,如下圖所示:

@結論
這篇文章其實很淺,目的是為了讓還沒動手寫過單元測試/測試程式的朋友們,可以step by step的動手玩玩看。

不過本篇文章提到的兩個切入點,都是後續文章或環節的重要起手式:

  1. 從既有程式碼產生單元測試,是重構既有程式碼,很好用的一個方式。也是增加新的測試案例,很方便的方式。
  2. 從測試案例,產生對應的程式碼。可以輔助我們,只開發需要開發的功能。top-down的設計方式,讓我們專注在解決眼前這個需求,也可以避免我們浪費很多不必要浪費的時間。

不管是哪一種角度當切入,設計物件時,有一個重要的原則,希望各位讀者用心記住:
設計物件,應思考外部如何使用這個物件,而不是bottom-up的思考,這個物件要提供哪些功能給外面用

其實,就像介面導向的原則一樣,只相依於介面,就可以只專注在抽象層面上,而不被實作細節所影響。


上一篇
[Day 2]Unit Testing 簡介
下一篇
[Day 4]單元測試:是否需針對非 public method 進行測試?
系列文
30天快速上手TDD31
0
ted99tw
iT邦高手 1 級 ‧ 2012-10-11 09:23:10

MVP果真是名不虛傳,就算在手忙腳亂殺怪獸的當下,也要挪一隻手來狠狠地按“推”!!!
讚讚讚

pajace2001 iT邦研究生 1 級 ‧ 2012-10-11 11:16:30 檢舉

這是一定要的啦rock

0
andysheu
iT邦新手 5 級 ‧ 2012-10-15 09:27:55

2012取消按右鍵「建立單元測試」的選項,不是很適應,即使要推TDD;但是事後做單元測試,也比都沒有做單元測試來的好。

pajace2001 iT邦研究生 1 級 ‧ 2012-10-15 09:45:40 檢舉

原來是取消了~難怪我怎麼找都找不到~只好乖乖的從目錄上面去新增XD

我要留言

立即登入留言