使用 xUnit 測試

紀錄使用 xUnit 測試的一些筆記

準備單元測試環境

1
2
3
4
5
6
7
8
9
# 查看專案安裝的 nuget 套件
dotnet list package

# 安裝 xunit 核心套件
dotnet add package xunit
# Visual Studio 及 dotnet 需要安裝下面兩個套件才能執行測試
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package xunit.runner.visualstudio

單元測試範例、測試框架是否正確工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Xunit;

public class UnitTestDemo
{

[Fact]
public void TestSuccess()
{
Assert.True(true);
}

[Fact]
public void TestFail()
{
Assert.True(false);
}
}

執行測試

1
2
## 執行測試
dotnet test

理論 throey 與事實 fact 的比較與介紹

實際上 fact 就等同於其他測試框架的測試,沒有甚麼不同,但是如果是需要測試多個不同的輸入,卻也要有相同的結果,則可以利用 throey 來做測試,他實際上是為了在以前的單元測試框架下,因為要測試多組參數呼叫同一個方法的情況,可能會寫出多個不同名稱的測試,內容都是呼叫相同方法,只是在測試不同的傳入參數。就可以採用 throey 的方式來做測試。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Theory]
[InlineData(3)]
[InlineData(5)]
public void MyFirst_Theory_IsOdd(int value)
{
Assert.True(IsOdd(value));
}


[Theory]
[InlineData(6)]
public void MyFirst_Theory_IsNotOdd(int value)
{
Assert.False(IsOdd(value));
}

如果不用 throey 要驗證這三個情況,就可能需要寫成三個 fact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Fact]
public void MyFirstFactWithOddValue3()
{
Assert.True(IsOdd(3));
}

[Fact]
public void MyFirstFactWithOddValue5()
{
Assert.True(IsOdd(5));
}

[Fact]
public void MyFirstFactWithEvenValue6()
{
Assert.False(IsOdd(6));
}

這兩種寫法都是可以的,因為它們也都各有優缺點,關注的點也不太一樣,所以要看自己的需求來決定要用哪一種。

一般來說 fact 可以很明確的驗證一件事情,並且在發生錯誤的時候可以直接根據測試的名稱識別出錯誤的測試。
而 throey 比較適用於需要測試相同功能在不同輸入的情況下是否正常運作,並且避免撰寫過多的測試方法。

也可以理解為

  1. 當測試不會根據不同的輸入參數而改變行為,並且只需要驗證一個確定的行為時,可以採用 fact 較為合適
  2. 當需要驗證多種輸入參數的情況下,並且需要驗證的行為是相同的,可以採用 throey 較為合適

驗證多種輸入參數的時候不建議把多種行為也寫在同一個 throey

加入配置文件設定

官方配置設定說明的蠻清楚的,有需要可以參考官方文件進行設定,這邊用一個簡單的設定來示範

測試專案目錄下新增 xunit.runner.json 檔案

1
2
3
4
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"methodDisplayOptions": "replaceUnderscoreWithSpace,useOperatorMonikers "
}

選擇該檔案在建置動作時複製到輸出目錄 (修改專案 csproj 檔)

1
2
3
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

並行測試,並透過顯示驗證

首先先在測試中透過建構式注入 ITestOutputHelper 來輸出測試的訊息,預設的情況下, TestClass1 底下的兩個測試會一個接著一個的執行,用來驗證的單元測試執行的開始跟結束,都有紀錄時間,透過指定 logger 參數可以在 CLI 看到測試的進度及結果

1
dotnet test --logger "console;verbosity=detailed"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestClass1
{
private readonly ITestOutputHelper _output;

public TestClass1(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void Test1()
{
_output.WriteLine("Test 1 begin at {0:HH:mm:ss}", DateTime.Now);
Thread.Sleep(3000);
_output.WriteLine("Test 1 end at {0:HH:mm:ss}", DateTime.Now);
}

[Fact]
public void Test2()
{
_output.WriteLine("Test 2 begin at {0:HH:mm:ss}", DateTime.Now);
Thread.Sleep(5000);
_output.WriteLine("Test 2 end at {0:HH:mm:ss}", DateTime.Now);
}
}

預設情況下,每一個測試類別都是一個唯一的測試集合,這些測試之間並不會同時執行,但如果將它們放在不同的測試類別當中,就能夠同時進行測試

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
public class TestClass2A
{
private readonly ITestOutputHelper _output;

public TestClass2A(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void Test1()
{
_output.WriteLine("Test 2A begin at {0:HH:mm:ss}", DateTime.Now);
Thread.Sleep(3000);
_output.WriteLine("Test 2A end at {0:HH:mm:ss}", DateTime.Now);
}

}

public class TestClass2B
{
private readonly ITestOutputHelper _output;

public TestClass2B(ITestOutputHelper output)
{
_output = output;
}

[Fact]
public void Test1()
{
_output.WriteLine("Test 2B begin at {0:HH:mm:ss}", DateTime.Now);
Thread.Sleep(5000);
_output.WriteLine("Test 2B end at {0:HH:mm:ss}", DateTime.Now);
}
}
❯ dotnet test --logger "console;verbosity=detailed"
  正在判斷要還原的專案...
  所有專案都在最新狀態,可進行還原。
C:\Program Files\dotnet\sdk\8.0.100-preview.7.23376.3\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(314,5): message NETSDK1057: 您目前使用的是 .NET 預覽版。請參閱: https://aka.ms/dotnet-support-policy [D:\Code\GitLabCE\MyProject\MyProject.csproj]
  MyProject -> D:\Code\GitLabCE\MyProject\bin\Debug\net7.0\MyProject.dll
D:\Code\GitLabCE\MyProject\bin\Debug\net7.0\MyProject.dll 的測試回合 (.NETCoreApp,Version=v7.0)
Microsoft (R) Test Execution Command Line Tool 17.7.0-preview-23317-01+919ec8358820228cc5fa77ef000051c1d6875399 (x64) 版Copyright (C) Microsoft Corporation. 著作權所有,並保留一切權利。

正在啟動測試執行,請稍候...
總共有 1 個測試檔案與指定的模式相符。
D:\Code\GitLabCE\MyProject\bin\Debug\net7.0\MyProject.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.5.0.1+5ebf84cd75 (64-bit .NET 7.0.5)
[xUnit.net 00:00:00.06]   Discovering: MyProject
[xUnit.net 00:00:00.08]   Discovered:  MyProject
[xUnit.net 00:00:00.08]   Starting:    MyProject
  已通過 MyProject.TestClass2A.Test1 [3 s]
  標準輸出訊息:
 Test 2A begin at 15:44:50
 Test 2A end at 15:44:53


  已通過 MyProject.TestClass2B.Test1 [5 s]
  標準輸出訊息:
 Test 2B begin at 15:44:50
 Test 2B end at 15:44:55


  已通過 MyProject.TestClass1.Test2 [5 s]
  標準輸出訊息:
 Test 2 begin at 15:44:50
 Test 2 end at 15:44:55


[xUnit.net 00:00:08.15]   Finished:    MyProject
  已通過 MyProject.TestClass1.Test1 [3 s]
  標準輸出訊息:
 Test 1 begin at 15:44:55
 Test 1 end at 15:44:58



測試回合成功。
測試數總計: 4
     通過: 4
 時間總計: 8.5914 秒

在 Visual Studio 2022, Rider, CLI 環境都是相同的

從 MSTest 到 xUnit

請參閱官方文件
基本上就是

  1. 替換 nuget package
  2. 修正一些 mstest 的標籤,改成用 xunit 的語法
  3. 可以依照警告來逐步修正,修正完畢後可以移除掉向後兼容的暫時用的套件xunit.MSTest