プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

Visual Studio 2017 のライブ ユニット テスト機能による「車窓からの TDD」

Visual Studio Advent Calendar 2016 の12月16日の記事。

logo

コーディング機能やデバッグやテストの機能が大幅パワーアップ! 次期バージョン「Visual Studio 2017 RC」 | CodeZine という記事を書かせていただき、その中で、「Visual Studio 2017 RC」の「特におすすめの新機能」としてライブ ユニット テストに触れた。 もう少し具体的にライブ ユニット テストを紹介してみたい。

題材として、『ソフトウェア技術者サミット in 福井 2016』 (7月16日) の「テスト駆動開発ライブ 〜C#ペアプログラミング実況中継」というセッションの中で、平鍋健児氏と C#Visual Studio 2015 を使っておこなったテスト駆動開発 (Test-Diven Development: TDD) のデモンストレーションを用いることにする。

「テスト駆動開発ライブ 〜C#ペアプログラミング実況中継」 | 『ソフトウェア技術者サミット in 福井 2016』
テスト駆動開発ライブ 〜C#ペアプログラミング実況中継」 | 『ソフトウェア技術者サミット in 福井 2016』

このデモンストレーションは、北野弘治氏と平鍋健児氏による「車窓からの TDD | オブジェクト倶楽部」 を C# で再現したものだ。 テストを書きながら、スタックのクラスを作成していく。 順を追ってやってみよう。

プロジェクトの作成

最初に、Visual Studio 2017 RC を起動し、テストするプロジェクトとテストされるプロジェクトを作る。

  • テストするプロジェクトの作成
    • メニューから「ファイル > 新規作成 > プロジェクト > Visual C# > テスト > 単体テスト プロジェクト Visual C#
単体テスト プロジェクト (C#) の作成
テストするプロジェクト – 単体テスト プロジェクト (C#) の作成
  • テストされるプロジェクトの作成
    • ソリューション エクスプローラでソリューション名を右クリックし、「追加 > 新しいプロジェクト > Visual C# > クラス ライブラリ (.NET Framework) Visual C#
テストされるプロジェクト – クラス ライブラリ (C#) の作成

ライブ ユニット テストの開始

では、早速 Visual Studio 2017 の新機能であるライブ ユニット テストを開始しよう。 次のようにメニューから選ぶだけだ。

  • メニュー > テスト > Live Unit Testing > Start
ライブ ユニット テストの開始
ライブ ユニット テストの開始

最初のテスト

それでは、単体テスト プロジェクトに最初のテストを追加してみよう。 TDD では、テストされるコードよりテスト コードを先に書くことが多い。

最初のテスト: 「作ったら空」

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Shos.Collections.Test
{
    [TestClass]
    public class Stackのテスト
    {
        [TestMethod]
        public void 作ったら空()
        {
            var stack = new Stack<int>();
            Assert.IsTrue(stack.IsEmpty);
        }
    }
}

クラス名を「Stackのテスト」とし、「作ったら空」というテストを追加した。 まだ Stack クラスは作っていないので、静的なエラー (コンパイル時エラー) になり、エラー箇所に赤い波線が表示される。

最初のテストを作った直後
最初のテストを作った直後

バルブ アイコンをクリックするか、エラー箇所にカーソルがある状態で 'Ctrl+.' をタイプし、クラス ライブラリ プロジェクトの方に Stack クラスを作成しよう。

Stack クラスの作成: 新しい型の生成
Stack クラスの作成: 新しい型の生成
Stack クラスの作成: クラス ライブラリ プロジェクトの方に Stack クラスを生成
Stack クラスの作成: クラス ライブラリ プロジェクトの方に Stack クラスを生成

OK ボタンを押すと、クラス ライブラリ プロジェクトに Stack クラスが生成される。

生成された Stack クラス

namespace Shos.Collections
{
    public class Stack<T>
    {
        public Stack()
        {
        }
    }
}

テストの方では、まだ "stack.IsEmpty" の部分が静的エラーになっている。

バブル アイコンまたは 'Ctrl+.' から「プロパティ 'Stack.IsEmpty' を生成します」を選ぼう。

プロパティ 'IsEmpty' の生成
プロパティ 'Stack.IsEmpty' の生成

Stack クラスに、プロパティ "IsEmpty" が生成される。 このように、Visual Studio の助けを借りることで、スムーズに TDD を行うことができる。

ライブ ユニット テスト

"IsEmpty" が作られたことで、で静的エラーが消えた。

すると、ここでライブ ユニット テスト機能が自動で働く。 テストする側のクラス「Stackのテスト」の左に、テストが失敗したことを示す赤い×印が表示される。

ライブ ユニット テストの結果: テストする側のクラス
ライブ ユニット テストの結果: テストする側のクラス

そして、テストされる側のクラス Stack の左にも、テストが失敗したことを示す赤い×印があらわれる。

ライブ ユニット テストの結果: テストされる側のクラス
ライブ ユニット テストの結果: テストされる側のクラス

では、テストを成功させるために、Stack クラスを修正しよう。 テスト コード中の "IsEmpty" にカーソルがある状態で 'F12' を押す (または、"IsEmpty" を右クリックして「定義に移動」)。

ここでは、"IsEmpty" の実装を "public bool IsEmpty => true;" に変更してみる。

そうすると、ライブ ユニット テストによって自動でテストが通り、Stack クラスと「Stackのテスト」クラスに表示されていた赤い×印緑のチェックに変わる。

ライブ ユニット テストの結果 - テスト成功 - Stack クラス
ライブ ユニット テストの結果 – テスト成功 – Stack クラス
ライブ ユニット テストの結果 - テスト成功 「Stackのテスト」クラス
ライブ ユニット テストの結果 – テスト成功 「Stackのテスト」クラス

このように TDD では、次のようなステップで踏み、それを繰り返しながら、プログラミングを進めていく。

  1. テストを書く
  2. (コンパイラーによる静的エラー – 静的なテスト)
  3. テストされる側のコードを書き、テストを失敗させる (ライブ ユニット テストによる動的エラー – 動的なテスト – レッド)
  4. テストを成功させる分だけテストされる側のコードを修正し、テストを成功させる (グリーン)
  5. 1 に戻る

テストされる側にコードを追加する場合は、テストを追加し一度テストを失敗させる。 そして、テストが通る分だけのコードをテストされる側に追加する。

テストによってコードのその時点でのあるべき姿を記述する。 つまり、メソッドなりクラスなりの外からみた仕様をテストで先に記述するわけだ。 このテストが通ったことをもって、コードがあるべき姿になったことにする。 これを繰り返すことで、あるべき姿を少しずつインクリメンタルに作っていくのだ。 メソッドやクラスが、その時点でのあるべき姿になったかどうかが、テストによってフィードバックされる。

そして、Visual Studio 2017 のライブ ユニット テスト機能を使えば、このフィードバックがリアルタイムに行われることになるのだ。 仕様を満たしていないコードがレッドで、仕様を満たしたコードがグリーンで、リアルタイムで可視化される。 とても効率の良いコーディング環境だ。

テスト「Pushしたら空じゃなくなる」の追加

さて、テストを追加してみよう。

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Shos.Collections.Test
{
    [TestClass]
    public class Stackのテスト
    {
        Stack<int> stack;

        [TestInitialize]
        public void 準備() => stack = new Stack<int>();

        [TestMethod]
        public void 作ったら空()
        {
            Assert.IsTrue(stack.IsEmpty);
        }

        [TestMethod]
        public void Pushしたら空じゃなくなる()
        {
            stack.Push(1);
            Assert.IsFalse(stack.IsEmpty);
        }
    }
}

"stack = new Stack<int>()" のコードが重複するのが嫌なので、"[TestInitialize] public void 準備() => stack = new Stack<int>();" というコードを追加した。 このメソッドは、個々のテストの実行前に一回ずつ呼ばれる。 この部分は、まだテストされていないが、このようなコードの左には、青い横棒が表示される。 テストしていないコードが可視化されるわけだ。 これにより、常にカバレッジが確認できる。

テストされていないコードの左には青い横棒
テストされていないコードの左には青い横棒

テストを追加した直後は、"stack.Push(1)" の "Push" が静的エラーになる。 Stack.Push メソッドがまだないためだ。 バブル アイコンまたは 'Ctrl+.' から「メソッド 'Stack.Push' を生成します」を選ぼう。 また動的テストが自動で走り、ちゃんと失敗する。

テスト「Pushしたら空じゃなくなる」の失敗
テスト「Pushしたら空じゃなくなる」の失敗

テストが通るように Stack クラスの Push メソッドを修正しよう。 テスト コード中の "Push" にカーソルがある状態で 'F12' を押す (または、"Push" を右クリックして「定義に移動」)。 "public void Push(int v) { throw new NotImplementedException(); }" の左には赤い×印が表示されているはずだ。

Stack クラスを、例えば次のように変更すると、テストが通って、緑のチェックに変わる。

Stack クラスを修正するとテストが通った
Stack クラスを修正するとテストが通った

TDD のリズム

ではまた、テストを追加しよう。 静的エラーになる (赤い波線)。

テスト「PushしてTopをみたらPushしたやつ」の追加
テスト「PushしてTopをみたらPushしたやつ」の追加

'Ctrl+.' から「プロパティ 'Stack.Top' を生成します」を選択。 また動的テストが自動で走り、ちゃんと失敗 (赤い×印)。

テスト「PushしてTopをみたらPushしたやつ」がちゃんと失敗
テスト「PushしてTopをみたらPushしたやつ」がちゃんと失敗

'F12' を押して、Stack クラスを修正。 また動的テストが自動で走って成功 (緑のチェック)。

Stack クラスを修正するとテストが成功 - Stack クラス
Stack クラスを修正するとテストが成功 – Stack クラス
Stack クラスを修正するとテストが成功 - 「Stackのテスト」クラス
Stack クラスを修正するとテストが成功 – 「Stackのテスト」クラス

今度は、テスト「PushせずにTopをみたら例外」の追加。失敗。

テスト「PushせずにTopをみたら例外」を追加すると失敗
テスト「PushせずにTopをみたら例外」を追加すると失敗

Stack クラスを修正するとテストが成功。

Stack クラスを修正するとテストが成功 - Stack クラス
Stack クラスを修正するとテストが成功 – Stack クラス
Stack クラスを修正するとテストが成功 - 「Stackのテスト」クラス
Stack クラスを修正するとテストが成功 – 「Stackのテスト」クラス

TDD ではレッドグリーンの繰り返しのリズムが大切だと思う。 テストのための無駄な作業を省いてくれるライブ ユニット テスト機能が、このリズムを心地よいものにし、プログラミングをより楽しくしてくれることだろう。

この例では、なるべく小さなステップで進めるようにしているので、テストが通る最小限の修正にとどめているが、コードが明らかであるような場合は、もっと大きな修正をしても構わないだろう。

リファクタリングについて

今回は出てきていないが、TDD では、テストが通った後にリファクタリング (外から見た仕様を変えずに内部構造を改善すること) を行うことが多い。Visual Studio 2017 のライブ ユニット テスト機能を使うと、このリファクタリングの際も常に自動で静的テストと動的テストによって、仕様どおりであり続けていることがチェックされることになる。 リファクタリング中に誤ってバグを作ってしまってもすぐに気付けることだろう。

リファクタリングでは、なるべくエラーが起きている時間を短くすることが肝要だが、こうしたエラーの可視化によるフィードバックが常に起こることは、エラー時間の最小化につながるだろう。

また、リファクタリング中の継続的なフィードバックによって、なるべくレッドにならないような手順でリファクタリングをするような良い習慣が生まれるのではないだろうか。

もちろん、Visual Studio 2017 で追加された多くの新たなリファクタリング機能も安全なリファクタリングに役立ってくれることだろう。

まとめ

Visual Studio 2017 RC の新たな機能であるライブ ユニット テストは、機能を使うことに手間を取られない。 機能を使うことに振り回されて、コーディングのリズムを狂わされることがない。 つまり、Visual Studio 2017 RC を使うと TDD が楽しく快適になる。