dotnet-testing-advanced-xunit-upgrade-guide
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-advanced-xunit-upgrade-guide
Agent 安装分布
Skill 文档
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. æ¸¬è©¦å°æ¡è®æå¯å·è¡æª
<!-- 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 çè½æ
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10377477
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day26
宿¹æä»¶
- xUnit.net 宿¹ç¶²ç«
- xUnit v3 æ°åè½æä»¶
- xUnit 2.x â 3.x 宿¹é·ç§»æå
- xunit.v3 NuGet å¥ä»¶
ç¯ä¾åè
è«åèåç®éä¸çç¯ä¾æªæ¡ï¼
templates/xunit-v3-project.csproj– xUnit 3.x å°æ¡è¨å®ç¯æ¬templates/upgrade-checklist.md– åç´æª¢æ¥æ¸ å®templates/code-migration-examples.cs– ç¨å¼ç¢¼é·ç§»ç¯ä¾templates/new-features-examples.cs– æ°åè½ä½¿ç¨ç¯ä¾