【C#】単体テストで意図通りの例外が発生しているか確認する方法

# 単体テストで意図通りの例外が発生しているか確認する方法

皆さん、こんにちは！
[前回の記事](https://qiita.com/KM_20005/items/3e4a1ac0f38d82f74cc7)ではC#で単体テストの実装を行う方法をまとめました。



今回は渡された引数が異常である場合などに発生させる例外が意図通りスローされているか確認する方法に学習しテストの品質を上げていこうと思います。
学習には下記を参考にしました。

https://learn.microsoft.com/ja-jp/visualstudio/test/walkthrough-creating-and-running-unit-tests-for-managed-code?view=vs-2022#use-unit-tests-to-improve-your-code

## 環境

- Windows11
- Visual Studio 2022

## テスト対象の準備

テスト対象として引数で渡された数値が偶数か判定する`IsEven()`メソッドを実装しました。ただし「引数は０以上に限定し、負の数が渡された場合には例外を出力する」という仕様があるものとします。

``` C#
public bool IsEven(int x)
{
    if(x < 0)
    {
        // 負の数
        throw new ArgumentOutOfRangeException();
    }

    if(x % 2 == 0)
    {
        // 偶数
        return true;
    }
    else
    {
        // 奇数
        return false;
    }
}
```

## テストの実装

例外が発生しているかの判定には`Assert`クラスの`ThrowsException()`メソッドを使用しこちらのようにテストメソッドを実装します。

```C#
[TestMethod]
public void IsEven_ExceptionTest()
{
    var prog = new Prog();

    // テストの実行
    Assert.ThrowsException<ArgumentOutOfRangeException>(() => prog.IsEven(-1));
}
```

### 解説

`ThrowsException<ArgumentOutOfRangeException>`で期待値の例外の種類を指定したのちに、ラムダ式にてテスト対象のメソッド`IsEven(-1)`を記載しています。

このテストでは`ArgumentOutOfRangeException`が発生した場合のみテスト結果がOKになりそれ以外の場合(例外が発生しないor別の例外が発生)はテスト結果がNGになります。


## さらに詳細に例外についてテストを追加

発生している例外について詳しくテストするコードを実装します。

このメソッドに「100より大きい整数が渡された場合も例外(`ArgumentOutOfRangeException`)を出力する。」という仕様を追加します。

`ArgumentOutOfRangeException`は複数のコンストラクターを持つことが可能で必要に応じてスローする際に情報を追加することが可能です。今回はその中の一つ[ArgumentOutOfRangeException(String, String)](https://learn.microsoft.com/ja-jp/dotnet/api/system.argumentoutofrangeexception.-ctor?view=net-8.0#system-argumentoutofrangeexception-ctor(system-string-system-string))を使用します。こちらは変数名とエラーメッセージをそれぞれ格納することができます。

ですので今回は「100より大きい整数が指定された」のか「０より小さい整数が指定された」のかを`ArgumentOutOfRangeException`のエラーメッセージに格納します。

実装したコードが下記になります。

```C#
public bool IsEven(int x)
{
    if(x < 0)
    {
        // 負の数
        throw new ArgumentOutOfRangeException("x","x is less than 0");
    }

    if(x > 100)
    {
        // 100より大きい
        throw new ArgumentOutOfRangeException("x","x is more than 100");
    }

    if(x % 2 == 0)
    {
        // 偶数
        return true;
    }
    else
    {
        // 奇数
        return false;
    }
}
```

ここでテストとして`ThrowsException()`を使用して`ArgumentOutOfRangeException`が発行されているか確認する方法ももちろん大丈夫ですが今回はもう少し踏み込で、例外発行時に設定したメッセージまで検証したいと思います。

``` C#
[TestMethod]
public void IsEven_ExceptionMessageTest() 
{
    var prog = new Prog();

    // テストの実施
    try
    {
        prog.IsEven(-1);
    }
    catch(ArgumentOutOfRangeException ex)
    {
        StringAssert.Contains(ex.Message, "x is less than 0");
        return;
    }

    Assert.Fail();
}
```

### 解説

先ほどまでと異なりメソッドの実行を`try-catch`節の中に移動し例外が、発生したときのみ`catch`節の判定処理に進むようになっています。例外が発生しなかった場合にテスト結果がNGになるように処理の最後に`Assert.Fail()`メソッドを呼び出し強制的にNGにしています。

またテストの判定に`StringAssert`クラスを使用して判定を行っています。`Contains()`メソッドは指定した文字列に第二引数の文字列が含まれているかを検証しOKかNGを判定します。

このテストケースの`catch`節は`ArgumentOutOfRangeException`をキャッチしているためテストケース全体としては「`IsEven()`内で`ArgumentOutOfRangeException `を発行して、なおかつそのエラーメッセージが"x is less than 0"である」ケースのみテストOK になります。

このように１つのメソッド内で複数の同じ種類の例外を発行しているような状況でより詳細にテストを行うことが可能です。

> 追記
> `IsEven()`内の処理的に`StringAssert`を使用せずとも十分にテストできている状態だったため「100以上で例外発行」の処理を追加しました。
## 最後に

ここまで、ご覧くださりありがとうございます。
例外をスロー単体テストの作成方法をまとめてみました！

戻り値がbool型の場合についてのみ扱いましたが、そのほかの型の場合にもAssertクラスのメソッドを用いることで同様にテストを行うことができます。また、ほかにも意図した例外が発生しているか確認することも可能です。

この記事で皆様のコーディングライフの助けになれれば幸いです！
ではまた次の記事で！！