[NUnit]列挙子でテストケースを量産する

Qiita C# Advent Calendar 2014 16日目のエントリーになります。

どうも、僕です。ワクワクするコード書いてますか?

NUnitのテストがオールグリーンになる瞬間ってワクワクしますよね。
ということで、NUnitのTestCaseの作り方について。
 

適当なテストのターゲットとして、以下のようなTimeCheckerクラスの
IsValidTimeメソッドを想定しておきます。
ただ単に時間が0-23の範囲で分が0-59の間であることを確認するだけのもの。

public class TimeChecker
{
    public bool IsValidTime(int hour, int minute)
    {
        return 0 <= hour && hour <= 23 && 0 <= minute && minute <= 59;
    }
}

ベーシックなテストの書き方からいくと、このメソッドのテストは
たとえば以下のような感じになる。

[TestFixture]
public class TimeCheckerTest
{
    [Test]
    public void 時刻の値として2時30分が正しいことを判定する()
    {
        Assert.IsTrue(new TimeChecker().IsValidTime(2, 30));
    }
    [Test]
    public void 時刻の値として2時90分が正しくないことを判定する()
    {
        Assert.IsFalse(new TimeChecker().IsValidTime(2, 90));
    }
}

 

さてここで、NUnit 2.5からはTest Case属性が使えるので、
これを利用しない手はないですね。

TestCaseを使って書き直すとこんな感じ。

[TestFixture]
public class TimeCheckerTest
{
    [TestCase(2, 30, Result = true, TestName = "2:30")]
    [TestCase(2, 90, Result = false, TestName = "2:90")]
    public bool 時刻の値が妥当かどうかを判定する(int hour, int minute)
    {
        return new TimeChecker().IsValidTime(hour, minute);
    }
}

 

さらに、TestCase属性でパラメータを指定する代わりに
TestCaseSourceを使えば、コンパイル定数でない値をパラメータに使えたり、
より柔軟な書き方が可能になる。

TestCaseSourceは、メソッドのパラメータの順番通りに値を返すものでされあれば
object[]とかでもいいのだけど、ここはNUnitで用意してくれている
TestCaseDataクラスを使いましょう。

[TestFixture]
public class TimeCheckerTest
{
    private static TestCaseData[] isValidTestSource
        = new[] 
        {
            new TestCaseData(2, 30).Returns(true).SetName("2:30"),
            new TestCaseData(2, 90).Returns(false).SetName("2:90"),
        };

    [TestCaseSource("isValidTestSource")]
    public bool 時刻の値が妥当かどうかを判定する(int hour, int minute)
    {
        return new TimeChecker().IsValidTime(hour, minute);
    }
}

 

ここからようやく本題。
TestCaseSourceはIEnumerableでさえあればいいので、
たとえばプロパティにして↓こんなふうに書いても良いのだ。

private static IEnumerable<TestCaseData> IsValidTestSource
{
    get
    {
        yield return new TestCaseData(2, 30).Returns(true).SetName("2:30");
        yield return new TestCaseData(2, 90).Returns(false).SetName("2:90");
    }
}

んでまあ必要性からいえば、せいぜい
0:00, 23:59, -1:00, 0:-1, 24:59, 23:60
あたりをテストしておけば十分でしょうけど、
んなちいせぇことを言わず、なんなら正常パターン全部並べたって
こんな程度ならたいして実行時間かかんないわけですよ。

テストケース作るのだって全然簡単!

private static IEnumerable<TestCaseData> IsValidTestSource
{
    get
    {
        for (int h = 0; h < 24; h++)
        {
            for (int m = 0; m < 60; m++)
            {
                yield return new TestCaseData(h, m)
                                .Returns(true)
                                .SetName(h + ":" + m.ToString("D2"));
            }
        }
    }
}

実行してみる。

testcase_enumeration
ひゃっはー。1440ケース!

補足ですが、実はNUnitの機能としてパラメータにRangeだとかValuesだとかっていう
属性を指定することができて、今回くらいの例であれば
Enumerateとかするまでもなく同じことが可能だったりする。

ですがまあ、yield returnで動的にテストケースを組み立てていけるよっていうのは
知っておくと多少は便利かと思います。

あと一応注意点としては、「動的に」とか言ったけども
テストが実行されるのは、すべてのテストケースの列挙が終わった後なんで、
同じ変数の値を入れ替えつつyield returnとかやってると
アレ?ってことになりがちです。