はじめに
C#の有名なテストフレームワークは、MSTest、NUnit、xUnitの3つがあります。
xUnitが一番人気があります。
本稿は、現在MSTestを利用していて、人気のあるxUnitに乗り換えたいという人が、乗り換える価値があるということをチームメンバーに理解してもらうための記事です。
xUnit とは
Microsoftの以下のページから、xUnitの概要を抜粋します。
.NET でのテスト
.NET 用の無料のオープン ソースのコミュニティ向け単体テスト ツールです。 xUnit.net は、NUnit v2 の最初の発明者によって記述された、.NET アプリを単体テストする最新のテクノロジです。 xUnit.net は、ReSharper、CodeRush、TestDriven.NET および Xamarin と共に動作します。
xUnit.net の公式ページは以下です。
https://xunit.net/
現在のトレンド
MSTest、NUnit、xUnitについて、2020年12月時点でのトレンドは、以下の記事を参照ください。
.Net 5 時代のテストフレームワーク比較
上記の記事によると、NuGetのダウンロード数も、GitHubのスター数も、xUnitが1番多いです。
人気が高い方が、ライブラリの改善や技術コミュニティでの安定した情報取得が期待できます。
テストフレームワークごとの比較
以下の記事は、MSTest、NUnit、xUnitの3つを比較した上で、xUnitが優れている点が分かりやすい記事です。
NUnit vs. XUnit vs. MSTest: Comparing Unit Testing Frameworks In C#
上記の記事で、ポイントになる部分を、以下に抜粋していきます。
テストごとの初期化の属性の廃止
抜粋
The popular attributes [SetUp] and [TearDown] are also not a part of the xUnit framework. As per the creators of NUnit (and xUnit), usage of [SetUp] and [TearDown] led to code duplication, and they wanted to implement the same features in a much more optimized manner in xUnit. For initialization, constructor of the test class is used, whereas, for de-initialization, IDisposable interface is used. This also encourages writing much cleaner tests. This makes this C# unit testing framework a much better option when it comes to Selenium automation testing as it is more robust and extensible.
要約
NUnitの[SetUp]と[TearDown]、MSTestの[TestInitialize]と[TestCleanup]の属性は、xUnit にはありません。
上記の属性を使うとコードが重複してしまうため、xUnit では同じ機能をより最適な方法で実装したいと考えました。
[SetUp]の代わりにコンストラクタを使い、[TearDown]の代わりにIDisposable.Disposeメソッドを使います。
これにより、よりクリーンなテストを書くことができます。
補足
なぜ [SetUp]と[TearDown] 属性を使わない方が良いのかについては、以下の記事に詳細が書いてあります。
要約すると、この属性で初期化処理を共通化することで、すべてのテストに必要な論理和の初期化が行われる事が多くなります。その結果、テストごとに無駄な初期化が実行され、各テストに本当に必要な初期化が何なのか分かりにくくなるため、廃止したということです。
Why you should not use SetUp and TearDown in NUnit
テストの分離
抜粋
xUnit framework provides much better isolation of tests in comparison to NUnit and MSTest frameworks. For each test case, the test class is instantiated, executed, and is discarded after the execution. This ensures that the tests can be executed in any order as there is reduced/no dependency between the tests. Executing each test as a separate instance minimizes the chances of one test causing the other tests to fail!
要約
xUnitフレームワークは、NUnitおよびMSTestフレームワークと比較して、テストの分離がはるかに優れています。テストケースごとに、テストクラスがインスタンス化されて実行され、実行後に破棄されます。これにより、テスト間の依存関係が減少するか、依存関係がなくなるため、テストを任意の順序で実行できるようになります。各テストを個別のインスタンスとして実行すると、1つのテストが他のテストの失敗を引き起こす可能性が最小限に抑えられます。
拡張性
抜粋
As far as NUnit vs. XUnit vs. MSTest is concerned, the biggest difference between xUnit and the other two test frameworks (NUnit and MSTest) is that xUnit is much more extensible when compared to NUnit and MSTest.
要約
NUnit、xUnit、MSTest の比較において、xUnitと他の2つのテストフレームワークの最大の違いは、xUnitがNUnitおよびMSTestと比較してはるかに拡張性があることです。
補足
xUnitには、[MemberData]属性と[ClassData]属性があり、これらを用いることで、IEnumerable<object[]>
型のプロパティやクラスをテストデータとして利用できます。
詳細は以下の記事を参照ください。
xUnit 単体テスト 入門 : データドリブンテスト
MSTestには、xUnitの[InlineData]属性と同じ機能の[DataRow]属性はあります。しかし、[MemberData]属性と[ClassData]属性と同等の機能は見つかりませんでした。
MSTestにも、[MemberData]属性と同じ機能の[DynamicData]属性がありましたので、xUnitとMSTestの拡張性は大差ないと考えられます。
[DynamicData]属性の詳細は以下ページを参照ください。
MSTest v2: Data tests
RFC 006- DynamicData Attribute for Data Driven Tests
NUnitには、[TestCaseSource]属性があり、これを用いれば、プロパティやクラスをテストデータとして利用できるため、xUnitとNUnitの拡張性は大差ないと考えられます。
[TestCaseSource]属性の詳細は以下の記事を参照ください。
TestCaseSource | NUnit Docs
例外のアサーション
抜粋
xUnit framework makes use of Assert.Throws instead of [ExpectedException] which is used in NUnit and MSTest. The drawback of using [ExpectedException] is that the errors might not be reported if they occur in the wrong part of the code. For example, if assert has to be raised for Security Exception, but Authentication Exception occurs, [ExpectedCondition] will not raise assert.
要約
xUnitフレームワークは、NUnitおよびMSTestで使用される[ExpectedException]の代わりにAssert.Throwsを使用します。[ExpectedException]を使用することの欠点は、コードの間違った部分でエラーが発生した場合、エラーが報告されない可能性があることです。たとえば、セキュリティ例外に対してassertを発生させる必要があるが、Authentication Exceptionが発生した場合、[ExpectedCondition]はassertを発生させません。
補足
MSTestの[ExpectedException]属性は、どのコード行で例外が発生するかを指定できません。
そのため、意図しないところで、たまたま同じ例外が発生した場合に、テストに合格してしまいます。また、例外自体の詳細な検査(Messageプロパティの値の確認など)もできません。
xUnitには[ExpectedException]属性が存在せず、代わりにAssert.Throwsを使用するため、上記のようなコードを書くことがなくなります。
結論
抜粋
If I had an option to select a framework, I would go with xUnit since it is more extensible and has fewer attributes, making the code clean & easy to maintain.
要約
フレームワークを選択するオプションがある場合は、xUnitを使用します。これは、xUnitの方が拡張性が高く、属性が少なく、コードがクリーンで保守しやすいためです。
補足
ここまでの内容により、以下の点で、MSTestを使い続けるより、xUnitの方がより高いコード品質の単体テストが書きやすいと言えます。
- MSTestの[TestInitialize]と[TestCleanup]属性は、初期化処理を共通化することで、すべてのテストに必要な論理和の初期化が行われる事が多くなります。その結果、テストごとに無駄な初期化が実行され、各テストに本当に必要な初期化が何なのか分かりにくくなります。xUnitでは、クリーンなコードを保つために、その属性を廃止し、代わりにコンストラクタとIDisposable.Disposeメソッドを使います。
- xUnitは、テストケースごとに、テストクラスがインスタンス化されて実行され、実行後に破棄されます。これにより、テスト間の依存関係が減少するか、依存関係がなくなるため、テストを任意の順序で実行できるようになります。各テストを個別のインスタンスとして実行すると、1つのテストが他のテストの失敗を引き起こす可能性が最小限に抑えられます。
- MSTestの[ExpectedException]属性は、どのコード行で例外が発生するかを指定できません。そのため、意図しないところで、たまたま同じ例外が発生した場合に、テストに合格してしまいます。また、例外自体の詳細な検査(Messageプロパティの値の確認など)もできません。xUnitは、Assert.Throws を用いることで、どのコード行で例外が発生したか分かり、その例外自体の詳細な検査もできます。
MSTest から xUnit への移行手順
xUnitの公式ページに、MSTestからの移行手順が書いてあります。
Migrating from MSTest to xUnit.net
まとめ
本稿の目的は以下でした。
本稿は、現在MSTestを利用していて、人気のあるxUnitに乗り換えたいという場合に、わざわざ乗り換える価値があるということをチームメンバーに理解してもらうための記事です。
ここまでに書いた内容により、MSTest よりも xUnit の方が、__世界的に人気が高く今後の成長や技術コミュニティでの情報が期待でき、より高いコード品質の単体テストが書きやすい__と言えます。
したがって、MSTest から xUnit に乗り換える価値はあると判断します。
ちなみに私は、普段はエンジニアリングマネージャーとして、チームの皆で楽しく開発する施策を色々実施しています。詳しくは以下を参照ください。
1年以上かけて生産性倍増+成長し続けるチームになった施策を全部公開
Twitterでも開発に役立つ情報を発信しています → @kojimadev