C# 單元測試 基礎示範

C# NUnit 單元測試通過 平時很常聽到單元測試,但是到底要怎麼做?該怎麼寫?本篇來介紹一次,讓大家可以開始撰寫單元測試。

建立專案和單元測試專案

這裡建立一個範例指令視窗程式,名稱叫做 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

留言

張貼留言

如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com