平時很常聽到單元測試,但是到底要怎麼做?該怎麼寫?本篇來介紹一次,讓大家可以開始撰寫單元測試。
單元測試框架也是很重要的一點,目前主流的就是 NUnit 和 xUnit ,而 MSTest 使用的人就比較少,不太建議使用。這裡使用 NUnit 做示範,建立名稱叫做 Calculator.UnitTests 的單元測試專案。
在平時開發時應該要將各「功能」拆分和簡化,一個 Method 中只有一個最小的功能,這稱為單一職責原則 (Single Responsibility Principle),能夠很好的方便我們做測試。最好測試的 Method 就是 pure function (純函數),代表同樣的輸入一定會產生同樣的輸出,不會有任何額外的副作用,不會改到外部的變數,也不會輸出檔案、呼叫 API 之類的,就只有實現一個功能。
這裡使用兩個最簡單的功能做示範,就是「加法」和「減法」:
在類別名稱上按下滑鼠右鍵 > 建立單元測試
測試架構選擇 NUnit3 ,測試專案選擇我們建立好的 Calculator.UnitTests ,命名空間設定為和單元測試一樣的 .UnitTests ,點選確定
就看到已經建立好單元測試類別了:
而在測試方法中我們習慣使用 Arrange, Act, Assert 的方式來撰寫單元測試:
測試範例:
完整測試示範:
使用滑鼠右鍵點擊 出現選單 > 執行測試(Ctrl + R, T)
所有測試都通過,這樣之後再寫新功能或重構時就可以時不時執行一次,確保沒有把舊的功能改壞。不過要注意的是需要確保測試案例是符合實際情況,免得測試都過結果上線時發現和想像的測試案例都不同,假成功(flase positive)。
另外在執行單元測試時應該都是幾秒鐘內(應該不會超過 5 秒)就執行完全部的單元測試,如果時間過久就要確認是否不小心寫成整合測試,有實際的呼叫 API 、讀取檔案等等。
延伸閱讀:
在 C# 單元測試中使用 Fluent Assertions ,用更容易閱讀的方式寫測試
參考資料:
NUnit Documentation
建立專案和單元測試專案
這裡建立一個範例指令視窗程式,名稱叫做 Calculator ,第一次建立專案時會同時建立解決方案(Solution)和專案(Project),名稱都叫做 Calculator 。然後再建立一個單元測試的專案,這裡單元測試的專案名稱通常會是原本的專案名稱再加上「.UnitTests」,或是直接將專案名稱加上「Tests」作為單元測試專案的名稱。筆者比較喜歡前者的做法,因為之後還可以建立其他測試專案,例如 IntegrationTests ,不過都沒有硬性規定。單元測試框架也是很重要的一點,目前主流的就是 NUnit 和 xUnit ,而 MSTest 使用的人就比較少,不太建議使用。這裡使用 NUnit 做示範,建立名稱叫做 Calculator.UnitTests 的單元測試專案。
撰寫功能
有許多人推崇 TDD(Test-Driven Development 測試驅動開發),強調先寫測試,再寫功能,不過對於一開始寫測試的我們來說先晚點再說,我們應該要先踏出開始測試的第一步。在平時開發時應該要將各「功能」拆分和簡化,一個 Method 中只有一個最小的功能,這稱為單一職責原則 (Single Responsibility Principle),能夠很好的方便我們做測試。最好測試的 Method 就是 pure function (純函數),代表同樣的輸入一定會產生同樣的輸出,不會有任何額外的副作用,不會改到外部的變數,也不會輸出檔案、呼叫 API 之類的,就只有實現一個功能。
這裡使用兩個最簡單的功能做示範,就是「加法」和「減法」:
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}
建立單元測試類別
在 Visual Studio 中可以產生測試,不過需要先安裝 NUnit Test Generator VS2022 的套件才可以自動產生 NUnit 的單元測試類別,如果是使用 Enterprise 版本的 Visual Studio 甚至還可以使用 IntelliTest 功能產生自動測試內容。在類別名稱上按下滑鼠右鍵 > 建立單元測試
測試架構選擇 NUnit3 ,測試專案選擇我們建立好的 Calculator.UnitTests ,命名空間設定為和單元測試一樣的 .UnitTests ,點選確定
就看到已經建立好單元測試類別了:
using NUnit.Framework;
using Calculator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Calculator.UnitTests
{
[TestFixture()]
public class CalculatorTests
{
[Test()]
public void AddTest()
{
Assert.Fail();
}
[Test()]
public void SubtractTest()
{
Assert.Fail();
}
}
}
撰寫單元測試
在 C# 中我們通常會以大駝峰(PascalCase)的方式替 Method 命名,例如 AddNumber 。不過在單元測試中的類別名稱比較特別,大家習慣使用「原始方法名稱_測試案例_預期結果」的方式來替測試方法命名,例如 Add_TwoPositiveIntegers_ReturnsCorrectSum而在測試方法中我們習慣使用 Arrange, Act, Assert 的方式來撰寫單元測試:
[Test]
public void Add_TwoPositiveIntegers_ReturnsCorrectSum()
{
// Arrange 初始化測試、準備測試資料
// Act 執行測試
// Assert 驗證測試結果
}
測試範例:
[Test]
public void Add_TwoPositiveIntegers_ReturnsCorrectSum()
{
// Arrange 初始化測試、準備測試資料
var calculator = new Calculator();
var a = 1;
var b = 2;
var expected = 3;
// Act 執行測試
var actual = calculator.Add(a, b);
// Assert 驗證測試結果
Assert.AreEqual(expected, actual);
}
完整測試示範:
namespace Calculator.UnitTests;
[TestFixture]
public class CalculatorTests
{
[Test]
public void Add_TwoPositiveIntegers_ReturnsCorrectSum()
{
// Arrange 初始化測試、準備測試資料
var calculator = new Calculator();
var a = 1;
var b = 2;
var expected = 3;
// Act 執行測試
var actual = calculator.Add(a, b);
// Assert 驗證測試結果
Assert.AreEqual(expected, actual);
}
[Test]
public void Add_TwoNegativeIntegers_ReturnsCorrectSum()
{
// Arrange
var calculator = new Calculator();
var a = -1;
var b = -2;
var expected = -3;
// Act
var actual = calculator.Add(a, b);
// Assert
Assert.AreEqual(expected, actual);
}
[Test]
public void SubtractTest_TwoPositiveIntegers_ReturnsCorrectDifference()
{
// Arrange
var calculator = new Calculator();
var a = 1;
var b = 2;
var expected = -1;
// Act
var actual = calculator.Subtract(a, b);
// Assert
Assert.AreEqual(expected, actual);
}
}
使用滑鼠右鍵點擊 出現選單 > 執行測試(Ctrl + R, T)
所有測試都通過,這樣之後再寫新功能或重構時就可以時不時執行一次,確保沒有把舊的功能改壞。不過要注意的是需要確保測試案例是符合實際情況,免得測試都過結果上線時發現和想像的測試案例都不同,假成功(flase positive)。
另外在執行單元測試時應該都是幾秒鐘內(應該不會超過 5 秒)就執行完全部的單元測試,如果時間過久就要確認是否不小心寫成整合測試,有實際的呼叫 API 、讀取檔案等等。
延伸閱讀:
在 C# 單元測試中使用 Fluent Assertions ,用更容易閱讀的方式寫測試
參考資料:
NUnit Documentation
感謝教學~
回覆刪除