文章

第26天:單元測試與測試驅動開發 (TDD)

課程簡介

今天的課程將介紹 單元測試(Unit Testing)測試驅動開發(Test-Driven Development,TDD)。單元測試是軟體開發中一項重要的技術,它可以幫助開發人員確保應用程式中的每個功能都能正確運作。TDD 則是一種開發流程,要求開發者在編寫實際程式碼之前先撰寫測試。這種方法可以提升代碼的質量和穩定性。


學習目標

  • 理解單元測試的基本概念與用途
  • 學習如何使用 .NET 的 xUnit 框架撰寫單元測試
  • 掌握 TDD 的流程與應用
  • 實作一個以 TDD 驅動開發的小專案

課程內容

1. 單元測試的基本概念

單元測試 是指對應用程式中的最小可測單位進行獨立測試,以確保每個單元(通常是一個方法或類別)在不同情況下都能正確執行。

  • 目的:檢驗程式中每個單位的正確性,找出潛在的錯誤,並在程式碼變更後快速檢查是否引入了新問題。
  • 優勢
    • 更早發現程式錯誤
    • 提高程式碼的可靠性
    • 支援重構,讓開發者更有信心進行修改

2. 使用 xUnit 撰寫單元測試

在 .NET 中,常用的單元測試框架包括 xUnitNUnitMSTest。其中,xUnit 是一個現代化且廣泛使用的測試框架。

安裝 xUnit

  1. 在 Visual Studio 中建立一個新的 單元測試專案
  2. 安裝 xUnitxUnit.Runner.VisualStudio NuGet 套件。

撰寫 xUnit 測試

假設我們有一個簡單的數學類別 MathService

1
2
3
4
5
6
7
8
9
10
11
12
public class MathService
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        return a - b;
    }
}

我們可以針對此類別撰寫單元測試來檢驗 AddSubtract 方法是否正確運作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MathServiceTests
{
    private readonly MathService _mathService;

    public MathServiceTests()
    {
        _mathService = new MathService();
    }

    [Fact]
    public void Add_WhenCalled_ReturnsCorrectSum()
    {
        // Arrange
        int a = 5;
        int b = 3;

        // Act
        int result = _mathService.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }

    [Fact]
    public void Subtract_WhenCalled_ReturnsCorrectDifference()
    {
        // Arrange
        int a = 10;
        int b = 4;

        // Act
        int result = _mathService.Subtract(a, b);

        // Assert
        Assert.Equal(6, result);
    }
}

測試結構:

  • Arrange:設置測試所需的資料。
  • Act:調用需要測試的功能。
  • Assert:驗證結果是否符合預期。

在 Visual Studio 中執行測試,並檢查結果是否通過。


3. 測試驅動開發(TDD)

測試驅動開發 是一種軟體開發方法,要求開發者在編寫功能實作之前,先撰寫測試。

TDD 流程:

  1. 撰寫測試:根據需求撰寫一個尚未通過的測試。
  2. 實作程式碼:撰寫最少的程式碼來通過該測試。
  3. 重構:優化程式碼,並確保測試仍然通過。

TDD 範例:

假設我們的需求是撰寫一個 Calculator 類別,首先撰寫測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CalculatorTests
{
    [Fact]
    public void Add_WhenCalled_ReturnsSum()
    {
        // Arrange
        var calculator = new Calculator();

        // Act
        int result = calculator.Add(2, 3);

        // Assert
        Assert.Equal(5, result);
    }
}

接著,我們編寫一個最簡單的 Calculator 類別來通過測試:

1
2
3
4
5
6
7
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

執行測試,確認是否通過。若通過測試,我們可以繼續擴展類別,並重複這一流程。


4. Mock 物件與依賴注入的測試

在許多情況下,應用程式的某些類別會依賴外部資源(例如:資料庫、API)。為了在單元測試中模擬這些外部依賴,我們可以使用 Mock 技術。

使用 Moq 進行 Mock 測試

Moq 是 .NET 中一個常用的 Mock 庫,可以模擬介面或類別的行為,並幫助我們進行單元測試。

假設我們有一個依賴資料庫的服務類別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface IUserRepository
{
    User GetUserById(int id);
}

public class UserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public User GetUserDetails(int id)
    {
        return _userRepository.GetUserById(id);
    }
}

在測試中,我們可以使用 Moq 來模擬 IUserRepository 的行為,而不需要實際訪問資料庫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceTests
{
    [Fact]
    public void GetUserDetails_WhenCalled_ReturnsUser()
    {
        // Arrange
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(repo => repo.GetUserById(1))
                .Returns(new User { Id = 1, Name = "John Doe" });

        var userService = new UserService(mockRepo.Object);

        // Act
        var result = userService.GetUserDetails(1);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(1, result.Id);
        Assert.Equal("John Doe", result.Name);
    }
}

5. 測試覆蓋率與最佳實踐

測試覆蓋率

測試覆蓋率是一個衡量指標,用來表示程式碼中有多少比例被測試覆蓋。雖然高覆蓋率不一定代表高質量,但它能幫助我們識別未測試的程式碼區域。

  • 覆蓋率工具:Visual Studio 提供內建的覆蓋率分析工具,也可以使用像 Coverlet 等第三方工具。

單元測試的最佳實踐

  1. 保持測試簡潔:每個測試應只測試一個行為,避免複雜的測試邏輯。
  2. 避免測試私有方法:單元測試應針對公共介面進行,私有方法的測試應通過測試公開方法來間接測試。
  3. 獨立測試:每個測試應該是獨立的,不應依賴其他測試的結果。
  4. 持續整合與自動化:將單元測試納入持續整合 (CI) 流程,讓測試自動執行並驗證程式碼質量。

6. 實作練習

  1. 撰寫單元測試
    • 使用 xUnit 撰寫測試,為現有專案中的功能建立單元測試。
    • 確保測試覆蓋了各種邊界條件和例外狀況。
  2. 實作 TDD 流程
    • 建立一個簡單的小專案,並使用 TDD 方法來驅動開發流程。先撰寫測試,再實作程式碼,並反覆進行測試與重構。
  3. **

使用 Mock 進行依賴測試**:

  • 模擬應用程式中的外部依賴(如資料庫或 API),使用 Moq 或其他 Mock 庫來撰寫單元測試。

教學重點

  • 理解單元測試的概念與其對應用程式開發的重要性。
  • 掌握 xUnit 的基本用法,並撰寫單元測試。
  • 探索 TDD 的開發流程,並應用於實際專案。
  • 學會如何使用 Mock 庫來處理依賴注入的測試。

學習單元測試與 TDD 不僅能提高程式碼的穩定性,還能讓開發者更有信心進行程式碼的修改和擴展。

本文章以 CC BY 4.0 授權