アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

NUnit を使ってみる

恥ずかしながらユニット テストをまともに使ったことがない。

重要性は理解しているつもりで興味もあったのだけど、仕事で触れず趣味の開発にも取り入れることはなかった。しかしプログラマが知るべき 97 のことのテストに関するトピックを読んで関心度がモリモリ上昇。なにかをはじめる時はこういう気分に乗るのがよい。

それと仕事でユニット テストの採用を提案するとして自分で使っていないものを勧められようはずもない。使うにしても簡単なスニペットを書いてそれをテストして...ではダメだ。ユニット テストを考慮していないものに取り入れるほうが現実的だろう。

というわけでまずは趣味のプロジェクトにユニット テストを導入してみる。プロジェクトは .NET 製なのでツールは NUnit を選ぶ。

レガシーコード改善ガイド

いきなりユニット テストを取り入れても、その背景にある理念を知らないとちょっとした問題でもつまづくだろう。そして「テストなんてはじめからなかった」などと諦めてしまいそうだ。だいいちテストをするといっても、どんな範囲でどのようにというのがよく分からない。何かリファレンス的なものが必要だぞということで多くの人が読んでいそうなレガシーコード改善ガイドを購入してみた。

内容は具体的かつ実践的でとても分かりやすい。表紙に「テストがないコードはレガシーコードだ!」なんて書いてあるけどテスト導入が難しいことを前提としており、大半の内容はそうした状況への対策となっている。

テストは対象の依存が少ないほど実行しやすくなる。よって対策は依存性の排除や抽象化へ向かう。例を見ると過剰に感じられるが、それも織り込み済み。全体的な複雑度の増加よりも個々の機能が独立することを目指すという。このあたりは著者も誤解の元と予想したのだろう、かなり丁寧に解説している。

実現方法としてはインターフェース、PImpl イディオムや Strategy パターンといった手法が多用される。これらの実践的なサンプルとしても有用だと思う。

難点としては「詳しくは~章を参照してください」という記述が挙げられる。紙の書籍でこれはつらい。PC であればタブやマルチ ウィンドウに期待できるけれど紙媒体ならば後述は禁物ではないか。あってもせめて数ページの範囲に留めるべきだ。その章で完結できないなら半端に触れないほうがいい。

あと表紙のデザインが残念。素っ気ないというか書店で面置にしても確実に埋もれるタイプ。地味すぎてあらかじめ評判を知っている人か手に取らないだろう。オライリーならどんな生物を持ってきただろうか?などと考えてしまった。

NUnit のインストール

テスティング フレームワークの世界では xUnit 系がメジャーのようだ。Smalltalk の SUnit を元に JUnit が生み出され様々な言語に移植されているとのこと。今回、利用する NUnit もそのひとつ。これは .NET 用のツールとなる。

ダウンロード ページからインストーラーを入手する。現時点の最新版は 2.5.9。複数のファイルが公開されているので拡張子が .msi のものをダウンロードする。入手したものは Windows インストーラ形式だからそのまま実行すればセットアップ開始。特に迷うことなくインストールできるだろう。

テスト プロジェクトの作成

NUnit を使用するにはテスト用プロジェクトが必要。このプロジェクトの概要は以下のようになる。

  • nunit.framework.dll を参照している
  • テスト用メソッドを公開 (アクセス指定子が public) している
  • プロジェクト形式は EXE と DLL のどちらでもよい

プロジェクトは専用のものを作成する。Conditional 属性を利用してデバッグ版だけテスト メソッドを有効にすればリリース用プロジェクトにテストを書くのもよさそう。しかし nunit.framework.dll 依存が気になるし internal までなら設定だけで外部プロジェクトから参照できる。リフレクションを使えば private も可能。

またテストだけで固まっていたほうが管理しやすい。プロジェクトの参照を見ればテスト対象が一望できるしテスト対象も集約される。NUnit の設定ファイルやテスト データの置き場所も専用プロジェクトにまとめるのがよいだろう。

以上を踏まえてテスト用プロジェクトを作成。Visual Studio でテスト対象を含むソリューションを開きプロジェクトを追加。名前を「テスト対象 + Tests」にして、出力の種類はクラス ライブラリを選ぶ。複数のプロジェクトに対するテストを集約するなら「ソリューション名 + Tests」にすると分かりやすい。

NUnit 自身のテスト用モジュールもこのようになっている。名前からテスト対象が分かったほうがよいし単独で実行することはないから DLL でよいということなのだろう。今回作成するものは Owl というライブラリに対するテストなので、OwlTests という名前にした。

プロジェクトを作成したら nunit.framework.dll への参照を追加する。NUnit をインストールしているなら .NET アセンブリがシステムに登録されているはず。

次にテストしたいプロジェクトの参照を追加。同じソリューション内ならプロジェクトをそのまま参照すればよい。

テスト プロジェクトから対象の internal 部分にアクセスしたい場合は特別な設定が必要。テスト対象を Owl、テスト プロジェクトが OwlTests なら Owl の Assemblyinfo.cs へ以下の記述を加える。

[assembly: InternalsVisibleTo("OwlTests")]

これで Owl の internal 部分が OwlTests に公開される。いわゆるフレンド アセンブリである。この状態でビルドすると生成された DLL が NUnit から利用できる形式になる。

テスト コード

テスト用クラスを作成する。名前空間は「テスト プロジェクト名.テスト対象クラスの名前空間」にしておくと分類しやすい。クラス名は「テスト対象のクラス名 + Test」としておく。

NUnit とテスト対象を using で参照し、テスト クラスであることを示すために TestFixture 属性を付ける。クラスとテスト メソッドのアクセス指定子は NUnit に公開する必要があるため public にする。例えば Owl.Id3 にある MpegAudioFrameHeader というクラスをテストするなら以下のような実装になるだろう。

using System;
using NUnit.Framework;
using Owl.Id3;

namespace OwlTests.Id3
{
    /// <summary>
    /// MpegAudioFrameHeader のテストを実行します。
    /// </summary>
    [TestFixture]
    public class MpegAudioFrameHeaderTest
    {
    }
}

NUnit は多機能でテスト コードの表現力も非常に高い。けれどはじめは TestTestCase 属性と Assert クラスのメソッドをいくつか知っておけばよいだろう。Test または TestCase 属性を付けたメソッドがテスト対象になる。メソッドを単体実行するなら Test、ひとつのメソッドで複数条件をテストしたいなら TestCase を利用する。

テスト メソッド内では Assert クラスのメソッドを実行する。メソッドが実行されたときに正常ならテスト成功、Assert が起きるなら失敗となる。例えば Assert.AreEqual() なら期待値と値が異なることをチェックし Assert.Throws()Assert.That() を組み合わせると例外の発生をテストできる。

NUnit の入門記事だと Test 属性 + Assert.AreEqual() を説明することが多いようだが TestCase 属性がそれを兼ねられるので利用機会は少ない。よってはじめは TestCase 属性を使ってみる。実際に書いてみると以下のようになる。

/// <summary>
/// バージョン情報の読み込みをテストします。
/// </summary>
/// <param name="version">MPEG バージョンとレイヤー。</param>
/// <param name="target">バージョン情報の期待値。</param>
[TestCase( 0xFB, "1",   TestName = "MPEG 1"   )]
[TestCase( 0xF3, "2",   TestName = "MPEG 2"   )]
[TestCase( 0xE3, "2.5", TestName = "MPEG 2.5" )]
public void ReadMpegVersion( byte version, string target )
{
    var expected = MpegVersion.None;
    switch( target )
    {
    case "1":   expected = MpegVersion.Mpeg1;   break;
    case "2":   expected = MpegVersion.Mpeg2;   break;
    case "2.5": expected = MpegVersion.Mpeg2_5; break;
    }

    var h = new MpegAudioFrameHeader( new byte[ 4 ] { 0xFF, version, 0x00, 0x00 } );
    Assert.AreEqual( expected, h.Version, "バージョン" );
}

TestCase 属性のパラメーターは可変長で対象となるメソッドの引数に対応している。また、属性は複数同時に定義できる。この仕組みを利用することで単一メソッドによる複数条件テストを書ける。条件ごとに名前を付けたいなら TestName パラメーターを指定。ここに書いた名前は NUnit 上にそのまま表示される。テスト用パラメータと区別するために記述位置は末尾にするとよい。

この例では引数をわざわざ文字列にしてメソッド内で MpegVersion 列挙体に変換しているのだが、これには理由がある。MpegVersion 列挙体は Owl.dll で internal として定義されているため OwlTests.dll の public メソッドの引数に指定できない。internal もテスト可能といってもアクセス指定は通常どおり。これは案外はまりそうなので、あえて例に挙げてみた。

Assert.AreEqual() の引数は

  1. 期待値
  2. 評価する値
  3. Assert 発生時のメッセージ**

となる。第一、第二引数の内容が一致するならテスト成功、それ以外は Assert として扱われる。

次は例外に対するテスト。

/// <summary>
/// 同期ヘッダが見つからなかった時の振る舞いをテストします。
/// </summary>
[Test]
public void FailSyncHeader()
{
    var ex = Assert.Throws< ArgumentException >(
       () =>
       {
           var header = new MpegAudioFrameHeader( new byte[ 4 ] { 0x00, 0x00, 0x00, 0x00 } );
       } );

    Assert.That( ex.Message == "'Sync header' has no information.", "同期ヘッダ" );
}

Assert.Throws の型パラメーターに例外クラスを指定することでメソッド内に発生した例外を補足してくれる。その内容が戻り値となるためそれを Assert.That() で評価することで例外チェック可能。

try - catch 方式でテストする場合は try で例外の有無、catch で例外の内容を評価する必要があるけれど、この方法なら評価部分が Assert.That() だけで済む。指定された例外が発生しなかったなら Assert.Throws() の戻り値は null になるのでテストも失敗してくれる。

NUnit でテストを実行してみる

NUnit を起動して以下の手順を実行する。

  • NUnit のメイン メニューから「File」→「New Project...」を選ぶ
  • ファイル保存ダイアログが表示される
  • テスト プロジェクトのフォルダを開く
  • テストプロジェクト名.nunit という名前でファイルを保存し、ダイアログを閉じる
  • NUnit のメイン メニューから「Project」→「Add Assembly...」を選ぶ
  • ファイル選択ダイアログが表示される
  • テスト プロジェクトの bin\Debug または Release を開く
  • テスト プロジェクトの DLL を選んでダイアログを閉じる

すると NUnit の左にあるツリーにテスト メソッドが表示される。

NUnit

これらのどれかを選んだ状態で NUnit の右にある Run ボタンを押すとテストが実行される。テストに成功したら緑、失敗なら赤が表示される。

テストを実行

赤になった時は原因も表示されるため解決の参考になる。Assert.AreEqual() によるチェックなら期待値と実際の値、Assert に到達できず例外が発生した場合はその内容が出力される。

Visual NUnit

NUnit は対象プロジェクトがビルドされると自動的にモジュールを読み直してくれる。しかしたまに失敗する。その場合は Socket が云々というエラーが表示され再起動するまで操作を受け付けなくなったりする。

Visual Studio で開発している場合、テスト実行のために NUnit へ移るのは面倒である。Visual Studio Professional の標準テスト機能なら内部実行できるので NUnit でも同じことができればなぁと思って調べたところ Visual Nunit というツールを見つけた。これは前に紹介した AnkhSVN と同じく拡張機能となるので、インストールすると Visual Studio に統合される。セットアップの手順は以下。

まず Visual Studio のメインメニューから「ツール」→「拡張機能マネージャー」→「オンラインギャラリー」を選ぶ。次に右上の検索欄に NUnit と入力して Visual NUnit を探す。

拡張機能マネージャー

Visual NUnit が見つかったら「ダウンロード」ボタンを押す。すると以下の確認メッセージが表示されるので「インストール」ボタンを押す。

インストール確認

インストールが完了すると拡張機能マネージャーの下部に「変更を有効にするには Microsoft Visual Studio を再起動する必要があります。」と表示されるので「今すぐ再起動」ボタンを押す。自分で再起動してもよい。

再起動メッセージ

再起動した後に NUnit を利用しているソリューションを開きメインメニューから「表示」→「その他のウィンドウ」→「Visual NUnit」を選ぶ。

Visual NUnit の表示

すると Visual NUnit のウィンドウが表示される。自動的にテスト コードが読み取られ以下のようにリスト アップされる。

Visual NUnit

ウィンドウ内の「→」と書かれたボタンを押すとテストを実行してくれる。この記事を書いている最中に更新があり、最新版だと「→」ボタンは「Run」という表記になったようだ。

Visual NUnit でテスト実行

個別にテストを実行する場合はリストから単体を選択する。複数なら CtrlShift キーを押しながら項目を選ぶ。Ctrl + A で全選択になる。

このツールを使うとテストから実行までを Visual Studio で完結できて非常に便利。惜しむらくは GUI がリストなので NUnit のように階層選択できないことか。