Claude-skill-registry dotnet-testing-advanced-xunit-upgrade-guide
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dotnet-testing-advanced-xunit-upgrade-guide" ~/.claude/skills/majiayu000-claude-skill-registry-dotnet-testing-advanced-xunit-upgrade-guide && rm -rf "$T"
manifest:
skills/data/dotnet-testing-advanced-xunit-upgrade-guide/SKILL.mdsource content
xUnit 升級指南:從 2.9.x 到 3.x
適用情境
當被要求執行以下任務時,請使用此技能:
- 將現有 xUnit 2.x 測試專案升級到 xUnit 3.x
- 評估 xUnit 升級的影響範圍
- 解決 xUnit 升級過程中的編譯錯誤
- 使用 xUnit 3.x 新功能改進測試
核心概念
套件命名變革
xUnit v3 採用全新的套件命名策略:
| v1~v2 套件名稱 | v3 套件名稱 | 說明 |
|---|---|---|
| | 主要測試框架 |
| | 斷言函式庫 |
| | 核心元件 |
| (移除) | 不再需要 |
| (3.x.y) | 測試執行器 |
重要:使用
xunit.v3 套件名稱,不是 xunit。
最低運行時需求
xUnit 3.x 的嚴格要求:
- .NET Framework 4.7.2+ 或
- .NET 8.0+ (推薦)
不支援的版本:
- .NET Core 3.1
- .NET 5、6、7
破壞性變更清單
1. 測試專案變成可執行檔
<!-- xUnit 2.x (Library) --> <PropertyGroup> <OutputType>Library</OutputType> </PropertyGroup> <!-- xUnit 3.x (Exe) - 必須變更 --> <PropertyGroup> <OutputType>Exe</OutputType> </PropertyGroup>
2. async void 測試不再支援
// ❌ xUnit 2.x - 3.x 中會失敗 [Fact] public async void 測試某個非同步功能() { var result = await SomeAsyncMethod(); Assert.True(result); } // ✅ xUnit 3.x - 正確寫法 [Fact] public async Task 測試某個非同步功能() { var result = await SomeAsyncMethod(); Assert.True(result); }
3. IAsyncLifetime 變更
在 xUnit 3.x 中,
IAsyncLifetime 繼承 IAsyncDisposable。如果同時實作 IAsyncLifetime 和 IDisposable,只會呼叫 DisposeAsync,不會呼叫 Dispose。
// ⚠️ 需要注意的模式 public class MyTestClass : IAsyncLifetime, IDisposable { public async Task InitializeAsync() { /* ... */ } public async Task DisposeAsync() { /* 會被呼叫 */ } public void Dispose() { /* 在 3.x 中不會被呼叫 */ } } // ✅ 建議:將清理邏輯統一放在 DisposeAsync public class MyTestClass : IAsyncLifetime { public async Task InitializeAsync() { /* 初始化 */ } public async Task DisposeAsync() { /* 所有清理邏輯 */ } }
4. SkippableFact/SkippableTheory 移除
// ❌ xUnit 2.x - 已移除 [SkippableFact] public void 可跳過的測試() { Skip.If(某個條件, "跳過原因"); // 測試邏輯 } // ✅ xUnit 3.x - 使用 Assert.Skip [Fact] public void 可跳過的測試() { if (某個條件) { Assert.Skip("跳過原因"); } // 測試邏輯 }
5. 僅支援 SDK-style 專案
檢查專案檔案開頭是否為:
<Project Sdk="Microsoft.NET.Sdk">
如果是傳統格式,必須先轉換為 SDK-style。
升級步驟
步驟 1:建立升級分支
git checkout -b feature/upgrade-xunit-v3
步驟 2:更新專案檔案
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <OutputType>Exe</OutputType> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> </PropertyGroup> <ItemGroup> <!-- xUnit v3 套件 --> <PackageReference Include="xunit.v3" Version="3.0.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> <!-- 常用輔助套件 --> <PackageReference Include="AwesomeAssertions" Version="8.1.0" /> <PackageReference Include="NSubstitute" Version="5.3.0" /> </ItemGroup> </Project>
步驟 3:修正 async void 測試
使用 IDE 搜尋:
async\s+void.*\[(Fact|Theory)\]
將所有
async void 改為 async Task。
步驟 4:更新 using 陳述式
// 移除 (不再需要) // using Xunit.Abstractions; // 保留 using Xunit;
步驟 5:編譯與測試
dotnet clean dotnet restore dotnet build dotnet test --verbosity normal
xUnit 3.x 新功能
動態跳過測試
聲明式 (SkipUnless/SkipWhen):
[Fact(SkipUnless = nameof(IsWindowsEnvironment), Skip = "此測試只在 Windows 環境執行")] public void 只在Windows上執行的測試() { // 測試邏輯 } public static bool IsWindowsEnvironment => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
命令式 (Assert.Skip):
[Fact] public void 根據環境變數跳過的測試() { var enableTests = Environment.GetEnvironmentVariable("ENABLE_INTEGRATION_TESTS"); if (string.IsNullOrEmpty(enableTests) || enableTests.ToLower() != "true") { Assert.Skip("整合測試已停用。設定 ENABLE_INTEGRATION_TESTS=true 來執行"); } // 測試邏輯... }
明確測試 (Explicit Tests)
[Fact(Explicit = true)] public void 昂貴的整合測試() { // 這個測試預設不會執行,除非明確要求 // 適用於效能測試、長時間執行的測試 }
[Test] 屬性
// 三種寫法功能相同 [Test] public void 使用Test屬性的測試() { Assert.True(true); } [Fact] public void 使用Fact屬性的測試() { Assert.True(true); }
矩陣理論資料 (Matrix Theory Data)
public static TheoryData<int, string> TestData => new MatrixTheoryData<int, string>( [1, 2, 3], // 數字資料 ["Hello", "World", "Test"] // 字串資料 ); // 這會產生 3×3=9 個測試案例 [Theory] [MemberData(nameof(TestData))] public void 矩陣測試範例(int number, string text) { number.Should().BePositive(); text.Should().NotBeNullOrEmpty(); }
Assembly Fixtures
public class DatabaseAssemblyFixture : IAsyncLifetime { public string ConnectionString { get; private set; } public async Task InitializeAsync() { // 建立測試資料庫 ConnectionString = await CreateTestDatabaseAsync(); } public async Task DisposeAsync() { // 清理測試資料庫 await DropTestDatabaseAsync(); } } // 註冊 Assembly Fixture [assembly: AssemblyFixture(typeof(DatabaseAssemblyFixture))] // 在測試中使用 public class UserServiceTests { private readonly DatabaseAssemblyFixture _dbFixture; public UserServiceTests(DatabaseAssemblyFixture dbFixture) { _dbFixture = dbFixture; } [Fact] public void Test1() { /* 使用 _dbFixture.ConnectionString */ } }
Test Pipeline Startup
public class TestPipelineStartup : ITestPipelineStartup { public async Task ConfigureAsync(ITestPipelineBuilder builder, CancellationToken cancellationToken) { // 全域初始化邏輯 Console.WriteLine("初始化測試環境..."); await InitializeDatabaseAsync(); } } // 註冊 [assembly: TestPipelineStartup(typeof(TestPipelineStartup))]
xunit.runner.json 設定
{ "$schema": "https://xunit.net/schema/v3/xunit.runner.schema.json", "parallelAlgorithm": "conservative", "maxParallelThreads": 4, "diagnosticMessages": true, "internalDiagnosticMessages": false, "methodDisplay": "classAndMethod", "preEnumerateTheories": true, "stopOnFail": false }
測試報告格式
xUnit 3.x 支援多種報告格式:
# 產生 CTRF 格式報告 dotnet run -- -ctrf results.json # 產生 TRX 格式報告 dotnet run -- -trx results.trx # 產生 XML 格式報告 dotnet run -- -xml results.xml # 產生多種格式報告 dotnet run -- -xml results.xml -ctrf results.json -trx results.trx
常見問題與解決方案
問題 1:找不到 xunit.abstractions
錯誤:
The type or namespace name 'Abstractions' does not exist
解決:移除
using Xunit.Abstractions;,相關類型已移到 Xunit 命名空間。
問題 2:自訂 DataAttribute 無法運作
// ❌ xUnit 2.x 的實作 public class CustomDataAttribute : DataAttribute { public override IEnumerable<object[]> GetData(MethodInfo testMethod) { // 舊的實作 } } // ✅ xUnit 3.x 的實作 public class CustomDataAttribute : DataAttribute { public override async Task<IReadOnlyCollection<ITheoryDataRow>> GetDataAsync( MethodInfo method, DisposalTracker disposalTracker) { var data = await GenerateDataAsync(); return data.Select(item => new TheoryDataRow(item)).ToList(); } }
問題 3:IDE 無法發現測試
確認 IDE 版本符合要求:
- Visual Studio 2022 17.8+
- Rider 2023.3+
- VS Code (最新版)
如仍有問題,可暫時停用 Microsoft Testing Platform:
<PropertyGroup> <EnableMicrosoftTestingPlatform>false</EnableMicrosoftTestingPlatform> </PropertyGroup>
升級檢查清單
升級前
- 確認目標框架版本 (.NET 8+ 或 .NET Framework 4.7.2+)
- 檢查專案檔案格式 (SDK-style)
- 識別所有 async void 測試方法
- 檢查 IAsyncLifetime 實作
- 評估相依套件相容性
- 建立備份分支
升級過程
- 更新套件參考 (使用
)xunit.v3 - 移除
參考xunit.abstractions - 修改 OutputType 為 Exe
- 修正所有 async void 測試方法
- 更新 using 陳述式
- 重構自訂屬性 (如有)
- 驗證編譯成功
- 執行所有測試
升級後驗證
- 功能完整性測試
- 效能基準比較
- CI/CD Pipeline 驗證
- 文檔更新
- 團隊培訓
IDE 與工具支援
IDE 版本需求
| IDE | 最低版本 |
|---|---|
| Visual Studio | 2022 17.8+ |
| VS Code | 最新版 |
| Rider | 2023.3+ |
Microsoft Testing Platform
xUnit 3.x 預設啟用 Microsoft Testing Platform:
<PropertyGroup> <EnableMicrosoftTestingPlatform>true</EnableMicrosoftTestingPlatform> <OutputType>Exe</OutputType> </PropertyGroup>
效能改進
xUnit 3.x 帶來的效能改進:
- 獨立進程執行:測試在獨立進程中執行,更好的隔離性
- 改進的並行演算法:更智慧的負載平衡
- 更快的啟動時間:可執行檔直接執行
- 更好的記憶體隔離:減少測試之間的干擾
參考資源
原始文章
本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:
- Day 26 - xUnit 升級指南:從 2.9.x 到 3.x 的轉換
官方文件
範例參考
請參考同目錄下的範例檔案:
- xUnit 3.x 專案設定範本templates/xunit-v3-project.csproj
- 升級檢查清單templates/upgrade-checklist.md
- 程式碼遷移範例templates/code-migration-examples.cs
- 新功能使用範例templates/new-features-examples.cs