◆ Assert 系では Equals と ReferenceEquals は使わない
  ◆ Object のメソッドで true/false 返すだけで Assert 機能はない
  ◆ Assert.Equals だけオーバーライドして AreEqual 使うようにメッセージ出る
◆ CollectionAssert でコレクションの中身で Assert
  ◆ AreEqual で順番通り AreEquivalent で順不同

MSTest に v2 ができてたという記事を書いて そういえば昔テストに関して書いたときに Assert メソッドのまとめ記事を書こうとして書いてなかったことを思い出しました
ちょうど忘れかけててまた調べたので書いておきます

Is***

見たままです

[TestClass()]
public class Tests
{
[TestMethod()]
public void test()
{
Assert.IsTrue(true); // pass
Assert.IsTrue(false); // fail
Assert.IsFalse(true); // fail
Assert.IsFalse(false); // pass
Assert.IsNull(null); // pass
Assert.IsNull(false); // fail
Assert.IsNull(new { }); // fail
}
}

instanceof

is 演算子みたいなもの
親クラスや Interface のインスタンスでもある

[TestClass()]
public class Tests
{
class A { }
class B : A { }

[TestMethod()]
public void test()
{
Assert.IsInstanceOfType(10, typeof(int));
Assert.IsInstanceOfType(10L, typeof(long));
Assert.IsInstanceOfType("text", typeof(string));
Assert.IsInstanceOfType(DateTime.Now, typeof(DateTime));
Assert.IsInstanceOfType(new B(), typeof(A));
Assert.IsInstanceOfType(new { }, typeof(object));
Assert.IsInstanceOfType(new List<int>(), typeof(IList<int>));
}
}

型指定なしの Generics のインスタンスではない

Assert.IsNotInstanceOfType(new Dictionary<int, string>(), typeof(Dictionary<,>)); // fail

null は instanceof でも notinstanceof でもない

Assert.IsInstanceOfType(null, typeof(string)); // fail
Assert.IsNotInstanceOfType(null, typeof(string)); // fail

匿名型は順番と名前と型が一緒だと同じ型になるので

var a = new { a = 1, b = 1 };
var b = new { a = 0, b = 1 };
var c = new { b = 1, a = 0 };
var d = new { a = false, b = "text" };
Assert.IsInstanceOfType(a, b.GetType());
Assert.IsNotInstanceOfType(a, c.GetType());
Assert.IsNotInstanceOfType(a, d.GetType());

↑ all passed

Fail

失敗させるメソッド

Assert.Fail(); // fail

Assert.Inconclusive(); // fail

Inconclusive はスキップ扱い
エラーアイコンではなく警告アイコンが出る

Throw

例外が出ることを確かめる

class ExampleException : Exception { }

[TestMethod()]
public void test()
{
Assert.ThrowsException<Exception>(() => { }); // fail
Assert.ThrowsException<Exception>(() => { throw new Exception(); }); // pass
Assert.ThrowsException<Exception>(() => { throw new ExampleException(); }); // fail
}

継承した型でもだめ
実行するものがわかってれば正確な例外のクラスがわかるはずだからちゃんと書くように ということなのかも

やりたいなら ラムダ式で例外変換すればできる
例えば いくつかの指定した例外の中のどれかでいいならこういうの

Assert.ThrowsException<ExampleException>(() =>
{
try
{
Example1.method();
}
catch (Exception ex) when (
ex is InvalidOperationException
|| ex is ArgumentNullException
|| ex is KeyNotFoundException
|| ex is NullReferenceException)
{
throw new ExampleException();
}
});

↑ passed

Exception 全部なら when のところで絞り込まないようにすればいい

何度も書くの大変だからこの処理をする Assert メソッドを自作したほうが良いかも
というか Assert 関数作るなら 投げ直して ThrowsException で取るより catch 句で Exception を instanceof でチェックしたほうが良さそう

探せばもっといい方法ありそう?

Equals

値が同じ確認してくれそうな Equals メソッド

var x = 1;
var y = 1;
Assert.Equals(x, x); // fail
Assert.Equals(x, y); // fail
var z = new { };
Assert.Equals(z, z); // fail

x 同士でもエラー
メソッドの説明は

// 概要:
// 静的な Equals オーバーロードは、2 つの型のインスタンスを比較して参照の等価性を調べる ために使用されます。2 つのインスタンスを比較して等価性を調べるためにこのメソッドを使用
// することはできません。このオブジェクトは常に Assert.Fail を使用してスロー します。単体テストでは、Assert.AreEqual および関連するオーバーロードをご使用ください。

「使用することはできません」
常に Fail みたい

ReferenceEquals

参照を比較しそうな ReferenceEquals

var x = 1;
var y = 1;
var z = 2;
Assert.ReferenceEquals(x, x);
Assert.ReferenceEquals(x, y);
Assert.ReferenceEquals(x, z);
var a = new { a = 1 };
var b = new { b = 2 };
Assert.ReferenceEquals(a, b);

全部通る

Equals メソッドは Assert で定義されてるけど ReferenceEquals は Object で定義されてる Object.ReferenceEquals
つまり参照が一致するかを boolean 値で返すだけ
Assert 機能はない

var x = 1;
var y = new { a = 1 };
var r1 = Assert.ReferenceEquals(x, x);
var r2 = Assert.ReferenceEquals(y, y);
Assert.IsFalse(r1);
Assert.IsTrue(r2);

この False/True チェックが通る

AreEqual

値が等しいかの確認は AreEqual

var a = 1;
var b = 1;
var c = 1m;
var d = 1L;
var e = 1f;
Assert.AreEqual(a, a);
Assert.AreEqual(a, b);
Assert.AreEqual(a, c);
Assert.AreEqual(a, d);
Assert.AreEqual(a, e);

↑ all passed

// 概要:
// 指定した値どうしが等しいかどうかをテストして、 2 つの値が等しくない場合は例外をスローします。論理値が等しい場合であっても、異なる数値型は 等しくないものとして処理されます。42L
// は 42 とは等しくありません。

型が違う 42L と 42 は等しくないと書いてるのに
やってみたら等しいらしい

値比較なので同じデータを設定して別途作った構造体は等しいけど
クラスの場合は NotEqual

class Class1 { }

[TestMethod()]
public void test()
{
var a = new DateTime(0);
var b = a;
var c = new DateTime(0);
var x = new Class1();
var y = x;
var z = new Class1();
Assert.AreEqual(a, a);
Assert.AreEqual(a, b);
Assert.AreEqual(a, c);
Assert.AreEqual(x, x);
Assert.AreEqual(x, y);
Assert.AreNotEqual(x, z);
}

↑ all passed

null 同士は型が違っても一緒

Assert.AreEqual(null, null);
Assert.AreEqual((string)null, (Class1)null);

== だと false になる NaN 同士も AreEqual では同じ

Assert.IsFalse(double.NaN == double.NaN);
Assert.AreEqual(double.NaN, double.NaN);
Assert.AreEqual(double.NaN, float.NaN);

同じ型指定でエラー起きた時だと

var x = new Class1();
var z = new Class1();
Assert.AreEqual(x, z);

メッセージ: Assert.AreEqual に失敗しました。<ConsoleApp1.Tests.Tests+Class1> が必要ですが、<ConsoleApp1.Tests.Tests+Class1> が指定されています。

エラーメッセージみても何が悪いのかがわからない
一緒じゃん!

AreSame

AreSame は参照を比較なので

var x = 1;
var y = 1;
Assert.AreSame(x, x); // fail
Assert.AreSame(x, y); // fail

値型は全部だめ
同じ変数でもだめ
それぞれを個別にオブジェクト化して変換してるみたい

var x = 1;
var y = (object)x;
var z = (object)x;
Console.WriteLine(y == z);
// false

こういうこと

親切なメッセージもある

メッセージ: Assert.AreSame に失敗しました。AreSame() には値型を渡すことはできません。オブジェクトに変換された値が同じにはなりません。AreEqual() を使用することを検討してください。

値型なので構造体でも

var x = new DateTime(0);
var y = new DateTime(0);
Assert.AreEqual(x, y);
Assert.AreNotSame(x, y);

参照が同じならベースクラスにキャストしていても同じ
null 同士も同じ

var a = new Class1();
var b = a;
var c = (object)a;
var d = (object)a;
var e = new Class1();
Assert.AreSame(a, b);
Assert.AreSame(c, d);
Assert.AreNotSame(a, e);
Assert.AreSame(null, null);

↑ all passed

String Assert

文字列のアサートはみたまま

StringAssert.StartsWith("abcde", "ab");
StringAssert.EndsWith("abcde", "de");
StringAssert.Contains("abcde", "cd");
StringAssert.Matches("<10>", new Regex(@"<\d+>"));
StringAssert.DoesNotMatch("abcd", new Regex(@"^[abc]+$"));

StringAssert にも Equals と ReferenceEquals がある
実体は両方 Object のメソッド
等しいかのチェックだけ

Assert.IsTrue(StringAssert.Equals(1, 1));
Assert.IsFalse(StringAssert.Equals(1, 2));

↑ all passed


Collection Assert

コレクションクラスを普通に AreEqual や AreSame しても参照でしかチェックされない
中身でチェックしてくれるのが CollectionAssert のメソッド

var lw = new List<int> { 1, 2 };
var lx = new List<int> { 1, 2 };
var ly = new List<int> { 2, 1 };
var lz = new List<int> { 3, 4 };

Assert.AreNotEqual(lw, lx);
Assert.AreNotSame(lw, lx);
CollectionAssert.AreEqual(lw, lx);
CollectionAssert.AreEquivalent(lw, lx);

CollectionAssert.AreNotEqual(lw, ly);
CollectionAssert.AreEquivalent(lw, ly);

CollectionAssert.AreNotEqual(lw, lz);
CollectionAssert.AreNotEquivalent(lw, lz);

↑ all passed

AreEqual は順番も一緒
AreEquivalent は順不同

含まれるか
サブセットか
ユニークか
null が存在しないか
全部が指定クラスのインスタンスか

をチェックするメソッドもある

CollectionAssert.Contains(new List<string> { "ab", "bc" }, "bc");
CollectionAssert.DoesNotContain(new List<int> { 1, 2 }, 3);
CollectionAssert.IsSubsetOf(new List<int> { 1, 2 }, new List<object> { 1, "str", 2, "text" });
CollectionAssert.IsNotSubsetOf(new List<int> { 1, 3 }, new List<object> { 1, "str", 2, "text" });
CollectionAssert.AllItemsAreUnique(new List<int> { 1, 2, 3 });
CollectionAssert.AllItemsAreUnique(new List<int> { 1, 2, 1 }); //fail
CollectionAssert.AllItemsAreNotNull(new List<string> { "a", "b" });
CollectionAssert.AllItemsAreNotNull(new List<string> { "a", null }); // fail
CollectionAssert.AllItemsAreInstancesOfType(new List<object> { "x", "y" }, typeof(string));
CollectionAssert.AllItemsAreInstancesOfType(new List<object> { "x", 3 }, typeof(string)); // fail

↑ all passed

CollectionAssert でも Equals と ReferenceEquals は Object のメソッド

Assert.IsTrue(CollectionAssert.Equals(1, 1));
Assert.IsFalse(CollectionAssert.Equals(1, 2));

↑ all passed

Equals と ReferenceEquals

全クラスに継承されるメソッドで存在するから Assert.Equals もある
名前的に Assert として等価確認に使うものと思いそうだから Assert.Equals をオーバーライドして エラーと等価確認の Assert には AreEqual メソッドを使ってとメッセージを出す親切なつくり?
それならもう CollectionAssert 系や ReferenceEquals もしてくれたらいいのに