單元測試的基本結構與三種 .NET 測試框架的屬性標籤對照,包含各自的生命週期事件與參數化測試寫法。
3A 原則
Arrange:安排/準備
在這一步你準備所有測試要用到的東西。包括被測試物件 (SUT, System Under Test)、依賴(例如模擬物件 mock、stub)、輸入參數、預期結果,設定測試環境或上下文等等。
Act:執行/行動
在這一步調用你的程式碼中要測試的方法或函式,做實際的操作。就是把輸入傳進去,讓它執行。
Assert:斷言/驗證
最後檢查操作結果是否如你預期的那樣。把實際結果和預期結果比較,有沒有錯誤。這部份可以用測試框架提供的 assert 函數或方法。
Unit Test Framework
社群用量比較 xUnit > NUnit > MSTest,其中 xUnit 具備高靈活性與現代特性,更易與持續整合(CI/CD)工具整合。
(1) 字串比較
比較是否(不)相符:
| 框架 | 方法 |
|---|---|
| nUnit | Assert.AreEqual(Assert.AreNotEqual) |
| xUnit | Assert.Equal(Assert.NotEqual) |
比較字串 1 是否(不)存在在字串 2:
| 框架 | 方法 |
|---|---|
| nUnit | |
| xUnit | Assert.Contains(Assert.NotContains) |
(2) 布林比較
Assert.True:比較結果是否為 trueAssert.False:比較結果是否為 false
(3) Null Reference 比較
Assert.Null(Assert.NotNull):比較物件是否(不)為 null
(4) 物件比較
Assert.Same(Assert.NotSame):比較兩物件是否(不)相同
(5) 數值比較
Assert.InRange(Assert.NotInRange):比較數值是否(不)在範圍中
Event Hooks(測試生命週期事件)
是指測試框架提供的一組方法,這些方法可以在測試生命週期中的特定時間點執行某些操作。
一、MSTest
TestClass:標示測試類別屬性。TestMethod:標示這是測試方法。AssemblyInitialize:所有測試執行前只會跑一次。方法需是靜態。ClassInitialize:在測試類別中的所有測試方法執行之前執行一次。方法需是靜態。ClassCleanup:在測試類別中的所有測試方法執行後執行一次。方法需是靜態。TestInitialize:在每個測試方法執行前執行一次。TestCleanup:在每個測試方法執行後執行一次。AssemblyCleanup:所有測試結束後跑一次。TestCategory:設定測試分類。範例:TestCategory("Math")。DataRow:執行多筆資料的測試。DataTestMethod:執行多筆資料的測試。
範例:
[TestClass]
public class CalculatorTests
{
[DataTestMethod]
[DataRow(1, 2, 3)]
[DataRow(5, 5, 10)]
[DataRow(-1, 1, 0)]
public void Add_TestMultipleInputs(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.AreEqual(expected, result);
}
}
Ignore:忽略此測試,不執行。ExpectedException:驗證某個測試會丟出指定的例外。
範例:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ThrowsException()
{
throw new ArgumentNullException();
}
二、NUnit
Test:標示這是測試方法。Ignore:跳過此測試。SetUp:在每個測試方法之前執行。TearDown:在每個測試方法之後執行。OneTimeSetUp:在所有測試方法之前執行一次。OneTimeTearDown:在所有測試方法之後執行一次。TestCase:傳遞不同參數組合。
範例:
[TestCase(1, 2, 3)]
[TestCase(-1, -2, -3)]
public void Add_WithVariousInputs_ReturnsExpected(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.AreEqual(expected, result);
}
Category:可將測試方法分類,方便篩選執行。範例:[Category("Math")]。TestFixture:標記的類別可以與多個測試共享同一個上下文。
範例:
[TestFixture(1, 2, 3)]
[TestFixture(2, 3, 5)]
public class AddTests
{
private readonly int _a, _b, _expected;
public AddTests(int a, int b, int expected)
{
_a = a;
_b = b;
_expected = expected;
}
[Test]
public void TestAddition()
{
var calc = new Calculator();
Assert.AreEqual(_expected, calc.Add(_a, _b));
}
}
三、xUnit
Fact:標記一個簡單的測試方法。Theory:傳入不同的資料進行多次測試。
範例:
[Theory]
[InlineData(1, 2, 3)]
[InlineData(3, 4, 7)]
public void Add_ValidNumbers_ReturnsCorrectSum(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.Equal(expected, result);
}
Skip:跳過測試。Trait:為測試分類。