MSTest・NUnit・xUnitにおけるリスト比較の違い

C#にはMSTest・NUnit・xUnitという3種類のテスティングフレームワークがあります。

違いが良くわからないままMSTestを使っていたのですが、MSTestはリスト比較のアサーションに失敗した際のメッセージが分かりにくく、リスト比較がしやすいテスティングフレームワークはどれなのか検証してみました。

バージョン

  • .NET Core : 3.1.201
  • MSTest : 2.1.0
  • NUnit : 3.12.0
  • xUnit : 2.4.0

MSTest

MSTestはCollectionAssert.AreEquivalentで順不同の比較、CollectionAssert.AreEqualで要素の順番まで考慮した比較が行えます。

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTestSample
{
    [TestClass]
    public class CollectionTests
    {
        [TestMethod]
        public void TestDifferentValue()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"aaa", "ccc"};

            // The expected collection contains 1 occurrence(s) of <bbb>.
            // The actual collection contains 0 occurrence(s).
            CollectionAssert.AreEquivalent(list1, list2);
        }

        [TestMethod]
        public void TestDifferentOrder()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"bbb", "aaa"};

            // Element at index 0 do not match.
            CollectionAssert.AreEqual(list1, list2);
        }
    }
}

このメッセージだけでは情報が少なく、アサーションに失敗した原因を確認するためにはデバッガを使用するかprintデバッグする必要がありますね。

NUnit

NUnitはAssert.That〜Is.EquivalentToで順不同の比較、Is.EqualToで要素の順番まで考慮した比較が行えます。
NUnitにもMSTestと同様のCollectionAssertクラスがありますが、内部的にはIs.EquivalentToやIs.EqualToを実行しています。)

using NUnit.Framework;
using System.Collections.Generic;

namespace NUnitSample
{
    public class CollectionTests
    {
        [Test]
        public void TestDifferentValue()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"aaa", "ccc"};

            // Expected and actual are both <System.Collections.Generic.List`1[System.String]> with 2 elements
            //     Values differ at index [1]
            // String lengths are both 3. Strings differ at index 0.
            //     Expected: "ccc"
            // But was:  "bbb"
            // Assert.That(list1, Is.EqualTo(list2));

            // Expected: equivalent to < "aaa", "ccc" >
            //     But was:  < "aaa", "bbb" >
            //     Missing (1): < "ccc" >
            //     Extra (1): < "bbb" >
            Assert.That(list1, Is.EquivalentTo(list2));
        }

        [Test]
        public void TestDifferentOrder()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"bbb", "aaa"};

            // Expected and actual are both <System.Collections.Generic.List`1[System.String]> with 2 elements
            //     Values differ at index [0]
            // String lengths are both 3. Strings differ at index 0.
            //     Expected: "bbb"
            // But was:  "aaa"
            Assert.That(list1, Is.EqualTo(list2));
        }
    }
}

Is.EqualToで出力されるメッセージでも一致しなかった部分が確認出来ますが、Is.EquivalentToだとより見やすく出力されるため要素の順番まで一致していて欲しい場合以外はIs.EquivalentToを使った方が良さそうです。

xUnit

xUnitはAssert.Equalでリストや配列を比較出来ます。
(順不同での比較はありません)

using Xunit;
using System.Collections.Generic;

namespace xUnitSample
{
    public class CollectionTests
    {
        [Fact]
        public void TestDifferentValue()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"aaa", "ccc"};

            // Expected: List<String> ["aaa", "bbb"]
            // Actual:   List<String> ["aaa", "ccc"]
            Assert.Equal(list1, list2);
        }

        [Fact]
        public void TestDifferentOrder()
        {
            var list1 = new List<string>() {"aaa", "bbb"};
            var list2 = new List<string>() {"bbb", "aaa"};
            
            // Expected: List<String> ["aaa", "bbb"]
            // Actual:   List<String> ["bbb", "aaa"]
            Assert.Equal(list1, list2);
        }
    }
}

シンプルなメッセージで、個人的にはxUnitの出力内容が好み。

まとめ

分かりやすいメッセージ出力・順不同での比較の両方を備えたNUnitが配列やリストを比較するケースには向いているようです。

メッセージ出力についてはデバッガを使えば済む問題であり、テスティングフレームワークを選ぶ基準は他にもあるとは思いますが、この記事がMSTest・NUnit・xUnitのどれを使えばいいのか迷った自分のような人の助けになれば嬉しいです。

余談

実際のテストコードで書く事はないでしょうが、興味本位で
List<string> { "10", "20" }

List<int> { 30, 40 }
を比較した場合の挙動も確認してみました。

xUnit

CollectionTests.cs(14,26): error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.List<string>' to 'string'
CollectionTests.cs(14,33): error CS1503: Argument 2: cannot convert from 'System.Collections.Generic.List<int>' to 'string'

リストの比較に使われるのはEqual<T>(T expected, T actual)なので期待値と比較対象の型が違う場合は実行されず、Equal(string expected, string actual)を実行しようとしてコンパイルエラーになります。

NUnit

Expected: equivalent to < 30, 40 >
But was:  < "10", "20" >
Missing (2): < 30, 40 >
Extra (2): < "10", "20" >

ダブルクオートの有無で見分けなければいけませんが、まあ型が違う事は分かります。

MSTest

The expected collection contains 1 occurrence(s) of <10>.
The actual collection contains 0 occurrence(s).

ええ・・・