天天看點

C# 單元測試(入門)

C# 單元測試(入門)

轉載weixin_33875564 釋出于2018-11-27 00:04:00 閱讀數 658  收藏

展開

注:本文示例環境

VS2017

XUnit 2.2.0 單元測試架構

xunit.runner.visualstudio 2.2.0 測試運作工具

Moq 4.7.10 模拟架構

什麼是單元測試?

確定軟體應用程式按作者的期望執行操作,其中最好的一種方法是擁有自動化測試套件。 可以對軟體應用程式進行各種不同的測試,包括內建測試、Web 測試、負載測試等。 測試各個軟體元件或方法的單元測試是最低級測試。

所謂單元測試(unit testing),就是開發者編寫的一小段代碼,用于對軟體中的最小單元進行檢查和驗證,其一般驗證對象是一個函數或者一個類。通常而言,一個單元測試是用于判斷某個特定條件(或者場景)下某個特定函數的行為。

為什麼要使用單元測試?

  • 大大節約了測試和修改的時間,有效且便于測試各種情況。
  • 能快速定位bug(每一個測試用例都是具有針對性)。
  • 能使開發人員重新審視需求和功能的設計(難以單元測試的代碼,就需要重新設計)。
  • 強迫開發者以調用者而不是實作者的角度來設計代碼,利于代碼之間的解耦。
  • 自動化的單元測試能保證回歸測試的有效執行。
  • 使代碼可以放心修改和重構。
  • 測試用例,可作為開發文檔使用(測試即文檔)。
  • 測試用例永久儲存,支援随時測試。

對于我個人來說,主要是防止自己犯低級錯誤的,同時也友善修改(BUG修複)而不引入新的問題。可以放心大膽的重構。簡言之,這個簡單有效的技術就是為了令代碼變得更加完美。

既然單元測試有這些好處,為什麼我們不去用呢?

可以歸納為以下幾個理由。

  1. 對單元測試存在的誤解,如:單元測試屬于測試工作,應該由測試人員來完成,是以單元測試不屬于開發人員的職責範圍。

    答:雖然單元測試雖然叫做"測試",但實際屬于開發範疇,應該由開發人員來做,而開發人員也能從中受益。

  2. 沒有真正意識到單元測試的收益,認為寫單元測試太費時,不值得。

    答:在開發時越早發現bug,就能節省更多的時間,降低更多的風險。單元測試先期要編寫測試用例,是需要多耗費些時間,但是後面的調試、自測,都可以通過單元測試處理,不用手工一遍又一遍處理。實際上總時間被減少了。

  3. 項目經理或技術主管沒有要求寫單元測試,是以不用寫。

    答:寫單元測試應該成為開發人員的一種本能,開發本身就應該包含單元測試。

  4. 不知道有單元測試這回事,不知道如何用。經過這篇文檔的說明,就基本知道如何處理單元測試。

架構選型

常用單元測試架構:MSTest (Visual Studio官方)、XUnit 和 NUnit。

  1. MS Test為微軟産品,內建在Visual Studio 2008+工具中。
  2. NUnit為.Net開源測試架構(采用C#開發),廣泛用于.Net平台的單元測試和回歸測試中,官方網址(www.nunit.org)。
  3. XUnit.Net為NUnit的改進版。

(以下主要講解MSTest 和NUnit的使用,XUnit操作和NUnit操作基本類似)

基礎實踐

開始建立你的第一個的單元測試項目吧

1)  我們先來用 VS2017 中自帶的測試子產品(MSTest)來寫一個簡單的單元測試吧。

  1. 建立一個Solution,并添加項目UnitTestDemo(用于編寫被測試的項目)
  2. 在該工程中添加UnitTestClass類,并書寫一個靜态的GetTriangle(string[] sideArr) 函數用來傳回一個三角形的類型。
namespace UnitTest
{
    public class UnitTestClass
    {
        /// <summary>
        /// 擷取三角形類型.
        /// </summary>
        /// <param name="sideArr">三角形三邊長度數組.</param>
        /// <returns>傳回三角形類型名稱.</returns>
        public static string GetTriangle(string[] sideArr)
        {
            string result = string.Empty;
            int a = int.Parse(sideArr[0]);
            int b = int.Parse(sideArr[1]);
            int c = int.Parse(sideArr[2]);
            if (a + b > c && a + c > b && b + c > a)
            {
                if (a == b && a == c)
                {
                    result = "等邊三角形";
                }

                if (a == b || a == c || b == c)
                {
                    result = "等腰三角形";
                }
                else
                {
                    result = "一般三角形";
                }
            }
            else
            {
                result = "不構成三角形";
            }
            return result;
        }
    }
}      
  1. 然後在solution中添加一個UnitTestDemoTests測試項目,如圖所示,添加 => 建立項目之後選擇 測試 => 單元測試項目。
C# 單元測試(入門)
建立好測試項目之後,你會得到一個UnitTest1測試類模闆,即一個帶有[TestClass] attribute标記的類和一個帶有[TestMethod] attribute标記的空方法public void TestMethod1()。
  1. 單元測試項目無法自動通路它正在測試的類庫。 可以通過添加對類庫項目的引用來提供測試庫通路權限。 為此,請右鍵單擊UnitTestProject1項目,然後依次選擇“添加” > “引用”。在“引用管理器”對話框中,然後選擇 UnitTestDemo項目,如下圖中所示。

    

C# 單元測試(入門)

  在UnitTestDemoTests項目中添加UnitTestDemo項目的引用,現在我們的solution就具有了下圖所示的目錄結構。

C# 單元測試(入門)

  5. 在UnitTestDemoTests項目中的UnitTest1類中,将模闆提供的樣本單元測試代碼替換為以下代碼:

using UnitTest;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestDemoTests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod()]
        public void GetTriangle_Test()
        {
            string[] sideArr = {"5", "5", "5"};
            Assert.AreEqual("等邊三角形", UnitTestClass.GetTriangle(sideArr));
        }
    }
}      

  6. 生成UnitTestDemoTests測試項目,在生成項目後,測試項将出現在測試資料總管中。 如果測試資料總管視窗不可見,請選擇頂級 Visual Studio 菜單上的“測試”,然後依次選擇“視窗(Windows)”、“測試資料總管(Ctrl + E,T9)”,如圖所示。

  

C# 單元測試(入門)

  7. 在測試資料總管上可以看到剛剛所寫的測試方法,這樣在GetTriangle_Test單擊右鍵選擇“運作所標明的測試”就可以在Test Explorer裡看到單元測試的運作結果,如下圖所示。  

C# 單元測試(入門)
可以看到,我們在單元測試中提供的例子的期望是輸出“等邊三角形”,運作結果卻是“等腰三角形”。再看一看 GetTriangle() 函數的代碼,原來是在對在判斷三邊數值是等邊三角形之後沒有使用 else if 又用 if 判斷為等腰三角形了。通過這個簡單的單元測試就能夠發現一些意向不到的錯誤。不要以為這裡的bug很低級,類似的情況确實會在現實中發生。

  8. 把上面的錯誤更正後,再次運作TestMethod1()就會得到測試已認證的結果,如圖所示。

C# 單元測試(入門)

建立單元測試項目和測試方法,除了以上通過手動建立單元測試項目和根據你的要求進行編寫測試用例之外,還可以從你的項目的方法上直接生成單元測試項目和單元測試存根,那樣操作更加友善,速度也會更快一些。

2) 通過代碼直接生成單元測試項目和單元測試存根

  1. 在代碼編輯器視窗中,從上下文菜單右鍵單擊并選擇“建立單元測試”。
C# 單元測試(入門)
  1. 在建立單元測試視窗,選擇預設值,或更改用于建立并命名單元測試項目和單元測試的參數值。 單擊“确定”,建立單元測試項目。
C# 單元測試(入門)
這裡涉及測試架構的選擇,MSTest是VS自帶的測試架構。新的MS TEST現在是通過Nuget的包釋出了,目前MS釋出了兩個版本:
  • MS TEST V1:V1的版本依賴于一個包: MSTest.TestFramework
  • MS TEST V2:V2的版本依賴于兩個包: MSTest.TestFramework 和 MSTest.TestAdapter

    這兩個版本使用起來還是大同小異的,MSTest v2 主要是為了.net core準備的,當然也可以在.net framework上運作,并且在v1上新加入了一些擴充。

  1. 在生成的測試項目中,将測試代碼添加到對應單元測試方法中,以使單元測試有意義。
  2. 此後生成測試項目,并在測試資料總管中運作測試方法,得到測試結果(與上方步驟一緻)。

編寫測試代碼

你使用的單元測試架構和 Visual Studio IntelliSense 将指導你完成為代碼項目的單元測試編寫代碼。 若要在測試資料總管中運作,大多數架構要求你添加特定的屬性來識别單元測試方法。 架構還提供了一種方法,通常通過斷言語句或方法屬性,來訓示測試方法是否已認證或失敗。 其他屬性辨別可選的安裝方法,即在類初始化時和每個測試方法和每個拆卸方法之前的安裝方法,這些拆卸方法在每個測試方法之後和類被銷毀之前運作。

AAA(準備、執行、斷言)模式是編寫待測試方法的單元測試的常用方法:

  • 準備(Arrange),單元測試方法的準備部分初始化對象并設定傳遞給待測試方法的資料;
  • 執行(Act),執行部分調用具有準備參數的待測試方法;
  • 斷言(Assert),斷言部分驗證待測試方法的執行行為與預期相同。

如示例中驗證 UnitTest1.GetTriangle() 函數,我們編寫了一個測試來驗證方法的标準行為:

[TestMethod()]
public void GetTriangle_Test()
{
    // arrange  
    string[] sideArr = { "5", "5", "5" }; // 準備傳給待測試方法的資料
    string expected = "等邊三角形";

    // act  
    var actual = UnitTestClass.GetTriangle(sideArr); // 調用測試方法

    // assert  
    Assert.AreEqual(expected, actual); // 驗證待測試方法的執行結果是否與預期相同
}      

為單元測試設定逾時值:

在某些情況下(例如通過網絡擷取資料),常常不希望程式卡住而占用太多時間,通過設定測試方法的逾時時間,來測試一個方法是否在預期時間内執行。

[TestMethod()]
[Timeout(2000)] // 毫秒 要在單個測試方法上設定逾時時間
public void GetTriangle_Test()
{   ... 
}

[TestMethod()]
[Timeout(TestTimeout.Infinite)] // 毫秒 将逾時時間設定為允許的最大值
public void GetTriangle_Test()
{   ... 
}      

MSTest參數化測試:

什麼是參數化測試?

答:簡單的說,就是同樣的邏輯,根據輸入參數不同,給出不同的結果。因為隻是參數不同,是以并不希望把測試方法寫多遍,但是又希望對每個參數的測試成為一個獨立的測試用例。舉例說,假定我有一個數學計算的方法是把兩個整數相加求和,我希望證明這個方法對于任意兩個數都是通過的。

在MSTest中可以通過DataRow Attribute 來指定測試用例的參數,實作參數化測試:

/// <summary>
/// 相加(待測試方法)
/// </summary>
/// <param name="num1">數值1</param>
/// <param name="num2">數值2</param>
/// <returns>計算結果</returns>
public static int Add(int num1, int num2)
{
    return Math.Abs(num1 + num2);
}      
/// <summary>
/// 測試方法
/// </summary>
[TestMethod()]
[DataRow(10, 20)]
[DataRow(-2, -5)]
[DataRow(1, -2)]
[DataRow(5, null)]
public void Add_Test(int num1, int num2)
{
    Assert.AreEqual(UnitTestClass.Add(num1, num2), num1 + num2);
}      

測試了所有可能的情況,以達到更好的覆寫率。上方給出示例Add方法的單元測試運作測試結果如下圖所示。

C# 單元測試(入門)

測試結果:測試結果指出對兩個數相加操作的方法,目标方法還取了絕對值,與相應結果不符。

測試調試

可以使用測試資料總管為你的測試啟動調試會話。 使用 Visual Studio 調試程式可以無縫地逐句得使你在單元測試和所測試項目之間來回反複。 若要開始調試:

  1. 在 Visual Studio 編輯器中,在想要調試的一個或多個測試方法中設定斷點。
  2. 在測試資料總管中,選擇測試方法,然後點選右鍵從快捷菜單選擇“調試標明的測試”。
C# 單元測試(入門)

  3. 進入調試模式

F5 繼續。

F10 執行下一行代碼,但不執行任何函數調用。

F11 在執行進入函數調用後,逐條語句執行代碼。

Shift + F11 執行目前執行點所處函數的剩餘行。

Shift + F5 停止運作程式中的目前應用程式。可用于“中斷”模式和“運作”模式。

NUnit 測試架構

 1)下載下傳安裝NUnit插件

  我們在VS中選擇工具菜單欄下的擴充和更新,選擇聯機并在搜尋框中輸入NUnit。有2個版本的Nunit擴充卡,分别為NUnit 3.x(最新版為3.4.1)和NUnit 2.x(最新版為2.6.4),都支援Visual Studio 2012+。若想在VS2010中內建,需要安裝NUnit 2.6.4安裝包(可在官網下載下傳)與VS2010 NUnit整合插件下載下傳,下載下傳安裝完畢就能在 VS2010 的視圖=>其他視窗中看到 Visual Nunit (或使用快捷鍵Ctrl + F7),打開該視圖,将之拖到合适的位置。

 2)建立NUnit單元測試項目

 未完待續..

使用 Visual Studio 2017進行實時單元測試

Live Unit Testing 是 Visual Studio 2017 版本 15.3 中提供的一項技術,可在我們更改代碼,然後儲存的時候,它會自動生成自動測試,最後得出結果。

1)實時單元測試:

  • 讓你更有信心地對代碼進行重構和更改。 Live Unit Testing 在編輯代碼時自動執行所有受影響的測試,確定所做更改不會中斷測試。
  • 訓示單元測試是否充分覆寫代碼,并顯示未被單元測試覆寫的代碼。 Live Unit Testing 以圖形方式實時描繪代碼覆寫率,以便一眼就能看到每行代碼覆寫的測試數,目和未被任何單元測試覆寫的行。

2)進行實時單元測試:

  1. 在類庫項目中建立一個待測試方法,如下:
/// <summary>
/// 相加(待測試方法)
/// </summary>
/// <param name="num1"></param>
/// <param name="num2"></param>
/// <returns></returns>
public static int Add(int num1, int num2)
{
    return num1 + num2;
}      

  2. 根據以上建立單元測試項目的過程,建立一個單元測試項目(測試架構可以使用 Live Unit Testing 的 MSTest 測試架構(預設)。 還可使用 xUnit 和 NUnit 測試架構)。在測試項目添加對被測試類庫項目的引用來提供測試庫通路權限。

  3. 在測試項目類中,将模闆提供的樣本單元測試方法代碼替換為以下代碼:

[TestMethod()]
[DataRow(10, 20)]
[DataRow(-2, -5)]
[DataRow(1, -2)]
[DataRow(5, null)]
public void Add_Test(int num1, int num2)
{
    Assert.AreEqual(UnitTestClass.Add(num1, num2), num1 + num2);
}      

  4. 從頂級 Visual Studio 菜單中依次選擇“測試” > “Live Unit Testing” > “啟動” Visual Studio 啟動 Live Unit Testing,使其自動運作所有測試。

   

C# 單元測試(入門)

  5.  完成運作測試後,“測試資料總管” 顯示整體結果和各個測試的結果。 此外,代碼視窗以圖形方式顯示測試代碼覆寫率和測試結果。 如下圖所示,三項測試均已成功執行。 它還顯示測試中已覆寫 Add() 方法中的所有代碼路徑,并已成功執行這些測試(用綠色複選标記“✓”訓示)。 UnitTestClass.cs 中的其他方法有部分代碼沒有代碼覆寫率(用藍線“—”訓示)

C# 單元測試(入門)

  還可通過在代碼視窗中選擇一個特定的代碼覆寫率圖示來獲得有關測試覆寫率和測試結果的更多詳細資訊。 若要檢視此詳細資訊,請執行以下操作:

  單擊行上的綠色複選标記“✓”, 如下圖所示,Live Unit Testing 訓示隻有一個測試覆寫該行的代碼,并且都已成功執行。

C# 單元測試(入門)

  Live Unit Testing 中“—”辨別的主要問題是代碼覆寫率不完整,可以通過添加測試方法或改變測試參數,如下圖,可以看到代碼覆寫率已擴充到 GetTriangle() 的每一行代碼。 

  在你修改源代碼時,Live Unit Testing 将自動執行新增的和修改後的測試。

C# 單元測試(入門)

  6. 處理測試失敗:

  将 Add() 做些許修改,修改為計算兩數相加的絕對值,在儲存後,Live Unit Testing 訓示 Add() 方法執行失敗,如下圖所示:  

C# 單元測試(入門)

7. 停止實時單元測試:

C# 單元測試(入門)

使用 IntelliTest 為你的代碼生成單元測試

IntelliTest 浏覽你的 .NET 代碼,以生成測試資料和單元測試套件。 對于代碼中的每個語句,将生成執行該語句的測試輸入。 為代碼中的每個條件分支執行案例分析。 例如,分析 if 語句、斷言和可能引發異常的所有操作。 此分析用于為你的每個方法生成參數化單元測試的測試資料,進而建立具有較高代碼覆寫率的單元測試。

當你運作 IntelliTest 時,你可輕松看到哪些測試會失敗,并可添加任何必要的代碼來修複它們。 你可選擇要儲存到測試項目中的已生成測試,以提供回歸套件。 當你更改代碼時,重新運作 IntelliTest,以使生成的測試與你的代碼更改同步。

IntelliTest 僅可用于 C# 且不支援 x64 配置。

IntelliTest 入門

若要生成單元測試,你的類型必須是公共類。 否則,先建立單元測試,然後再生成它們。

  1. 在 Visual Studio 中打開解決方案。 然後打開包含你要測試的方法的類檔案。
  2. 在代碼中右鍵單擊一種方法并選擇“建立 IntelliTest”,為方法中的代碼建立生成單元測試項目。
C# 單元測試(入門)

  接受預設格式以生成測試,或更改項目和測試的命名方式。 你可以建立新的測試項目或将你的測試儲存到現有項目。

C# 單元測試(入門)

  3. 建立測試項目成功之後,選擇上圖中“運作 IntelliTest”,為方法中的代碼運作IntelliTest單元測試項目。

  IntelliTest 使用不同的輸入多次運作你的代碼。 每次運作都會在表中表示出來,顯示輸入測試資料以及産生的輸出或異常。

C# 單元測試(入門)

  要為一個類中的所有公共方法生成單元測試,隻需右鍵單擊類而不是特定的方法。 然後選擇“運作 IntelliTest”。 使用“浏覽結果”視窗中的下拉清單,顯示類中每個方法的單元測試和輸入資料。