﻿# ASP.NET MVC 單元測試系列 (4)：單元測試的目的與價值

透過前三篇的系列文章，不知道初學者能不能上手開發單元測試，畢竟這對許多人來說是個新玩意，不只是單元測試是新的，可能許多人連 ASP.NET MVC 都沒玩過，那就更難跟上了，不過**只要有心，人人都可以當大師** (靈感來自於周星馳經典台詞之食神)。若你開發的是 ASP.NET MVC Web 應用程式，到底單元測試要測試些什麼？可以測試些什麼？測試的範圍又是多少呢？

首先，大家要對「**單元測試**」有個非常清楚的認知：**單元測試**是軟體測試中的**最小單位**，必須是**可信任的**。其他對於單元測試的想法各位可以參考《[與 Roy Osherove 探討單元測試的藝術 (心得筆記)](/post/2010/02/21/The-Art-of-Unit-Testing-with-Roy-Osherove-Notes.aspx) 》文章裡的描述與 [ASP.NET MVC 2 開發實戰](http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9789866348624&sid=58261) 一書【第12章 單元測試】各有不同的陳述 (書上寫的比較完整)，各位可以交替著閱讀，看是否能理出一些對單元測試與整合測試的脈絡與原則，這些原則深植你心才能讓你的單元測試越寫越好。

單元測試並不是 ASP.NET MVC 專屬的，任何程式單元 ( 通常指的是一個 Method ) 都能透過獨立撰寫的單元測試程式進行驗證，不過，到底要**驗證**什麼呢？驗證需求是否正確嗎？還是？

報告各位，一個殘酷的事實是：**單元測試的目的並不是用來驗證是否符合客戶需求，而是用來確保程式的邏輯如你預期的方式執行，保障你的程式不會在日後修修改改的過程中被破壞，或是因為需求變更而導致程式發生錯誤。**

等等，如果測試的目的不在驗證客戶需求，那我幹嘛花時間寫一堆雞肋的測試程式來累死自己呢？這樣寫有意義嗎？

是的，這個問題正是我剛接觸單元測試時最想問的。在回答這個問題之前，我們先定義**何謂客戶需求**？客戶的需求通常都是有**情境**的，客戶通常會在工作上遇到一些問題，因而產生程式的需求，希望能夠過程式化的方式降低資訊處理的複雜度或加速某些工作的執行效率，所以客戶會跟你說：「我要一個表單，表單有 blah blah 欄位，某某欄位要做某某資料格式驗證，最後要能輸入到資料庫，還要檢查資料是否有重複、……」，你應該很少聽到客戶會說：「你幫我寫個 Method，我要用 HTTP POST 傳資料給你，我會傳 id 給你，你負責進資料庫查資料，並回傳一個 ViewResult 給我，喔，對了，最後還要記得幫我套上 Authorize 屬性喔」，是吧？！

軟體測試只要扯到「測試情境」事情就會變複雜，我們真的有時間在有限的專案開發時程內完成這麼多含有情境的測試程式開發嗎？而且每個人都要做這麼完整的測試情境？如果系統大一點，可能開發人員都只能知道自己熟悉的某一塊模組而已，並無法瞭解需求的全貌，這樣又該如何測試各種不同的情境呢？

也因此，我們必須對「單元測試」與「整合測試」做的清楚的分野與釐清，我們可以很大膽的做個二分法，**只要測試情境會牽扯到外部資源或牽扯到兩個 Method 以上互動的狀況，這類的測試程式全部都歸類到整合測試的範疇**，**而剩下的每一個 Method 測試都歸類在單元測試的領域**。還記得[上一篇文章](/post/2010/09/16/ASPNET-MVC-Unit-Testing-Part-03-Using-Mock-moq.aspx)提到的一句話嗎：「**單元測試是軟體測試的最小單位，如果測試的範圍輕易的就會擴展到其他類別或同類別的其他方法，那就不再是最小單位，也就不是單元測試了！**」

另外，單元測試的目的是用來確保程式的邏輯如你預期的方式執行這樣的思考邏輯就更怪了，我自己寫的程式自己知道阿，我不去動他自然就不會改變邏輯，我為何還要多寫程式去確保程式的邏輯呢？

有寫過程式的人都知道，當系統上線後修修改改是在所難免的，有時候是改 Bug，有時候是改需求，如果我們拿改 Bug 為例，有多少人曾經遇過為了改掉某一個 Bug 而衍生另一個 Bug 的情況！我覺得任何一個寫程式多年的開發人員多多少少都一定會遇過吧！另一方面，當需求變更時，有多少人真的能做到需求變更管理？有誰能因為需求變更而將所有本次變更項目會影響的範圍完整分析過一次？客戶有時間讓你分析嗎？大部分的客戶 (或老闆) 的 Schedule 通常都只有「越快越好」對吧？！ ^\_^

任何一個軟體系統，最好的文件就是程式碼！即便需求異常複雜，程式碼異常醜陋，但最後呈現在客戶面前的就是用這堆程式碼所執行的結果，我們假設程式碼已經修修改改到完全沒有 Bug 的情況，那麼你是否相信目前的程式碼邏輯就是對的？（請注意，我們已經假設程式碼已經修修改改到完全沒有 Bug ）

是的，那麼我們的每一個 Method 都應該要有一個以上的單元測試程式去**確保程式的邏輯如你預期的方式執行**，並透過一個一個短短的單元測試程式 (Test Method) 去建構出一道軟體品質的防火牆！

\---

我舉一個簡單的例子來說，如下程式碼套用了 \[Authorize\] 屬性(Attribute)：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ca9ad6386cd6dc048fb5-image_196.png "title")

這是一個只有已登入的會員才能使用的 Action，這時我們在單元測試程式裡除了驗證基本的回傳型別 ( ViewResult ) 外，我們應該還要驗證此 Action 是否套用了 \[Authorize\] 屬性，這才能**確保程式的邏輯如你預期的方式執行**，即便我們在測試專案中無須測試 AuthorizeAttribute 是如何執行的 (因為這是另一個類別的東西)，但我們還是可以驗證此方法的確有套用 \[Authorize\] 屬性，因為在 ASP.NET MVC 裡，我們知道套用了這個屬性就代表著已登入的會員才能使用，在單元測試的程式碼裡，你還是沒有超出應測試的範圍（因為你測試的還是這個 Method 的邏輯）。

假設你今天請假，客戶回報了一個 Bug，你的菜鳥同事臨危授命負責修掉你的程式 Bug，但是在測試 Bug 的過程中發現 \[Authorize\] 屬性阻礙了他自己的測試計畫，而他的**測試計畫**其實就是直接開瀏覽器測試，立即發現頁面開不起來，而且他還不知道原來 ASP.NET 有個 [Forms Authentication](/post/2008/02/20/Explain-Forms-Authentication-in-ASPNET-20) 機制，這時他很「自然」的幫你把 \[Authorize\] 屬性移掉並重新建置部署，然後就跟客戶說：「我 Bug 修掉了，這個功能可以用了」。

這時你如果有寫單元測試，回來上工後跑一遍單元測試專案，就會立即發現你的程式被改爛了，這時你趕緊幫你的菜鳥同事擦屁股，將客戶回報的真正 Bug 修好 (其實只是 web.config 設定錯誤)，然後再將菜鳥同事產生的新 Bug 修正，最後，你的程式又回到當初的高品質狀態！

你會覺得這種狀況很誇張嗎？我看過更扯的 …Orz… 。

\---

最後一個重點，**確保程式的邏輯如你預期的方式執行**這句話的重點在於「**如你預期**」四個字，應該是「誰」需要預期這些程式是對的呢？沒錯，就是**你**！程式是你寫的，還寫到讓客戶測不出有 Bug ( 謎之聲: 這句話讓[李組長眉頭一皺，覺得案情並不單純](http://www.google.com.tw/search?hl=zh-TW&safe=off&q=%E6%9D%8E%E7%B5%84%E9%95%B7%E7%9C%89%E9%A0%AD%E4%B8%80%E7%9A%BA%EF%BC%8C%E8%A6%BA%E5%BE%97%E6%A1%88%E6%83%85%E4%B8%A6%E4%B8%8D%E5%96%AE%E7%B4%94&meta=&aq=f&oq=) XD )，自然由你來寫你自己程式的「單元測試」程式碼，由你自己來把關你自己寫的程式品質，這一切就是那樣合情合理！想找工讀生來幫你寫單元測試程式？等你一分鐘幾十萬上下再來談談！ ( 開玩笑阿，別介意 ^^ )

所以，**單元測試**的是什麼呢？我將今天講的摘要整理如下：

1.  單元測試是軟體測試中的最小單位，必須是可信任的
2.  單元測試的目的是用來確保程式的邏輯如你預期的方式執行
3.  程式是你寫的，必須由你自己把關你自己寫的程式品質，所以要自己寫「單元測試」程式碼

講到這裡，不知道各位看倌是否已經發現到「單元測試」的價值了？**軟體品質的防火牆**耶！任何會摧毀你軟體品質的攻擊事件 (如：客戶的緊急電話、老闆的插單任務、同事的詭異笑聲、隔壁工程師的碎碎唸、無敵天兵改壞你的程式) 都能被你自己寫的單元測試程式完整的保護，你說讚不讚！^\_\_^

**相關連結**

-   [與 Roy Osherove 探討單元測試的藝術 (心得筆記)](/post/2010/02/21/The-Art-of-Unit-Testing-with-Roy-Osherove-Notes.aspx)
-   [單元測試 - 維基百科，自由的百科全書](http://zh.wikipedia.org/zh-tw/單元測試)
-   [Unit testing - Wikipedia, the free encyclopedia](http://en.wikipedia.org/wiki/Unit_testing)
-   [Unit Testing Guidelines](http://geosoft.no/development/unittesting.html)
