---
name: dotnet-testing-advanced-xunit-upgrade-guide
description: |
xUnit 2.9.x 到 3.x 升級完整指南。
涵蓋破壞性變更、套件更新、async void 修正、IAsyncLifetime 調整。
包含新功能介紹: Assert.Skip、Explicit Tests、Matrix Theory、Assembly Fixtures。
triggers:
# 核心關鍵字
- xunit upgrade
- xunit v3
- xunit 3.x
- xunit migration
- xunit 升級
# 技術術語
- xunit.v3
- OutputType Exe
- async void issue
- IAsyncLifetime v3
- SkippableFact removed
- Assert.Skip
# 新功能
- SkipUnless
- SkipWhen
- Explicit attribute
- MatrixTheoryData
- AssemblyFixture
- TestPipelineStartup
# 使用情境
- 破壞性變更
- Microsoft.Testing.Platform
- SDK-style project
- xunit.runner.visualstudio 3.x
- 測試不被發現
license: MIT
metadata:
author: Kevin Tseng
version: "1.0.0"
tags: "xunit, upgrade, migration, v3, breaking-changes, testing-framework"
---
# xUnit 升級指南:從 2.9.x 到 3.x
## 適用情境
當被要求執行以下任務時,請使用此技能:
- 將現有 xUnit 2.x 測試專案升級到 xUnit 3.x
- 評估 xUnit 升級的影響範圍
- 解決 xUnit 升級過程中的編譯錯誤
- 使用 xUnit 3.x 新功能改進測試
## 核心概念
### 套件命名變革
xUnit v3 採用全新的套件命名策略:
| v1~v2 套件名稱 | v3 套件名稱 | 說明 |
| --------------------------- | ----------------------------------- | ------------ |
| `xunit` | `xunit.v3` | 主要測試框架 |
| `xunit.assert` | `xunit.v3.assert` | 斷言函式庫 |
| `xunit.core` | `xunit.v3.core` | 核心元件 |
| `xunit.abstractions` | (移除) | 不再需要 |
| `xunit.runner.visualstudio` | `xunit.runner.visualstudio` (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. 測試專案變成可執行檔
```xml
Library
Exe
```
### 2. async void 測試不再支援
```csharp
// ❌ 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`。
```csharp
// ⚠️ 需要注意的模式
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 移除
```csharp
// ❌ xUnit 2.x - 已移除
[SkippableFact]
public void 可跳過的測試()
{
Skip.If(某個條件, "跳過原因");
// 測試邏輯
}
// ✅ xUnit 3.x - 使用 Assert.Skip
[Fact]
public void 可跳過的測試()
{
if (某個條件)
{
Assert.Skip("跳過原因");
}
// 測試邏輯
}
```
### 5. 僅支援 SDK-style 專案
檢查專案檔案開頭是否為:
```xml
```
如果是傳統格式,必須先轉換為 SDK-style。
---
## 升級步驟
### 步驟 1:建立升級分支
```bash
git checkout -b feature/upgrade-xunit-v3
```
### 步驟 2:更新專案檔案
```xml
net8.0
Exe
enable
enable
false
true
runtime; build; native; contentfiles; analyzers
all
```
### 步驟 3:修正 async void 測試
使用 IDE 搜尋:
```regex
async\s+void.*\[(Fact|Theory)\]
```
將所有 `async void` 改為 `async Task`。
### 步驟 4:更新 using 陳述式
```csharp
// 移除 (不再需要)
// using Xunit.Abstractions;
// 保留
using Xunit;
```
### 步驟 5:編譯與測試
```bash
dotnet clean
dotnet restore
dotnet build
dotnet test --verbosity normal
```
---
## xUnit 3.x 新功能
### 動態跳過測試
**聲明式 (SkipUnless/SkipWhen)**:
```csharp
[Fact(SkipUnless = nameof(IsWindowsEnvironment),
Skip = "此測試只在 Windows 環境執行")]
public void 只在Windows上執行的測試()
{
// 測試邏輯
}
public static bool IsWindowsEnvironment =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
```
**命令式 (Assert.Skip)**:
```csharp
[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)
```csharp
[Fact(Explicit = true)]
public void 昂貴的整合測試()
{
// 這個測試預設不會執行,除非明確要求
// 適用於效能測試、長時間執行的測試
}
```
### [Test] 屬性
```csharp
// 三種寫法功能相同
[Test]
public void 使用Test屬性的測試() { Assert.True(true); }
[Fact]
public void 使用Fact屬性的測試() { Assert.True(true); }
```
### 矩陣理論資料 (Matrix Theory Data)
```csharp
public static TheoryData TestData =>
new MatrixTheoryData(
[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
```csharp
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
```csharp
public class TestPipelineStartup : ITestPipelineStartup
{
public async Task ConfigureAsync(ITestPipelineBuilder builder,
CancellationToken cancellationToken)
{
// 全域初始化邏輯
Console.WriteLine("初始化測試環境...");
await InitializeDatabaseAsync();
}
}
// 註冊
[assembly: TestPipelineStartup(typeof(TestPipelineStartup))]
```
---
## xunit.runner.json 設定
```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 支援多種報告格式:
```bash
# 產生 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 無法運作
```csharp
// ❌ xUnit 2.x 的實作
public class CustomDataAttribute : DataAttribute
{
public override IEnumerable