NUnitAsp の高度な使い方はじめに開発時にいかに既存のコードにバグが混じらないようにするかは、これまでずっと大きな課題とされてきた。しかし今日では、テスト駆動型開発(Test Driven Development:TDD)という新しい手法により、この状況は変化しつつある。TDDの主な原則は次の2つである。
この原則はおおむね理にかなっている。多くの自動テストをボタンクリックやコマンドラインから実行したいと望んでいる開発者は多い。それによってアプリケーションの正常な動作を保証できるからだ。自動単体テスト実行用のフレームワークを実現するNUnitというオープンソースツールもある。このツールは、データ構造を操作するだけのクラスライブラリでは非常によく使われているが、GUIで利用するのはかなり難しい(NUnitの詳細については「Test Driven Development Using NUnit in C#」を参照)。 GUIのaspxページのテスト用フレームワークを実現するNUnitAspというオープンソースツールもある。NUnitAspはNUnitを拡張したもので、WebFormsで使用される NUnitAspのWebサイトには、汎用的なチュートリアルが用意されているので、ここでは一から説明するのではなく、簡単なサンプルを紹介したのちに、NUnitAspを利用してより高度な問題を解決する方法について見ていく。コードサンプルは上記のリンクからダウンロード可能である。 簡単な例からでは、まずNUnitAspを使った「Hello, World!」的なテストを実行してみよう。正しいTDDでは、先にテストを記述し、そのテストに合格するようなコードを書くというのが基本だが、ここではNUnitAspの使い方をわかりやすく説明するために、先にアプリケーション部分を記述することにする。 まず、次の2つのプロジェクトを使った新しいソリューションを作成する。
このWebプロジェクトでは、 関連するメソッドは次の1つだけである。 private void Button1_Click(object sender, System.EventArgs e) { this.Label1.Text = this.TextBox1.Text; } ここでプロジェクトを実行し、ページがきちんと動作するかを確認してみよう。このページをVisual Studioにロードできない場合は、おそらくNUnitAsp内でも実行できない。この簡単な例では、テキストボックス、ボタン、ラベルが正しく連動していることを確認しておく。 UitGuiプロジェクトでは、 さらに
今度は実際にテストを記述してみよう。まずはテストのコードを見てほしい。詳しい内容は後から説明する。 [Test] public void SelectDropdown() { string strURL = TestConstants.AppPath + "Simple/Basic.aspx"; Browser.GetPage(strURL); //declare the controls: ButtonTester Button1 = new ButtonTester("Button1", CurrentWebForm); LabelTester Label1 = new LabelTester("Label1", CurrentWebForm); TextBoxTester TextBox1 = new TextBoxTester("TextBox1", CurrentWebForm); //run through actions: string strValue = "Hello"; TextBox1.Text = strValue; Button1.Click(); //check values: WebAssertion.AssertEquals(strValue, Label1.Text); } //end of method このテストコードは、NUnitの標準的なメソッド宣言から始まっている。このメソッドは 重要なのは、 最後に、ページの最終的な状態をテストする必要がある。つまり、ラベルにテキストボックスの値が割り当てられているかどうかを確認するわけだ。NUnitAspの では、NUnitコンソールを開き、「UitGui.dll」をロードし、テストを実行してみよう。テストの横に緑の丸が表示されるはずだ。これは、テストに合格したことを意味する。 ![]() これで最初のテストは完了である。NUnitAspを使えば簡単だが、NUnitAspを使わなければもっと手間がかかったことだろう。 「Hello, World!」的なサンプルも悪くはないが、実際に解決しなければいけない問題はもっとずっと複雑である。以降では、NUnitAspを使ってこうした問題に取り組むため5つのテクニックを紹介する。
共通機能を基底クラスに抽象化する WebAssertion.AssertEquals(Browser.CurrentUrl.ToString(),strURL); しかし、これはどんなテストでも必要な処理なので、共通の基底ページオブジェクトに抽象化しておいた方が便利である。 public class TestBase : WebFormTestCase { public TestBase() { } //end of con public void CheckPage(string strExpectedUrl) { WebAssertion.AssertEquals("Expected page does not match actual page. " + "Expected=[" + strExpectedUrl + "]. Actual=[" + Browser.CurrentUrl.ToString() + "].", Browser.CurrentUrl.ToString(),strExpectedUrl); } //end of method } //end of class この基底ページが [TestFixture] public class TestBasic : WebFormTestCase から [TestFixture] public class TestBasic : TestBase に修正する必要がある。 ラッパーページを使用する NUnitAspが直接アクセスできるのはWebFormだけである。ユーザーコントロールや、Session値などの項目を含んでいる public string Subject public void SetValues(string[] astrValues) public string GetSelectedValue() ここでは ボタンがクリックされると、テキストボックスの値がユーザーコントロールの ![]() これはテスト時には大いに役立つが、このようなページを本番用のコードに含めるわけにはいかない。この問題を解決するには、ラッパーのWebFormを #if DEBUG using ... namespace WebMain { public class TestUCDropDown : System.Web.UI.Page { ... } public string Subject { get {return this.LblSubject.Text;} set{this.LblSubject.Text = value;} } } #endif このテクニックを使用すると、ユーザーコントロールをテストするだけでなく、 それぞれのテストを確実にベースラインにリセットするテストの基本原則は、すべてのテストを同じベースラインから開始し、1つのテストが別のテストに影響を与えないようにすることである。Webアプリケーションの場合、これは、1つのテストで生成されたApplication値、Session値、キャッシュ値を別のテストに持ち込んではならないということを意味する。 状態をリセットするメソッドを呼び出すには、前述のテクニックで説明したとおり、ラッパーページを使用する。その他に、少々手間はかかるがより厳密な方法として、コマンドラインから public void IISReset() { string strFile = "IISRESET"; ProcessStartInfo psi = new ProcessStartInfo(); psi.WindowStyle = ProcessWindowStyle.Hidden; psi.FileName = strFile; Process p = System.Diagnostics.Process.Start(psi); p.WaitForExit(); } キャッシュ値とSession値をリセットする他に、現在のスレッドのCultureとUICultureもリセットしなければならないことがある。グローバライゼーションのために、現在のスレッドには、日付と通貨の書式を決定するCultureと、使用するリソースファイルを決定するUICultureという値がある。グローバルアプリケーションをテストする場合には、各テストの後に現在のスレッドを次のように設定して、これらの値を確実にリセットするべきである。 public void ResetCulture() { System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); } どのテストをリセットするかに応じて、これらのメソッドをテストから個別に呼び出すことも、クラスまたは基底クラスの 単純な統合テストと機能テストを作成する動的に追加されるコントロールや構成可能なメニューユーザーコントロールのテストのように、厳密な意味でのGUI単体テストも確かに存在する。しかし、N層アプリケーションではプレゼンテーション層とそれ以下のすべての層が統合されていることがよくあるので、GUIテストを統合テストや機能テストと関連付けることが簡単にできる。NUnitAspでは、プレゼンテーション層を制御することにより、単純な機能テストや統合テストを暗黙的に記述することもできる。機能テストの例としては、ビジネスプロセスのフロー全体にわたってユーザーのアクションを調整することが考えられる。統合テストの例としては、単純にページを正しくロードすることが考えられる(これにより、アプリケーションがすべての層を正しく通ってデータベースに到達し、再び戻ってきたことが確認できる)。 サンプルコードの ![]() このようなテストは、強力(かつ高価)なサードパーティ製ツールの代わりにはならないが、簡単にテストをしたいときには大いに役立つし、予算がない(または専門のテスト担当者がいない)開発チームでも手軽にテストを実施できるというメリットがある。 テストをVisual Studioデバッガ内で実行する Visual Studio .NETデバッガの長所は、デバッガ内でコードのステップ実行やテストができることである。しかしNUnitAspでは、Webアプリケーションへのアクセス方法の都合により、デバッガでステップ実行をすると ![]() まとめ本稿では、NUnitAspを使用してGUIコンポーネントの単体テストを実現する方法と、簡単なものであれば機能テストや統合テストも実現できるということを紹介した。NUnitAspには、その他にも次のような長所がある。
NUnitAspの最大の短所は、Webアプリケーション内の分離コードクラスしかテストできないことだろう。つまり、JavaScriptなどのクライアントサイドコードをテストすることはできない。また、NUnitAspでは、単にNetscapeやIEで表示できるHTMLページではなく、適切な形式のHTMLページが要求される。HTMLページの有効性は、http://validator.w3.org/のオンライン検証プログラムで確認できる。 NUnitAspは便利なツールであり、特にGUIの単体テストには威力を発揮する。しかし、これを使用したからといって、その他の部分のテストプロセスが不要になるわけではないし、GUIからしかテストできないようなコードを書いてもよいことにはならない。バックエンドのビジネス層やデータベース層をテストするには、NUnitのような、別のテストフレームワークを使用する必要がある(NUnitの詳細については、「Test Driven Development Using NUnit in C#」を参照)。NUnitAspは無料で使い方も簡単なので、.NETツールキットに加えておくと重宝するプログラムである。 参考資料
著者紹介Tim Stall(Tim Stall)
|