2017年4月16日 星期日

【Unit Test】Day 1 - 為何要寫單元測試


Demo檔案 : Git傳送門
算一算開始寫單元測試也快兩年了,很感謝當時能有機會得到前輩的指導並接觸到這個技術,在實際運用到開發上面時,也真正的感受到它的好,為了將單元測試這好東西分享給更多人知道,所以有了寫這系列文章的想法。

這系列文章是希望在讀者讀完後,能讓你從一位不會寫單元測試的開發者,粗略的搞懂單元測試為何、如何撰寫、並且能透過實作中體會到它如何幫助專案更加穩固。

我不是什麼技術大牛,如果發現文章有疏漏的地方,還請不吝指正指導。 感謝!!

為何我要寫單元測試

不知你可曾遇過這樣的狀況....

情境一 :

接手了一個很舊的系統維護,關於規格與文件皆已不可考,連開發過的工程師們都已離職不幹了,現在PM跟你說他想改一個功能,並拍拍你肩說「這個很簡單,一下下就好」。而你卻不知道該從何下手....

單元測試能幫你做什麼?
優良的單元測試就像活著的規格書,不僅能幫助你了解那些不是你負責的功能,更是能夠執行的規格書

情境二:

是否曾經改發生過改好新功能,但舊功能就壞掉,修好舊功能,新功能又壞掉了呢?  假設下圖就是你的功能,而你希望它三個能同時選取時....

http://abcdefghijklmn-pqrstuvwxyz.com/en/you-cant-have-it-all/

單元測試能幫你做什麼?
它能告訴你目前的程式,是否執行都符合當時所要求的規格與產出結果,如果舊規格舊方法沒有改的情況下,單元測試壞了,那就表示你這次的改動絕對有影響到它,趕緊去修好它吧!!

情境三 :

一個你覺得很簡單的功能,直到你上線才發現原來功能是錯的無法正常執行,偵錯下去才發現,天殺的,原來是自己觀念上錯誤或不熟悉。例如Double a  = 1.1加上Double b = 1.2,而你以為它的結果會是2.3.... 

單元測試能幫你做什麼?
它能即時的驗證你的想法,而不是到上線時才賭人品,尤其是那些你覺得理所當然會對的功能中,魔鬼往往藏在細節裡

在你還沒寫單元測試之前,上述狀況是否都似曾相識呢?如果你想解決這些問題,那麼開始寫單元測試將是可以大幅度降低這些錯誤的有效方法,而且你一定會愛上它。


該如何開始寫單元測試

接著我們就來用實際案例重現上面提到了一些問題,並且透過撰寫單元測試來看看它如何幫我們避免這些狀況。

首先準備一個類別庫專案


寫下一段簡單的程式,而這段程式很簡單,就只是把基數加上2回傳回去即可


public class EasyMethod
{
   private int BaseNumber = 5;

   public int Method1()
   {
      return BaseNumber + 2;
   }   
}

而這段程式真的非常簡單,簡單到你一看就覺得它回傳是7,不過沒關係我們繼續完成單元測試,看它能幫助我們什麼


接著建立單元測試專案
在Method1的的方案下右鍵 > 建立單元測試
如果你是用VS2017,可能會發現沒有建立單元測試這個選項,請將VS更新到最新版就有了


接著就會看到單元測試專案已經幫你建立完成


讓我們來寫下第一個完整的單元測試
將那個自動建立的單元測試改成
        [TestMethod()]
        public void Method1Test_呼叫時應回傳結果為7()
        {
            //arrange
            var Sut = new EasyMethod();
            var expected = 7;

            //act
            var actual = Sut.Method1();

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

稍微檢視一下這個單元測試能帶給我們什麼?首先從標題上我們可以知道這個方法的目的跟應得到的結果,接著我們可以從單元測試的程式碼中看出這個方法該如何使用。

接著我們在測試總管中執行單元測試,知道目前這個方法符合我們需求跟得到預期的結果

當我們面臨需求的更改.....
接著我們知道日常狀況是,需求總是一直的在擴充及變動,所以我們有了一個新需求

我希望能有個新的方法,並且呼叫它時能回傳給我10這個答案,而且因應業務需求,基數需要改變成2
OK!!一切聽起來都不是太難,那就讓我們直接動手下去做吧
首先我先將基數改成2,並且新增一個Method2的方法,並且讓他加上基數後得到10這個結果回傳。

老方法,在Method2那邊按下右鍵 > 建立單元測試,並寫上新的方法的單元測試
namespace UnitTestDay1.Tests
{
    [TestClass()]
    public class EasyMethodTests
    {
        [TestMethod()]
        public void Method1Test_呼叫時應回傳結果為7()
        {
            //arrange
            var Sut = new EasyMethod();
            var expected = 7;

            //act
            var actual = Sut.Method1();

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

        [TestMethod()]
        public void Method2Test_呼叫時應回傳結果為10()
        {
            //arrange
            var Sut = new EasyMethod();
            var expected = 10;

            //act
            var actual = Sut.Method2();

            //assert
            Assert.AreEqual(expected, actual);
        }
    }
}
一切完美直到你執行單元測試時你會發現,原本的Method1壞掉啦!!
原來因為我們更改了基數所以造成了Method1回傳時不符合當時所制定的規格,這時候我們如果確定原需求沒有變動的情況下,那就是去修改原本的方法讓它能通過單元測試。這樣新舊需求就都確保正確的情況下更正完成了。



今日小結

從這個範例中我們可以整理出一些單元測試所能帶來的好處。

首先,因為在撰寫程式時,我們會在當下寫上單元測試,並且讓它通過,日後不管任何原因它壞掉了,我們都能從標題中或是案例來知道當時的需求與狀況,形成上面提到的所謂活著可執行的規格書,(當然前提是你的標題跟內容要寫得乾淨易懂,否則維護單元測試可能又是另一場災難),即便規格書遺失或是人員異動,對於程式本身的維護都有一定品質的保障。

接著是需求異動,雖然這個範例舉例的有點極端,但我只是想表達一種狀況,常常我們在改動共用方法或是核心功能時,往往不知道這樣改可能會造成哪邊的錯誤,甚至是編譯執行都是正常,但其實違反了當時所定義的程式或商業邏輯,而這往往是要上線的時候才會發現,但從這案例中,基數被改變時,雖然把Mehod2順利地完成的,但我們卻忽略了Method1可能導致它變動進而產生錯誤,有了單元測試或是整合測試,這樣的狀況可以有效地減少跟被控管(特別強調:非百分之百!!所以整合測試跟單元測試都一樣很重要,如果可以,兩個都做會是最好的選擇)

好的,希望第一篇文章可以淺顯易懂的讓大家感受單元測試的好處!!

0 意見:

張貼留言