MVC ビューを単体テスト

ちょっと勘違いしてたかも。とりあえず全部削除。



今回もフレームワークじゃなくて、パターンの方の話。
# フレームワークなら、ビューも簡単に単体テストできるのかな?

例えばこの記事に掲載した後者のコードに含まれる Lend というページクラス。ILendView の実装部分に関しては、サーバーコントロールのプロパティ設定程度しかやっていないわけで、一見するとこれは単体テストが簡単にできそうな気がする。


しかし、ページクラスのテストには大きな問題が 2 つある。


1. ASPX のコンパイル
普段僕たちがプログラミングしているページクラスはコードビハインドによって ASPX から分離されたクラスであり、コントロールツリーの生成処理は記述されていない。これは、ページクラスを継承し、ASPX ファイルに記述されているコントロールツリーを適切に生成するためのクラスが別に用意されるためだ。通常、ASPX のコンパイルは実行時に行われる。


2. HttpContext の用意
通常、ページの初期化を行うには HttpContext が必要となるのだが、これを自分でこしらえるのは容易ではないようだ。


問題 1 については、プリコンパイルの設定でなんとかできた。これには Web Deployment Project (リンク先は VS 2008 用なので注意) が必要。
問題 2 については、HttpContext を使わないでコントロールツリーを生成させることに成功した。具体的には、コンパイルされた ASPX に自動で定義される「__BuildControlTreeMethod」というプライベートメソッドをリフレクションで呼び出すことで、コントロールツリーの生成処理のみを行うことができる。


以下、僕がやった手順。
[ 1 ] ASP.NET Web アプリケーションプロジェクトの用意

    1. まずASP.NET Web アプリケーションのプロジェクト (Web サイトではない) を作成。
    2. デザイナで Default.aspx にテキストボックスを一つ配置 (ID は既定のまま)。
    3. コードビハインド側で次のような Hoge というメソッドを用意。
public void Hoge()
{
    this.TextBox1.Text = "Hoge";
}

[ 2 ] Web 配置プロジェクトの用意

    1. [ 1 ] で用意したWeb アプリケーションプロジェクトの Web 配置プロジェクトを作成。
    2. Web 配置プロジェクトのプロパティを開く。
    3. Debug 構成とRelease 構成の両方で、出力フォルダを ".\Output\" に変更*1し、「このプリコンパイル済みサイトを更新可能にする」のチェックを外す。
    4. ビルドする。

[ 3 ] 単体テストの用意

    1. 単体テスト用にクラスライブラリプロジェクトを作成。
    2. 「参照の追加」の「.NET」タブから NUnit.Framework.dll を選択して追加。
    3. 「参照の追加」の「.NET」タブから System.Web.dll を選択して追加。
    4. 「参照の追加」の「プロジェクト」タブから [ 1 ] で用意した ASP.NET プロジェクトを選択して追加。
    5. 「参照の追加」の「参照」タブにて Web 配置プロジェクト内の Output\bin フォルダを開き、名前がWeb 配置プロジェクト名の DLL を選択して追加。
    6. 次のようなテストメソッドを用意。
[Test]
public void Hoge()
{
    ASP.default_aspx page = new ASP.default_aspx();
    MethodInfo __BuildControlTreeMethod = typeof(ASP.default_aspx).GetMethod("__BuildControlTree", BindingFlags.Instance | BindingFlags.NonPublic);
    __BuildControlTreeMethod.Invoke(page, new object[] { page });

    TextBox tb = (TextBox)page.FindControl("TextBox1");

    Assert.That(tb.Text, Is.Empty, "Hoge する前はテキストボックスが未入力であるか");
    page.Hoge();
    Assert.That(tb.Text, Is.EqualTo("hoge"), "Hoge した後はテキストボックスが Hoge であるか");
}


これで、サーバーコントロールのプロパティ設定程度の処理は単体テストできるはず。


[ おまけ ]

public static TPage CreatePage<TPage>()
    where TPage : Page, new()
{
    TPage page = new TPage();

    MethodInfo __BuildControlTreeMethod = typeof(TPage).GetMethod("__BuildControlTree", BindingFlags.Instance | BindingFlags.NonPublic);
    __BuildControlTreeMethod.Invoke(page, new object[] { page });

    return page;
}

こんなメソッドを用意しておけば、ページの準備を

ASP.default_aspx page = CreatePage<ASP.default_aspx>();

というふうに、簡単に記述できる。

*1:単体テストプロジェクトから DLL を直接参照するので、出力先を一本化する。揃ってさえいれば別の名前でも構わない。