NCrunchでC#のライブユニットテストを試してみた

# 概要

この記事では、C# での FizzBuzz っぽいプログラム作成を通じて、NCrunch でライブユニットテストを試してみました。

## 対象読者

* C# を使っている人
* 簡単なコンソールアプリケーションを書き始め、入門書を一通り読んだぐらいのレベルの人
* テスト駆動開発を、ほんの少しだけかじってみたい人
* ライブユニットテストを使いたいけれど、諸事情で Visual Studio 2017 Enterprise を使わせてもらえない人

## 実行環境

* OS: Windows 7 Pro SP1 64bit（8や10でもOK）
* IDE: Visual Studio Professional 2013 （NCrunchが動作するバージョンならOK）
* NCrunch: NCrunch 3.6

# ライブユニットテストについて

ライブユニットテストとは、コードを書きながら、リアルタイムで単体テストが走り、結果を教えてくれるものです。Visual Studio 2017 Enterprise に搭載されて話題になったようです。

[Microsoft、Visual Studio 2017にライブユニットテストを提供（InfoQ）](https://www.infoq.com/jp/news/2017/02/Live-Unit-Testing)

使いこなせると非常に便利なのですが、予算の都合などで、Enterprise 版の Visual Studio を使えない人も多いかと思います。比較的安価なものとして、**NCrunch** というプラグインで、同様のことができるそうです。

# 事前準備

## NCrunch のインストール

2017年4月現在、このツールは有料で、個人ライセンスが 159米ドル（1ドル110円として、17,490円）です。
企業向けライセンスは 289米ドル（同じく、31,790円）です。
30日の体験版があります。英語のツールですが、簡単な単語しか出てこないので安心してください。

NCrunch は、以下のサイトからダウンロードできます。

[http://www.ncrunch.net/](http://www.ncrunch.net/)

まず、Download NCrunch をクリックします。
お使いのバージョンの Visual Studio の Download Installer をクリックすると、インストーラがダウンロードされます。

Visual Studio が起動している場合は終了してから、ダウンロードしたインストーラを実行し、NCrunch をインストールします。

## NCrunch の起動

Visual Studio を起動したら、画面上部のメニューバーに「NCRUNCH」が追加されています。「Enable NCrunch」をクリックすると、NCrunch が起動します。ウィザード形式で質問されることがありますが、すべてデフォルト値のまま先に進んで構いません。

![NCrunchMenu.png](https://qiita-image-store.s3.amazonaws.com/0/177927/84f6188a-b351-26a5-c006-6196d4fe3b36.png)

# 作成するプログラム

いわゆる [FizzBuzz](https://ja.wikipedia.org/wiki/Fizz_Buzz) のプログラムを作成します。
今回は、Wikipedia の例とは異なり、入力した値に対応した文字列を出力するプログラムとします。
また、負の数はすべて、入力値をそのまま文字列にします。

```
C:\> MyProgram.exe
整数を入力: 3
Fizz

C:\> MyProgram.exe
整数を入力: 5
Buzz

C:\> MyProgram.exe
整数を入力: 15
FizzBuzz

C:\> MyProgram.exe
整数を入力: 17
17

C:\> MyProgram.exe
整数を入力: -3
-3

C:\>
```

# コンソールアプリケーションの作成

ツールバーの「新しいプロジェクト」をクリックし、`MyProgram` プロジェクトを作成します。
コンソールアプリケーションとして作成します。

![CreateMyProgram.png](https://qiita-image-store.s3.amazonaws.com/0/177927/ff359b97-d15d-f2b4-cc36-43569233f747.png)

## InternalsVisibleTo の設定

`MyProgram` が、単体テストプロジェクトから見えるように、設定が必要です。
`Properties` の中にある `AssemblyInfo.cs` の末尾に、以下の行を追加します。

```csharp
[assembly: InternalsVisibleTo("MyProgramTest")]
```

## スタブの作成

FizzBuzz メソッドのスタブを作成します。。
以下のコードを、`Program.cs` の `MyProgram` クラスに追加します。

```csharp
/// <summary>
/// いわゆるFizzBuzzプログラムです。
/// 3の倍数のときは"Fizz"、5の倍数のときは"Buzz"、両方に該当するときは"FizzBuzz"を出力します。
/// 負の数や、どちらにも該当しないときは、与えられた数字を文字列として出力します。
/// </summary>
/// <param name="number">判定する数字</param>
/// <returns>FizzBuzzの出力を文字列で返します。</returns>
internal static string FizzBuzz(int number)
{
	// TODO: スタブ
	return null;
}
```

ここで、先頭にスラッシュ3個のコメントや、XMLのタグがあります。
これは、**ドキュメントコメント**といいます。
ソースコードをツールに通すと、HTMLなどの形式で、ドキュメントが自動生成されます。

また、先頭に `TODO` と書かれたコメントがあります。
TODO コメントは、Visual Studio のタスク一覧で、まとめて確認することができます。

![TaskList.png](https://qiita-image-store.s3.amazonaws.com/0/177927/a6e12de8-63e5-5f68-f88a-7aa385be9716.png)

詳細は、MSDNの以下のページを参照してください。

[ドキュメント コメント用の推奨タグ（C# プログラミング ガイド）](https://msdn.microsoft.com/ja-jp/library/5ast78ax.aspx)

[方法: タスク一覧のコメントを作成する](https://msdn.microsoft.com/ja-jp/library/zce12xx2.aspx)


# 単体テストプロジェクトの作成

作業中のソリューションに、単体テストプロジェクトを追加します。
ソリューション `MyProgram` を右クリックし、「追加」→「新しいプロジェクト」をクリックします。
名前を `MyProgramTest` とし、単体テストプロジェクトを作成します。

![NewProject.png](https://qiita-image-store.s3.amazonaws.com/0/177927/212c4962-1729-7bcb-c4e3-8a4020d81157.png)

![NewProject2.png](https://qiita-image-store.s3.amazonaws.com/0/177927/232e02de-2c33-dcfc-4d96-a2c68b282274.png)

## ユニットテストクラスの作成

`MyProgramTest` プロジェクトに `UnitTest1.cs` が作成されているので、`FizzBuzzTest.cs` にリネームします。

## 参照の追加

ユニットテストプロジェクトから `MyProgram` が見えるように、設定が必要です。

`MyProgramTest` の参照設定を右クリックし、**参照の追加**をクリックします。`MyProgram` にチェックを入れ、OKボタンをクリックします。

![AddReference.png](https://qiita-image-store.s3.amazonaws.com/0/177927/4537e77c-3006-54f7-2c8c-0205ef36cbc6.png)

## テストメソッドの追加

`FizzBuzzTest.cs` に、テストを記述していきます。
今回作成するプログラムのテストとして、以下のようなケースが考えられます。

* 3の倍数だが、5の倍数でない
* 5の倍数だが、3の倍数でない
* 3の倍数かつ5の倍数
* 3の倍数でも5の倍数でもない

上記に加えて、以下のようなテストケースも考えられます。

* 0の場合
* 負の数の場合
* 整数でない場合（今回は引数が int 型なので考えません）

上記のケースを、テストコードとして実装していきます。

* テスト用のメソッドには、先頭に `[TestMethod]` をつけます。
* `Assert.AreEqual(string expected, string actual)` は、2つの引数が等しいかどうかをテストします。条件に合致しない場合、実行が止まり、そのテストは失敗となります。
	* 第1引数: 仕様で想定している出力結果
	* 第2引数: テスト対象プログラムの実行結果
* メソッド内のすべてのAssertを通過すれば、そのテストは合格となります。
* Assert 系メソッドは、他にも種類があります。詳細は、MSDNの [Assert クラス](https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.assert.aspx) を参照してください。

```csharp
// FizzBuzzTest.cs
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyProgram;

namespace MyProgramTest
{
    [TestClass]
    public class FizzBuzzTest
    {
        /// <summary>
        /// 3の倍数のテストです。
        /// </summary>
        [TestMethod]
        public void TestFizzBuzz_3()
        {
            Assert.AreEqual("Fizz", Program.FizzBuzz(3));
            Assert.AreEqual("Fizz", Program.FizzBuzz(6));
        }

        /// <summary>
        /// 5の倍数のテストです。
        /// </summary>
        [TestMethod]
        public void TestFizzBuzz_5()
        {
            Assert.AreEqual("Buzz", Program.FizzBuzz(5));
            Assert.AreEqual("Buzz", Program.FizzBuzz(10));
        }

        /// <summary>
        /// 3の倍数かつ5の倍数のテストです。
        /// </summary>
        [TestMethod]
        public void TestFizzBuzz_Both()
        {
            Assert.AreEqual("FizzBuzz", Program.FizzBuzz(15));
            Assert.AreEqual("FizzBuzz", Program.FizzBuzz(30));
        }

        /// <summary>
        /// 3の倍数でも5の倍数でもない数のテストです。
        /// </summary>
        [TestMethod]
        public void TestFizzBuzz_OtherNaturalNumber()
        {
            Assert.AreEqual("2", Program.FizzBuzz(2));
            Assert.AreEqual("7", Program.FizzBuzz(7));
        }

        /// <summary>
        /// 自然数でない数（0、負数）のテストです。
        /// </summary>
        [TestMethod]
        public void TestFizzBuzz_NotNaturalNumber()
        {
            Assert.AreEqual("0", Program.FizzBuzz(0));
            Assert.AreEqual("-1", Program.FizzBuzz(-1));
            Assert.AreEqual("-3", Program.FizzBuzz(-3));
            Assert.AreEqual("-5", Program.FizzBuzz(-5));
            Assert.AreEqual("-15", Program.FizzBuzz(-15));
        }
    }
}
```

なお、1つのテストメソッド内に、複数のAssertを記述することについては、賛否両論あります。
今回は簡単な例なので、複数記述しています。

# FizzBuzz メソッドを実装する

それでは、`MyProgram` プロジェクトに戻り、スタブにしておいた FizzBuzz メソッドを実装してみましょう。
タスク一覧の TODO コメントをダブルクリックして、ジャンプすることもできます。

※このコードは、ユニットテストのデモのため、わざと間違えています。

```csharp
/// <summary>
/// いわゆるFizzBuzzプログラムです。
/// 3の倍数のときは"Fizz"、5の倍数のときは"Buzz"、両方に該当するときは"FizzBuzz"を出力します。
/// どちらにも該当しないときは、与えられた数字を文字列として出力します。
/// </summary>
/// <param name="number">判定する数字</param>
/// <returns>FizzBuzzの出力を文字列で返します。</returns>
internal static string FizzBuzz(int number)
{
	if (number % 3 == 0)
	{
		return "Fizz";
	}
	else if (number % 5 == 0)
	{
		return "Buzz";
	}
	else if (number % 3 == 0 && number % 5 == 0)
	{
		return "FizzBuzz";
	}
	else
	{
		return number.ToString();
	}
}
```

コードを入力すると、エディタの左側に赤、緑、黒の丸印が表示されています。
これは、NCrunchのライブユニットテストによって付与されたマークです。

* 赤丸: テストに失敗している
* 緑丸: テストに合格している
* 黒丸: テストされていない

![FizzBuzz_Failed.png](https://qiita-image-store.s3.amazonaws.com/0/177927/f2a6978e-a82b-723e-86b5-bd3aad858f1b.png)


`return "Fizz";` の行の赤丸をクリックしてみましょう。
当該行に関連するテストが、ポップアップで一覧表示されます。赤いバツ印の付いているテストが、失敗しています。

* 3の倍数かつ5の倍数: テストに失敗している
* 自然数でない数: テストに失敗している
* 3の倍数: テストに合格している

![ShowTestsCoveringThisLine.png](https://qiita-image-store.s3.amazonaws.com/0/177927/17dd61cc-0434-b43c-d57a-689c6275e0e8.png)

また、`return "FizzBuzz";` の行は、黒丸がついています。
これは、この行に到達できるテストコードが存在しないことを意味しています。
「3の倍数かつ5の倍数のテスト」は用意しているのに、テストが走っていない。意図した動作ではないので、これもバグということになります。

# FizzBuzz メソッドを修正する

それでは、FizzBuzzメソッドを修正していきます。
修正作業中も、リアルタイムにテストが走り、エディタ左側の丸が更新されていきます。
最終的に、以下のコードになるように修正してみましょう。

```csharp
/// <summary>
/// いわゆるFizzBuzzプログラムです。
/// 3の倍数のときは"Fizz"、5の倍数のときは"Buzz"、両方に該当するときは"FizzBuzz"を出力します。
/// 負の数や、どちらにも該当しないときは、与えられた数字を文字列として出力します。
/// </summary>
/// <param name="number">判定する数字</param>
/// <returns>FizzBuzzの出力を文字列で返します。</returns>
internal static string FizzBuzz(int number)
{
	if (number < 3)
	{
		// 何もせず if を抜けます
	}
	else if (number % 3 == 0 && number % 5 == 0)
	{
		return "FizzBuzz";
	}
	else if (number % 3 == 0)
	{
		return "Fizz";
	}
	else if (number % 5 == 0)
	{
		return "Buzz";
	}
	return number.ToString();
}
```

修正が終わると、エディタの左側の丸が、すべて緑色になりました。これで、用意したテストに、すべて合格しました。

![FizzBuzz_Passed.png](https://qiita-image-store.s3.amazonaws.com/0/177927/d5dd4566-1bef-791e-0a44-5252aef0e7dd.png)


# Main メソッドの実装

無事にテストを通過し、`FizzBuzz` メソッドが完成しました。
次は、`Main` メソッドを実装し、コンソールアプリケーションとして動作するようにします。

なお、`Main` メソッドの単体テストは割愛します。

```csharp
/// <summary>
/// FizzBuzzコンソールアプリケーションです。
/// </summary>
/// <param name="args">コマンドライン引数（使用せず）</param>
static void Main(string[] args)
{
	while (true)
	{
		try
		{
			Console.Write("整数を入力: ");
			Console.WriteLine(FizzBuzz(Int32.Parse(Console.ReadLine())));
			break;
		}
		catch (OverflowException)
		{
			Console.WriteLine("エラー: 数値が大きすぎます。");
			continue;
		}
		catch (Exception)
		{
			Console.WriteLine("エラー: 整数ではありません。");
			continue;
		}
	}
}
```

終わったら、ビルドして、できあがった exe ファイルを動かしてみましょう。

![CmdExec.png](https://qiita-image-store.s3.amazonaws.com/0/177927/7c3e451b-73f7-c280-af94-60b99ec75979.png)