[.NET][C#]NUnitのClassic Modelアサーションメソッド一覧

# はじめに
NUnitのClassic Modelのアサーションメソッドがどんなものがあるのか一通り触ってみることにした。

# Classic Modelとは
NUnitのアサーションクラスには、テスト対象機能を評価する様々なメソッドが用意されており、
これらを使用してテスト評価対象の機能が想定した結果となるかどうかを評価する。
**Assert#Thatメソッド以外のテスト評価メソッド**を
**Classic Model**と言う。

# 実施環境
.NET：3.1.401
C#：8.0
NUnit：3.12.0

# Assertクラス アサーションメソッド一覧
| アサーションメソッド | 説明 | 補足 | 
| :--- | :--- | :--- | 
|True(bool)<br>IsTrue(bool)|引数の値がTrueであることを検査||
|False(bool)<br>IsFalse(bool)|引数の値がFalseであることを検査||
|Null(object)<br>IsNull(object)|引数の値がNullであることを検査||
|NotNull(object)<br>IsNotNull(object)|引数の値がNull以外であることを検査||
|Zero(数値型※1)|引数の値が0であることを検査||
|NotZero(数値型)|引数の値が0以外であることを検査||
|IsNaN(double)|引数の値がNaN(非数値)であることを検査||
|IsEmpty(string)<br>IsEmpty(Ienumerable)|引数の値が空であることを検査||
|IsNotEmpty(string)<br>IsNotEmpty(Ienumerable)|引数の値が空ではないことを検査||
|AreEqual(object, object)|2つの引数が同じ値であることを検査|補足2参照|
|AreNotEqual(object, object)|2つの引数が異なる値であることを検査|補足2参照|
|AreSame(object, object)|2つの引数が同じオブジェクトを参照していることを検査|補足2参照|
|AreNotSame(object, object)|2つの引数が異なるオブジェクトを参照していることを検査|補足2参照|
|Contains(object, ICollection)|第1引数のオブジェクトが第2引数のコレクションに含まれていることを検査||
|Greater(数値型, 数値型)<br>Greater(Icomparable, IComparable)|第1引数の値 > 第2引数の値　であることを検査||
|GreaterOrEqual(数値型, 数値型)<br>GreaterOrEqual(Icomparable, IComparable)|第1引数の値 >= 第2引数の値　であることを検査||
|Less(数値型, 数値型)<br>Less(Icomparable, IComparable)|第1引数の値 < 第2引数の値　であることを検査||
|LessOrEqual(数値型, 数値型)<br>LessOrEqual(Icomparable, IComparable)|第1引数の値 <= 第2引数の値　であることを検査||
|Positive(数値型)|引数の値が0越え(プラスの数値)であることを検査||
|Negative(数値型)|引数の値が0未満(マイナスの数値)であることを検査||
|IsInstanceOf(Type, object)|第2引数のオブジェクトが第1引数の型のインスタンスであることを検査|補足3参照|
|IsInstanceOf<T>(object)|引数のオブジェクトがジェネリクスで指定した型のインスタンスであることを検査|補足3参照|
|IsNotInstanceOf(Type, object)|第2引数のオブジェクトが第1引数の型のインスタンスでないことを検査|補足3参照|
|IsNotInstanceOf<T>(object)|引数のオブジェクトがジェネリクスで指定した型のインスタンスでないことを検査|補足3参照|
|IsAssignableFrom(Type, object)|第2引数のオブジェクトが第1引数の型のインスタンスを割り当てることができることを検査|補足3参照|
|IsAssignableFrom<T>(object)|引数のオブジェクトがジェネリクスで指定した型のインスタンスを割り当てることができることを検査|補足3参照|
|IsNotAssignableFrom(Type, object)|第2引数のオブジェクトが第1引数の型のインスタンスを割り当てることができないことを検査|補足3参照|
|IsNotAssignableFrom<T>(object)|引数のオブジェクトがジェネリクスで指定した型のインスタンスを割り当てることができないことを検査|補足3参照|
|Throws(Type, TestDelegate)|第2引数の関数が第1引数の型の例外をスローすることを検査|補足4参照|
|Throws(IResolveConstraint, TestDelegate)|第2引数の関数が第1引数の制約に一致する例外をスローすることを検査|補足4参照|
|Throws<T>(TestDelegate)|引数の関数がジェネリクスで指定した型の例外をスローすることを検査|補足4参照|
|ThrowsAsync(Type, AsyncTestDelegate)|第2引数の非同期関数が第1引数の型の例外をスローすることを検査||
|ThrowsAsync(IResolveConstraint, TestDelegate)|第2引数の非同期関数が第1引数の制約に一致する例外をスローすることを検査||
|ThrowsAsync<T>(AsyncTestDelegate)|引数の非同期関数がジェネリクスで指定した型の例外をスローすることを検査||
|DoesNotThrow(TestDelegate)|引数の関数が例外をスローしないことを検査||
|DoesNotThrowAsync(AsyncTestDelegate)|引数の非同期関数が例外をスローしないことを検査||
|Catch(TestDelegate)|引数の関数が例外をスローすることを検査|補足4参照|
|Catch(Type, TestDelegate)|第2引数の関数が第1引数の型の派生例外をスローすることを検査|補足4参照|
|Catch<T>(TestDelegate)|引数の関数がジェネリクスで指定した型の派生例外をスローすることを検査|補足4参照|
|CatchAsync(AsyncTestDelegate)|引数の非同期関数が例外をスローすることを検査||
|CatchAsync(Type, AsyncTestDelegate)|第2引数の非同期関数が第1引数の型の派生例外をスローすることを検査||
|CatchAsync<T>(AsyncTestDelegate)|引数の非同期関数がジェネリクスで指定した型の派生例外をスローすることを検査||
|Pass()|テスト結果を成功にする|備考5参照|
|Fail()|テスト結果を失敗にする|備考5参照|
|Ignore()|テストを無視する|備考5参照|
|Inconclusive()|テストを保留する？|備考5参照|
※1)数値型はint、uint、long、ulong、decimal、double、floatを指す

#補足

## 1. エラーメッセージ指定
各アサーションメソッドには、上記表の引数に加えて、テスト結果NG時のエラーメッセージを指定することができる。

* 例(Assert.AreEqualの場合)

```C#
[TestCase]
public void messageTest()
{
    string expected = "hoge";
    string actual = "fuga";
    Assert.AreEqual(expected, actual, $"メッセージが想定と異なります。expected={expected}, actual={actual}");
}
```
* テスト実行結果

```
合計 1 個のテスト ファイルが指定されたパターンと一致しました。
  X messageTest() [36ms]
  エラー メッセージ:
     メッセージが想定と異なります。expected=hoge, actual=fuga
  String lengths are both 4. Strings differ at index 0.
  Expected: "hoge"
  But was:  "fuga"
  -----------^
```



## 2. AreEqual、AreSame
AreEqualメソッドは、指定されたオブジェクトのEqualsメソッドによる検査を行う。
例として、下記テストはOKとなる。

```C#
        [TestCase]
        public void TestAreEqual()
        {
            string expected = new string("hoge");
            string actual = new string("hoge");

            // Test OK.
            Assert.AreEqual(expected, actual);
        }
```
変数`expected`と`actual`の参照先は異なるが、
string.Equalsメソッドによる判定で2つとも同値と判定されるため、テスト結果もOKとなる。
変数の参照先をテストしたい場合は、`Assert.AreSame`を使う。

```C#
        [TestCase]
        public void TestAreSame()
        {
            string expected = new string("hoge");
            string actual = new string("hoge");

            // Test NG.
            Assert.AreSame(expected, actual);
        }
```

また、配列やコレクションの検査も可能。
要素を格納しているクラスの型が異なっていても、保持している要素の値と位置が一致していれば同一と見なされる。
下記の一番↓のように同じ要素を持つ配列とListの比較結果はテストOKとなる。

```C#
        [TestCase]
        public void TestCollections()
        {
            // Test OK.
            string[] args1 = {"りんご", "バナナ", "オレンジ"};
            string[] args2 = {"りんご", "バナナ", "オレンジ"};
            Assert.AreEqual(args1, args2);
            
            // Test OK.
            List<string> list1 = new List<string>(){ "りんご", "バナナ", "オレンジ"};
            List<string> list2 = new List<string>(){ "りんご", "バナナ", "オレンジ"};
            Assert.AreEqual(list1, list2);
            
            // Test OK.
            Assert.AreEqual(args1, list2);

        }
```

要素が一致していても、格納位置が異なる場合はテストNGとなる。

```C#

        [TestCase]
        public void TestCollections_NG()
        {
            // Test NG.
            string[] args1 = {"りんご", "バナナ", "オレンジ"};
            string[] args2 = {"りんご", "オレンジ", "バナナ"};
            Assert.AreEqual(args1, args2);
        }
        
```

## 3. IsInstanceOf、IsAssignableFrom
IsInstanceOfメソッドは、引数の`object`が第1引数もしくはジェネリクスに指定した型かそのサブクラスの型であることを検査する。
IsAssignableFromは、引数の`object`に第1引数もしくはジェネリクスに指定した型かそのサブクラスの型の変数を割り当て可能かを検査する。
（後者のメソッドはあまり使わなそうだ・・・）

```C#
        public class Target {}
        public class TargetSub : Target {}

        [TestCase]
        public void TestIsInstanceOf()
        {
            // Test OK.
            Assert.IsInstanceOf<Target>(new Target());
            // Test OK.
            Assert.IsInstanceOf<Target>(new TargetSub());
            // Test NG.
            Assert.IsInstanceOf<TargetSub>(new Target());
        }
        
        [TestCase]
        public void TestIsAssignableFrom()
        {
            // Test OK.
            Assert.IsAssignableFrom<TargetSub>(new Target());
            // Test OK.
            Assert.IsAssignableFrom<Target>(new Target());
            // Test NG.
            Assert.IsAssignableFrom<Target>(new TargetSub());
        }
```

## 4. Throws、Catch
Throwsメソッドは検査対象がスローする例外の正しさを検査する。
第1引数、もしくはジェネリクスにスローされるであろう例外を指定する。
第2引数（ジェネリクスで例外指定の場合は第1引数）に例外をスローするテストデリゲートを実装した関数を渡す。
下記のようにラムダ式で書くのが楽。

```C#
        public class MyException : Exception {}
        public class MyClass {
            public static void TestTargetFunc() { throw new MyException(); }
        }

        [TestCase]
        public void TestThrows()
        {
            // Test OK.
            Assert.Throws<MyException>(() => MyClass.TestTargetFunc());
        }
```
また、第1引数に制約を指定することで、テストOKとなる条件を詳細に指定することができる。

```C#
        public class MyClass2 {
            public static void TestTargetFunc() { throw new Exception("ErrorCode:9999"); }
        }

        [TestCase]
        public void TestThrowsConst()
        {
            // Test OK.
            Assert.Throws(
                Is.TypeOf<Exception>()
                .And.Message.EqualTo("ErrorCode:9999"),
                () => MyClass2.TestTargetFunc()
            );
        }
```
上記のようにスローされる例外オブジェクトのどこかしらにエラーコードがあって
その内容を検査したいときなどはこちらを使えば良さそう。

CatchメソッドはThrowsメソッドに似ているが、
こちらは指定した例外の派生例外も検査OKとなる。

```C#

        public class MyException : Exception {}

        public class MyClass {
            public static void TestTargetFunc() { throw new MyException(); }
        }

        [TestCase]
        public void TestThrows()
        {
            // Test OK.
            Assert.Throws<MyException>(() => MyClass.TestTargetFunc());
            // Test NG.
            Assert.Throws<Exception>(() => MyClass.TestTargetFunc());
        }

        [TestCase]
        public void TestCatch()
        {
            // Test OK.
            Assert.Catch<MyException>(() => MyClass.TestTargetFunc());
            // Test OK.
            Assert.Catch<Exception>(() => MyClass.TestTargetFunc());
        }

```

また、Catchメソッドには引数にもジェネリクスにもスローされる例外を指定しないテストデリゲート関数のみ指定するメソッドがある。
これを使用した場合、関数内でなんらかの例外がスローされれば、テスト結果はOKとなる。
つまり、例外をスローするかしないかを検査する。

```C#
        public class MyClass2 {
            public static void ThrowException() { throw new MyException(); }
            public static void NoException() {  }
        }

        [TestCase]
        public void TestCatch2()
        {
            // Test OK.
            Assert.Catch(() => MyClass2.ThrowException());
            // Test NG.
            Assert.Catch(() => MyClass2.NoException());
        }
```

## 5. Pass、Fail、Ignore、Inconclusive
Pass、Fail、Ignore、Inconclusiveの4つのメソッドは実行結果を検査するメソッドではなく、
テスト結果を決定するメソッドである。
検査ロジックは自分達が実装したい場合などに使う。

```C#
        [TestCase]
        public void TestPass()
        {
            Assert.Pass();
        }
        
        [TestCase]
        public void TestFail()
        {
            Assert.Fail();
        }
        
        [TestCase]
        public void TestIgnore()
        {
            Assert.Ignore();
        }
        
        [TestCase]
        public void TestInconclusive()
        {
            Assert.Inconclusive();
        }
```
これらのテスト実行結果は下記のようになる。
![スクリーンショット 2020-10-05 213039.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/712352/8f1d949e-b90b-750e-368c-d8095bcb105b.png)
御覧の通りテスト結果をコントロールできる。
テストメソッドにロジックを書くのはあまりよくないとは思うが、
場合によっては使える場面があるかもしれない。

Inconclusiveメソッドはテスト結果としてどうなったのかが出力されない。
恐らく該当テストメソッドのテストが正しく機能するか分からないときなどのマーキングに使う？

なお、いずれのメソッドも呼ばれたタイミングでそのテストメソッドは終了する。
これらのメソッド以降にコードを書いても実行されることは無い。

また、他のアサーションメソッドと同様、
引数にメッセージを指定することができる。

```C#
        [TestCase]
        public void TestPass()
        {
            Assert.Pass("成功だよ");
        }
        
        [TestCase]
        public void TestFail()
        {
            Assert.Fail("失敗だ！");
        }
        
        [TestCase]
        public void TestIgnore()
        {
            Assert.Ignore("無視するよ");
        }
        
        [TestCase]
        public void TestInconclusive()
        {
            Assert.Inconclusive("このテストは●●だから保留だよ");
        }
```
`dotnet test --logger "console;verbosity=detailed"`コマンドを実行すると以下のように結果にメッセージを出力できる。
（テスト成功時のメッセージの出力方法は分からず・・・）
![スクリーンショット 2020-10-05 214819.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/712352/0223b294-51de-c481-e392-e1d0abdff4df.png)

# まとめ
Assertクラスのメソッドは大体こんな感じ。
Assertクラスの他にも以下のアサーションクラスがある。

* StringAssert
* CollectionAssert
* FileAssert
* DirectoryAssert

Assertクラスのメソッドまとめるだけでも結構疲れたのでここら辺はまた今度:tea:

# 参考
[NUnit公式 Classic Modelメソッド一覧](https://docs.nunit.org/articles/nunit/writing-tests/assertions/assertion-models/classic.html)