Visual Studioで単体テスト

初めまして、SuperLightBrothersの弟役であり、サーバーサイド担当のDANです。

今日は昨日のわんくま忘年会でのbiac先生のMSF Agileの講義にいたく感激し、改めてTDDの重要性にも気づかされたので、今回のSuperLightBrothersの開発もがっつりテストしていこうと言う事で単体テストについて少し書きます。

単体テストとは

多くの現場では「単体テスト」って言うと、エクセルで書かれた(画面かDB直接見るぐらいでしか確認できない)テスト仕様書を、なぜかその機能を作った本人が書き、しかも本人がテストするという意味不明なものが多いのではないでしょうか?
しかも、大抵一回テスト仕様書書いたらコードを修正してもテスト仕様書は直さないってのがザラで、反復テストなんて思考は一切無い。。まあ、コード直す度にそのエクセルの項目通りに画面操作して丸付けてくなんて願い下げですけど。

もちろん、今の現場もそれに漏れず、一度たりともテスト仕様書を直せという指令は下りてきません。
たぶん、上層部的にも顧客に渡すだけの紙ぐらいにしか思ってないんでしょうねorz

ちなみに昨日のわんくま勉強会でとあるベンダーの品質管理部の方がいらしたので、そんな事を話したら、「ありえねぇw」と爆笑されちまいました。けっこう普通の光景だと思うんだけどな。

コードでの単体テスト

コードでの単体テストではJavaではJUnitが有名ですよね(最近はTestNGとかもあるらしいけど)。自分も一年前まではJavaで仕事してたのでとってもお世話になってました。始めはおっくうなんですけど、書き始めるとそれ無しには不安でリリースなんてしたくなかった。。

C#での単体テスト

C#にもxUnitファミリーのNUnitがあります。でも今回はせっかくなのでVisual Studio 2008 Professionalに付属の単体テスト機能を使ってやってみることにします。やっぱり連携強い方がサクサク作れますしね。

いきなり実践

では、さっそく超簡単なテストのサンプルを書いてみます。
今回はLibraryという名前で新規クラスライブラリプロジェクトを作ります。

テスト対象のベースとなるクラスを作る

Book.csという本を表すクラスを作ります。まずはクラスだけ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Library {
    public class Book {
    }
}

まだメソッドも何もありません。

テストクラスを作る

以下の手順で驚くほど簡単に作成できました。

  1. さきほど作成したBookクラスのどこかにカーソルを持っていき、右クリック。
  2. 単体テストの作成」という項目があるので、それをクリック。
  3. 単体テストの作成ダイアログが出てくるのでOKします。
  4. テスト用のプロジェクト名を聞かれるので「LibraryTest」としました。

あっという間にBookTest.csを自動生成してくれました。これはすごいですね!
ちなみに以下のソースが自動生成ソース。ちょっと余計なものが多いですね。まあテンプレートなので仕方ないですが。

using Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LibraryTest
{
    
    
    /// <summary>
    ///BookTest のテスト クラスです。すべての
    ///BookTest 単体テストをここに含めます
    ///</summary>
    [TestClass()]
    public class BookTest {


        private TestContext testContextInstance;

        /// <summary>
        ///現在のテストの実行についての情報および機能を
        ///提供するテスト コンテキストを取得または設定します。
        ///</summary>
        public TestContext TestContext {
            get {
                return testContextInstance;
            }
            set {
                testContextInstance = value;
            }
        }

        #region 追加のテスト属性
        // 
        //テストを作成するときに、次の追加属性を使用することができます:
        //
        //クラスの最初のテストを実行する前にコードを実行するには、ClassInitialize を使用
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //クラスのすべてのテストを実行した後にコードを実行するには、ClassCleanup を使用
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //各テストを実行する前にコードを実行するには、TestInitialize を使用
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //各テストを実行した後にコードを実行するには、TestCleanup を使用
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion


        /// <summary>
        ///Book コンストラクタ のテスト
        ///</summary>
        [TestMethod()]
        public void BookConstructorTest() {
            Book target = new Book();
            Assert.Inconclusive("TODO: ターゲットを確認するためのコードを実装してください");
        }
    }
}
さっそく実行

メニューの「テスト⇒実行⇒現在のコンテキスト」で実行してみました。
結果…、

Assert.Inconclusive に失敗しました。TODO: ターゲットを確認するためのコードを実装してください

まあ、そうですよね。クラスもテストも書いてないですもん。

テストクラスに被テストクラスのメソッドを書く

では、TDDっぽくテストクラスから書いていきましょう。
まずはBookから名前を取得するメソッドNameのテストを書きます。

        [TestMethod()]
        public void GetNameTest() {
            Book target = new Book();
            string targetName = target.GetName();
            Assert.IsNull(targetName);
        }

もちろんIntelliSenseとか出ないのでプロパティとかも完全手書きです。この時点でウハってなっちゃいけません、この手書きが次にすごい事を起こしてくれちゃうんです。
ちなみに、本の名前を設定してないから、期待値はNullです。

メソッドスタブを作る

このメソッドスタブって、たまにインターフェースでコード組み立ててると出てくるんで「何なんだろ?」って思ってたんですけど、実は凄い奴でした。
ではスタブを以下の手順で作ります。

  1. コード上のGetName()の部分にカーソルを持って行く。
  2. 「G」の文字の下部に青く枠が出ておりカーソルを近づけるとオプションが出てくるので、それをクリック。または1.の状態で「Shift + Alt + F10」
  3. メソッドスタブを生成するかのバーが現れるのでクリック。

さて、Book.csを見てみましょう。おお、メソッドが出来てます!
ちなみにこんな感じ

    public class Book {

        public string GetName() {
            throw new NotImplementedException();
        }
    }
この時点でテスト実行

メソッドが作られたので、再度テストを実行してみます。
結果…、「失敗しました」。もちろんデカデカと「NotImplementedException」と書いてあるんだから当たり前なんですが、ちょい嬉しいですね。てゆうかサクサク作れ過ぎ!

Bookクラスを実装

ではテストが通るようにBookクラスを実装しましょう。

    public class Book {

        private string name;
        public string GetName() {
            return this.name;
        }
    }

さて、これで改めてテストを実行してみます。
結果…、おお成功しました!てゆうか当たり前過ぎるんですけどね^^;

本日のまとめ

今回はテスト駆動の本当に本当の触りをやってみました。でもJUnitでやってた頃より自動化してくれる部分が多くて結構感動です。伊達にVisual Studioも有償ソフトじゃあないですね。

今後のテストに関する調査TODO
  1. Mockオブジェクトは対応してるのか
  2. テストスィートは作れるのか
  3. MSBuildでの自動化はどうするのか
  4. カバレッジは測定できるのか
  5. DB、通信(WCFとか)、Presentation(WinForm, WPF, Silverlight, ASP.NET)部分のテストはどうするのか

Win32も操れるとなるとテストも調べることは多そうですね。。

テストのやり方について

biac先生が嘆いておられたのは「若い人はテストをどうかけばいいのかがわからないらしい」という事でした。
確かに自分もJUnitで書いてた頃はどういう方針でテストを書けばいいのかよくわからなかったです。仮にカバレッジが100%になったからって、例外テストとか、特殊なケースが抜けてれば意味がなくてパニック寸前になったような。。
その辺は先輩達にレビューしてもらったり、「ホワイトボックステストブラックボックステスト」について教え込まれたりして、ちょっとはマシになったつもりですが、、たぶんまだまだでしょうね;;