引言 在現代化的軟體開發中,單元測試和集成測試是確保代碼質量和可靠性的關鍵部分。ASP.NET Core 社區內提供了強大的單元測試框架,xUnit 是其中之一,它提供了簡單、清晰和強大的測試功能,編寫單元測試有許多優點;有助於回歸、提供文檔及輔助良好的設計。下麵幾節我們來深入淺出探討如何使用 xU ...
引言
在現代化的軟體開發中,單元測試和集成測試是確保代碼質量和可靠性的關鍵部分。ASP.NET Core
社區內提供了強大的單元測試框架,xUnit
是其中之一,它提供了簡單、清晰和強大的測試功能,編寫單元測試有許多優點;有助於回歸、提供文檔及輔助良好的設計。下麵幾節我們來深入淺出探討如何使用 xUnit
進行 ASP.NET Core
應用程式的單元測試和集成測試。
內容大綱:
xUnit 簡介
xUnit.net
是一個免費、開源、面向社區的.NET
單元測試工具。由NUnit v2
的原始發明者編寫,xUnit.net
是用於C#
和F#
(其他.NET
語言可能也可以使用,但不受支持)的最新技術單元測試。xUnit.net
可與Visual Studio
、Visual Studio Code
、ReSharper
、CodeRush
和TestDriven.NET
一起使用。它是.NET
基金會的一部分,並遵守其行為準則。其許可協議為 Apache 2(為 OSI 批准的許可協議)。
創建單元測試項目
在單元測試中通常要遵循AAA
模式,也就是 Arrange
、Act
、Assert
,這是一種常見的測試組織結構。
Arrange(準備)
: 在這個階段,將設置測試的前提條件,初始化對象、設置輸入參數等。簡單講就是準備測試環境,確保被測代碼在正確的上下文中執行。Act(執行)
: 在這個階段,會執行要測試的代碼或方法。這是針對被測代碼的實際調用或操作。Assert
: 在這個階段,會驗證被測代碼的行為是否符合預期。檢查實際結果與期望結果是否一致,如果不一致則測試失敗。
示例:
[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator();
// Act
var actual = stringCalculator.Add("");
// Assert
Assert.Equal(0, actual);
}
可讀性是編寫單元測試最重要的方面之一,在測試中分離這些操作 都明確地突出調用代碼所需的依賴項、調用代碼的方式以及嘗試斷言的內容,讓測試儘可能具有可讀性。
好了理解了這個核心概念我們可以先創建項目一步步的練習了。
用 VS 創建單元測試項目
在項目創建完之後我們可以簡單瀏覽一下 xUnit
單元測試項目裝了那些 nuget
依賴,做到對項目有個簡單的瞭解
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
下麵我們創建一個簡單的數據計算類。
- 創建數學計算類
public class MathCalculator
{
public int Add(int a, int b)
{
return a + b;
}
}
- 創建數據計算測試類
public class MathCalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnSum()
{
// Arrange
var calculator = new MathCalculator();
// Act
var result = calculator.Add(3, 5);
// Assert
Assert.Equal(8, result);
}
}
測試一下,測試類庫右鍵->運行測試
可以看到 我們的單元測試通過。
單元測試命名規範
本著代碼自文檔的原則,測試的名稱建議應包括三個部分:
- 要測試的方法的名稱。
- 測試的方案。
- 調用方案時的預期行為。
示例
[Fact]
public void Add_TwoNumbers_ReturnSum()
{
// Arrange
var calculator = new MathCalculator();
// Act
var result = calculator.Add(3, 5);
// Assert
Assert.Equal(8, result);
}
要測試的方法名稱是 MathCalculator
中的 Add 方法,測試的方案是傳兩個數,預期是返回兩數之和 按照上面的測試名稱的命名規則可以命名為Add_TwoNumbers_ReturnSum
。
單元測試最佳命名規範應該包括三個關鍵部分:要測試的方法的名稱、測試的場景,以及調用該場景時的預期行為。良好的命名標準能清晰表達測試意圖,提供有效文檔,便於他人理解代碼行為和快速定位問題。
將方法標記為測試方法在
xUnit
中有兩個屬性,Fact
和Theory
Fact 屬性
在方法上我們看到有一個 Attribute
[Fact] ,[Fact] 屬性是 xUnit 中最基本的測試屬性之一,用於標記一個方法作為一個無需參數且不返回任何內容的測試方法。被標記為 [Fact] 的方法將會被 xUnit
框架識別並執行.
Theory 屬性
Theory
屬性用於標記一個測試方法,該方法可以接受參數並運行多次,每次運行時使用不同的參數值。Theory 屬性通常用於數據驅動測試,允許在同一個測試方法中使用不同的輸入數據進行測試.
InlineData 屬性
[InlineData] 屬性指定這些輸入 Theory 標記的測試方法的參數值。
示例:
[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
InlineData
適用於靜態、硬編碼的測試數據集合,適合於簡單且固定的測試場景。
MemberData 屬性
MemberData
屬性是 xUnit
中用於數據驅動測試的一種方式,它允許從一個欄位、屬性或方法中獲取測試數據,並將這些數據傳遞給測試方法進行多次測試。通過 MemberData
屬性,可以更靈活地管理和提供測試數據,適用於需要動態生成測試數據的情況。
使用方式
- 標記測試方法:使用 [Theory] 屬性標記測試方法,以便接受從 MemberData 屬性提供的測試數據。
- 準備測試數據:創建一個公共靜態欄位、屬性或方法,該欄位、屬性或方法返回一個 IEnumerable<object[]> 對象,其中每個 object[] 對象代表一組測試數據。
- 傳遞測試數據:在 MemberData 屬性中指定要使用的數據源,從而將數據傳遞給測試方法。
示例
public static IEnumerable<object[]> GetComplexTestData()
{
yield return new object[] { 10, 5, 15 }; // 測試數據 1
yield return new object[] { -3, 7, 4 }; // 測試數據 2
yield return new object[] { 0, 0, 0 }; // 測試數據 3
// 可以根據需要繼續添加更多的測試數據
}
[Theory]
[MemberData(nameof(GetComplexTestData))]
public void Add_TwoNumbers_ReturnsSumofNumbers01(int first, int second, int sum)
{
// Arrange
var calculator = new MathCalculator();
// Act
var result = calculator.Add(first, second);
// Assert
Assert.Equal(sum, result);
}
MemberData
適用於動態、靈活的測試數據集合,適合於需要從外部源動態獲取測試數據的情況。
自定義屬性
除了上面提到的 InlineData
和MemberData
之外還可以有更加靈活的方式繼承DataAttribute
實現自定義的Attribute
。
我們來做一個實現和上面一樣的需求
- 實現 Custom Attribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CustomDataAttribute : DataAttribute
{
private readonly int _first;
private readonly int _second;
private readonly int _sum;
public CustomDataAttribute(int first, int second, int sum)
{
_first = first;
_second = second;
_sum = sum;
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
yield return new object[] { _first, _second, _sum };
}
}
- 用例
[Theory]
[CustomData(1, 2, 3)]
[CustomData(2, 3, 5)]
public void Add_TwoNumbers_ReturnSum03(int num1, int num2, int expectedSum)
{
// Arrange
var calculator = new MathCalculator();
// Act
var result = calculator.Add(num1, num2);
// Assert
Assert.Equal(expectedSum, result);
}
自定義屬性相較於使用 InlineData
和 MemberData
有以下優勢:
-
靈活性:自定義屬性允許您實現更複雜的邏輯來動態生成測試數據,可以從不同數據源中獲取數據,實現更靈活的數據驅動測試。
-
重用性:通過自定義屬性,您可以將相同的測試數據邏輯應用於多個測試方法,提高測試代碼的重用性和可維護性。
-
可擴展性:自定義屬性可以根據需求進行定製和擴展,適應不同的測試場景和數據需求,使得測試數據的生成更具靈活性。
-
可讀性:通過自定義屬性,可以使測試代碼更具可讀性和表達力,更清晰地表達測試數據的來源和意圖。
儘管使用 InlineData
和 MemberData
可以滿足大多數簡單的測試數據需求,但當需要更複雜的數據生成邏輯、數據源、或者對測試數據進行處理時,使用自定義屬性會更具優勢,能夠更好地滿足個性化的測試需求。
在測試中應避免邏輯
[Theory]的出現就是為了避免我們在單元測試時編寫一些額外的邏輯,造成測試之外的一些錯誤。
編寫單元測試時,請避免手動字元串串聯、邏輯條件(例如 if、while、for 和 switch)以及其他條件。
錯誤示範:
[Fact]
public void Add_TwoNumbers_ReturnsSumofNumbers02()
{
// Arrange
var calculator = new MathCalculator();
var testData = new List<(int, int, int)>
{
(1, 2, 3),
(2, 3, 5),
(3, 4, 7)
};
// Act & Assert
foreach (var (first, second, sum) in testData)
{
var result = calculator.Add(first, second);
Assert.Equal(sum, result);
}
}
此處用了 forEach
迴圈來批量斷言,違反了單元測試的最佳實踐。
測試中應避免邏輯的好處是:
- 降低在測試中引入 bug 的可能性。
- 專註於最終結果,而不是實現細節。
ITestOutputHelper 控制台輸出
在 xUnit 中我們利用 Console.WriteLine
輸出時發現什麼也不會顯示,在 xUnit 單元測試項目中我們需要利用ITestOutputHelper
。
ITestOutputHelper
是 xUnit 中的一個介面,用於在單元測試中輸出信息。通過 ITestOutputHelper
,您可以在測試運行時將調試信息、日誌信息等輸出到測試結果中,方便調試和查看測試過程中的輸出信息。
調試
再要測試的方法上右鍵選擇調試測試,或者點擊方法上面的小點
最後
本篇文章簡單的講了單元測試的基礎知識,讓大家先對單元測試有個基本的概念,這些用在具體的項目中顯然是不夠的,後面的章節我們聊一下 TDD
,Fake
管理,Log
日誌輸出,單元測試覆蓋率,WebApi
的集成測試,DependencyInjection
,Bogus
,還有 Devops
的單元測試等知識。
本文來自博客園,作者:董瑞鵬,轉載請註明原文鏈接:https://www.cnblogs.com/ruipeng/p/18112221