第26天:單元測試與測試驅動開發 (TDD)
課程簡介
今天的課程將介紹 單元測試(Unit Testing) 和 測試驅動開發(Test-Driven Development,TDD)。單元測試是軟體開發中一項重要的技術,它可以幫助開發人員確保應用程式中的每個功能都能正確運作。TDD 則是一種開發流程,要求開發者在編寫實際程式碼之前先撰寫測試。這種方法可以提升代碼的質量和穩定性。
學習目標
- 理解單元測試的基本概念與用途
- 學習如何使用 .NET 的 xUnit 框架撰寫單元測試
- 掌握 TDD 的流程與應用
- 實作一個以 TDD 驅動開發的小專案
課程內容
1. 單元測試的基本概念
單元測試 是指對應用程式中的最小可測單位進行獨立測試,以確保每個單元(通常是一個方法或類別)在不同情況下都能正確執行。
- 目的:檢驗程式中每個單位的正確性,找出潛在的錯誤,並在程式碼變更後快速檢查是否引入了新問題。
- 優勢:
- 更早發現程式錯誤
- 提高程式碼的可靠性
- 支援重構,讓開發者更有信心進行修改
2. 使用 xUnit 撰寫單元測試
在 .NET 中,常用的單元測試框架包括 xUnit、NUnit 和 MSTest。其中,xUnit 是一個現代化且廣泛使用的測試框架。
安裝 xUnit
- 在 Visual Studio 中建立一個新的 單元測試專案。
- 安裝 xUnit 和 xUnit.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;
}
}
我們可以針對此類別撰寫單元測試來檢驗 Add
和 Subtract
方法是否正確運作。
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 流程:
- 撰寫測試:根據需求撰寫一個尚未通過的測試。
- 實作程式碼:撰寫最少的程式碼來通過該測試。
- 重構:優化程式碼,並確保測試仍然通過。
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 等第三方工具。
單元測試的最佳實踐
- 保持測試簡潔:每個測試應只測試一個行為,避免複雜的測試邏輯。
- 避免測試私有方法:單元測試應針對公共介面進行,私有方法的測試應通過測試公開方法來間接測試。
- 獨立測試:每個測試應該是獨立的,不應依賴其他測試的結果。
- 持續整合與自動化:將單元測試納入持續整合 (CI) 流程,讓測試自動執行並驗證程式碼質量。
6. 實作練習
- 撰寫單元測試:
- 使用 xUnit 撰寫測試,為現有專案中的功能建立單元測試。
- 確保測試覆蓋了各種邊界條件和例外狀況。
- 實作 TDD 流程:
- 建立一個簡單的小專案,並使用 TDD 方法來驅動開發流程。先撰寫測試,再實作程式碼,並反覆進行測試與重構。
- **
使用 Mock 進行依賴測試**:
- 模擬應用程式中的外部依賴(如資料庫或 API),使用 Moq 或其他 Mock 庫來撰寫單元測試。
教學重點
- 理解單元測試的概念與其對應用程式開發的重要性。
- 掌握 xUnit 的基本用法,並撰寫單元測試。
- 探索 TDD 的開發流程,並應用於實際專案。
- 學會如何使用 Mock 庫來處理依賴注入的測試。
學習單元測試與 TDD 不僅能提高程式碼的穩定性,還能讓開發者更有信心進行程式碼的修改和擴展。