TUnit を Visual Studio で試してみた
はじめに
記事の経緯、主旨
たまたま海外の記事が流れてきたのがきっかけで TUnit を最近知りました。
網羅的な紹介や導入方法はこちら ↓ の方が既にあげておりますので、
TUnit を使おう!
改めて、
・Visual Studio での導入や実行方法
・値の比較に関する Assertion の xUnit/NUnit との比較
あたりについて言及し、使用感についてレポートしてみたいとおもいます。
私自身は xUnit を触ったことがある程度ですが、一部 Assertion がわかりにくいと感じてましたので、
Assetion 用の追加ライブラリなどを使わずとも、純正で使いやすいものが出てくるならと期待しております。
なお、他 FW との比較に関する見解はあくまで個人のものですので、ご了承ください。
TUnit 側の公式見解は Framework Differences を参照いただけますと幸いです。
TUnit とは
GitHub 上で以下のように記載されています。
TUnit is a next-generation testing framework for C# that outpaces traditional frameworks with source-generated tests, parallel execution by default, and Native AOT support. Built on the modern Microsoft.Testing.Platform, TUnit delivers faster test runs, better developer experience, and unmatched flexibility.
(意訳)
TUnit は、ソース生成テスト、デフォルトでの並列実行、ネイティブ AOT サポートにより従来のフレームワークを凌駕する、C# 向け次世代テストフレームワークです。最新の Microsoft.Testing.Platform を基盤とする TUnit は、より高速なテスト実行、優れた開発者体験、比類のない柔軟性を提供します。
Visual Studio で実際に触ってみる
手元の環境
Windows 11
Microsoft Visual Studio Community 2022 (64-bit) / Version 17.14.13
導入手順
任意のコンソールアプリとテストプロジェクトを用意します。
今回は「TUnitSample」としました。
mkdir TUnitSample
cd TUnitSample
dotnet new sln -n TUnitSample
dotnet new console -n TUnitSample
dotnet new classlib -n TUnitSample.Tests
dotnet sln add TUnitSample/TUnitSample.csproj
dotnet sln add TUnitSample.Tests/TUnitSample.Tests.csproj
dotnet add TUnitSample.Tests/TUnitSample.Tests.csproj reference TUnitSample/TUnitSample.csproj
テストプロジェクトに TUnit のパッケージを追加します。
cd TUnitSample.Tests
dotnet add package TUnit
簡単なテストを書いてみる
メインのプロジェクトに以下の Calculator
クラスを作成します。
namespace TUnitSample;
public class Calculator
{
public int Add(int a, int b) => a + b;
}
テストプロジェクトに簡易的なテストを作成します。
namespace TUnitSample.Tests;
public class CalculatorTests
{
[Test]
public async Task Add_TwoNumbers_ReturnsSum()
{
// Given
var calculator = new Calculator();
// When
var result = calculator.Add(1, 2);
// Then
await Assert.That(result).IsEqualTo(3);
}
}
実行する
Test Explorer から実行してみました。成功しています。
なお、テスト結果の行をダブルクリックすると、該当のテストコードにとべました。
他の実行方法も確認してみます。
Visual Studio 上のコマンドライン(PowerShell)の場合はこのようになり、
テストプロジェクトをデバッグ(F5)で実行した場合はこのようになるみたいです。
また、CodeLens を on にしている場合は、コード上から実行もできました。
xUnit/NUnit コードとの比較
値の比較(Equal)
改めて、さきほどの単純比較のコードで既存の xUnit/NUnit コードとの比較です。
TUnit - IsEqualTo:
[Test]
public async Task Add_TwoNumbers_ReturnsSum()
{
var calculator = new Calculator();
var result = calculator.Add(1, 2);
await Assert.That(result).IsEqualTo(3);
}
xUnit - Equal:
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
var calculator = new Calculator();
var result = calculator.Add(1, 2);
Assert.Equal(3, result);
}
NUnit - EqualTo:
[Test]
public void Add_TwoNumbers_ReturnsSum()
{
var calculator = new Calculator();
var result = calculator.Add(1, 2);
Assert.That(result, Is.EqualTo(3));
}
比較:
・TUnit
非同期 Assertion がベースになっているのが特徴的です。
また、制約ベース(テスト対象の値とその値が満たすべき「制約」を分離して表現する)の Assertion となってます。
個人的には That における actual(実際の値)が IsEqualTo の expected(期待値)になっている点が非常にわかりやすいと思います。
なお、AND(や OR) 条件は以下のようにかけるようです。
await Assert.That(result)
.IsNotNull()
.And.IsPositive()
.And.IsEqualTo(3);
・xUnit
[Fact]
属性やシンプルな Assertion が特徴的です。
一応 Equal
の第一引数が expected
、第二引数が actual
となっています。
課題として、引数の順番間違えるケースがあるかなと思います。
・NUnit
こちらも TUnit と同じく制約ベースの Assertion が特徴的です。
(TUnit が NUnit と xUnit を参考につくられているとのことで、このあたりは NUnit に近しいようです。)
オブジェクトのプロパティ比較
xUnit の Assert.Equivalent
が便利だったので比較にあげてみました。
前提として、比較する User
クラスは以下とします。
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
TUnit - IsEqualTo, Serialize + IsEqualTo:
個別のプロパティごとの値比較
[Test]
public async Task UserEquality_SameProperties_AreEqual()
{
var expectedUser = new User { Id = 1, Name = "John Doe" };
var actualUser = new User { Id = 1, Name = "John Doe" };
await Assert.That(expectedUser.Id).IsEqualTo(actualUser.Id);
await Assert.That(expectedUser.Name).IsEqualTo(actualUser.Name);
}
あるいは、JSON にて
[Test]
public async Task GetUser_ShouldReturnCorrectUser()
{
var expectedUser = new User { Id = 1, Name = "John Doe" };
var actualUser = new User { Id = 1, Name = "John Doe" };
var expectedJson = JsonSerializer.Serialize(expectedUser);
var actualJson = JsonSerializer.Serialize(actualUser);
await Assert.That(actualJson).IsEqualTo(expectedJson);
}
xUnit - Equivalent:
[Fact]
public void UserEquality_SameProperties_AreEqual()
{
var expectedUser = new User { Id = 1, Name = "John Doe" };
var actualUser = new User { Id = 1, Name = "John Doe" };
Assert.Equivalent(expectedUser, actualUser); // v2.4.2から
}
NUnit - EqualTo + UsingPropertiesComparer:
[Test]
public void UserEquality_SameProperties_AreEqual()
{
var expectedUser = new User { Id = 1, Name = "John Doe" };
var actualUser = new User { Id = 1, Name = "John Doe" };
Assert.That(actualUser, Is.EqualTo(expectedUser).UsingPropertiesComparer()); // v4.1から
}
比較:
・TUnit
残念ながら、プロパティを比較してくれる Assertion はないようでした。
JSON 比較は簡単ですが、一部プロパティの除外したい場合など出てくるでしょうし、
FluentAssertions などライブラリとの併用が欲しいときがあるかもです。
あるいは、TUnit では独自の Assertionもつくれるようですので、
各クラスごとに独自の比較実装をつくるよりは、汎用的な Assertion をつくるのもよいのかもしれません。
・xUnit
個人的にとっても優秀なEquivalent
です。v2.4.2 から使えます。
2 つのオブジェクトが等価であることをテストし、型に関係なく行えます。
個々のパブリックプロパティとフィールドの等価性のみをテストします。
Dto オブジェクトなど活躍しそうです。
・NUnit
UsingPropertiesComparer
というのが用意されています。v4.1 から使えます。
型のすべてのパブリックプロパティが等価かどうかをテストします。
xUnit の Equivalent
と違い、そのままだと同じ型のインスタンスのみのようですが、
.UsingPropertiesComparer(o => o.AllowDifferentTypes()))
とすると異なる型でも利用できるようです。
(おまけ)パラメータ化テスト
TUnit - Arguments:
[Test]
[Arguments(1, 2, 3)]
[Arguments(-1, 1, 0)]
[Arguments(100, -50, 50)]
public async Task Add_MultipleInputs_ReturnsExpectedSum(int a, int b, int expected)
{
await Assert.That(new Calculator().Add(a, b)).IsEqualTo(expected);
}
xUnit - Theory + InlineData/MemberData:
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
[InlineData(int.MaxValue, 0, int.MaxValue)]
public void Add_MultipleTestCases_ReturnsExpectedResults(int a, int b, int expected)
{
var result = new Calculator().Add(a, b);
Assert.Equal(expected, result);
}
NUnit - TestCase/TestCaseSource/Values:
[TestCase(1, 2, 3)]
[TestCase(0, 0, 0)]
[TestCase(-1, 1, 0)]
[TestCase(int.MaxValue, 0, int.MaxValue)]
public void Add_MultipleTestCases_ReturnsExpectedResults(int a, int b, int expected)
{
var result = new Calculator().Add(a, b);
Assert.That(result, Is.EqualTo(expected));
}
比較:
・TUnit
Arguments
で複数かけます。
なお、Arguments
の引数部分はしっかり型がきいていました。
試しに expected
部分に string
型の値をいれようとするエラーになりました。
このあたりは他の FW とあまりかわらないようです。
なお、類似のものでMatrixという組み合わせテストがあり、直感的でわかりやすかったです。
公式の例)
[Test]
[MatrixDataSource]
public async Task MyTest(
[Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] int value1,
[Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] int value2
)
{
var result = Add(value1, value2);
await Assert.That(result).IsPositive();
}
・xUnit
InlineData
(または MemberData
)で同様にデータ指定ができます。
・NUnit
TestCase
(または TestCaseSource
)で同様にデータ指定ができます。
TUnit の [Matrix]
同様、[Values]
での組み合わせテストもできます。
まとめ
一部の Assertion を触ってみましたが、直感的で好みでした。
また、パラメータ化テストの Arguments
など、ひとつひとつの命名をとても大事にしていると感じました。
ドキュメントもすごく充実していますし、Visual Studio における IDE 統合もある程度揃っているとも思いました。
あとは公式ででているパフォーマンスや並列制御もドキュメント見るかぎりは良さそうなので実際のところが気になります。
まだまだ xUnit/NUnit とくらべるとシェアはかなり差↓はありますが、今後に期待したいです。
ありがとうございました!
Discussion