次の方法で共有


UI の簡単テスト

UI コードを分離してビジネス レイヤーに侵入させない

Mark Seemann

この記事で取り上げる話題:

  • User Interface Process Application Block バージョン 2
  • モデル ビュー コントローラのパターン
  • 単体テスト ユーザー インターフェイスのプロセス
  • ユーザー インターフェイス プロセス イベントの使用とテスト
この記事で使用する技術:
C#, NUnit

サンプルコードのダウンロード:
UIPApplicationBlock.exe (英語)
(260KB)

目次

シック ユーザー インターフェイス
モデル ビュー コントローラの実装
太陽系の周遊旅行
コントローラの作成
単体テスト プロジェクトの準備
コントローラの単体テスト
UI フロー内部のテスト
イベントの生成とテスト
シン ユーザー インターフェイス
結論

補足記事

スタブ ビューとビュー マネージャ

 

 

 

 

 

 

 

 

 


いまや、多くのアプリケーションが、多種多様な画面を使用する複雑なユーザー インターフェイス要件や、対話を規制する複雑なロジックをかかえ込んでいます。アプリケーションを複数のレイヤーに分けて設計する必要があることは、たいていの開発者には周知のことですが、私の経験から言うと、ユーザー インターフェイスをアプリケーションの他の部分から分離するには、実際には思った以上の困難が伴います。

Microsoft Patterns & Practices チームによる User Interface Process Application Block (UIP) は、UI を分離するのに役立ちますが、それで終わりなのでしょうか。UIP を利用すれば、すべて (または大半) の UI ロジック用の単体テストを作成することができ、その UI は最終的には、本来そうであるべき、アプリケーションの最上部のシン レイヤーになります。

簡単な例をあげて、UIP を使ってマルチ スクリーン アプリケーションを作成する方法と、ユーザー インターフェイスのロジック用の単体テストを作成する方法を示します。この例は、まず Windows フォーム アプリケーションで始まりますが、実際の UI にはほとんどコードが入っていないので、その後 2 番目の ASP.NET ユーザー インターフェイスを作成するのはきわめて簡単になります。事がどのように運ぶかも簡単に説明します。

シック ユーザー インターフェイス

理想的には、ユーザー インターフェイスは、アプリケーション内の次のレイヤーの最上部に位置するシン シェルでしかないはずです。 UI は、実際には 2 つの働きをするだけで用が足りるはずです。つまり、効果的な方法で情報を表示し、そしてできる限り迅速に入力を、その扱いを心得た所定のコードに引き渡すだけです。しかし現実には、ユーザー インターフェイスのコードには通常、多数のロジックが入り交じっています。それは、次にどこへ行くか、ユーザーの選択内容を次の画面にどうやって伝えるか、どこでデータを得るか、などを指示するためのものです。そのような傾向が特に顕著なのは、2 つの Web ページ間でデータを渡すための明確なオブジェクト指向の手段をもたない Web アプリケーションにおいてです。すなわち、ページ変数をフォーム フィールドやクエリ ストリングに変換する多数のコードや、そのようなフィールドやクエリ ストリングを読み込んで検査してから入力に変換するコードなどが、Web アプリケーションに組み入れられることがよくあります。典型的には、次のような大まかなコードの使い方を目にします。

string searchPhrase = Request.QueryString["SearchPhrase"];
searchPhrase = Server.UrlDecode(searchPhrase);
int page = 1;
try { page =     int.Parse(Request.QueryString["SearchPage"]); }
catch{}
// 以後のコードでは searchPhraseとpage を使用します。

この種のコードには、いくつかの理由で欠点があります。おそらく読者ご自身で多くの問題点を見つけられると思うので、ここでは保守とテストの可能性という、2 つの大きな問題に焦点をあててみます。

このようなコードは、アプリケーションのインフラストラクチャに関わるものであって、処理対象の実際のタスクとは何の関連性もありません。おそらくどのページも、処理する必要のある独自の一連の入力変数をもっており、そのため、そのインフラストラクチャ コードを共通ライブラリ中に取り出すのは困難です。コードの読み取りはますます困難になり、フォーム フィールドの修正が必要になったときは、そのフィールドを処理するすべてのページを修正する必要にせまられます。そのアプリケーションは、たちまち保守も拡張も難しくなります。

ユーザー インターフェイス内に置かれたコードに対して自動テストを実行するほうが通常は困難なのですが、その中に入っているロジックが増えれば増えるほど、自動テストの必要性は増します。 Web アプリケーションの場合、ユーザー インターフェイスの自動テスト ツールは存在しますが、そのようなテストは、単体テストより通常は安定性は劣ります。 Windows ベースのアプリケーションでは、そのようなテスト ツールはあまり広く使われていません。また、管理下にある Windows アプリケーションは、Web アプリケーションと同じようなインフラストラクチャ コードを必要としませんが、ユーザー インターフェイスのロジックが急速に巨大化するのは同じです。

モデル ビュー コントローラの実装

オブジェクト指向の開発における多くの問題と同様に、新しいレベルの間接指定を付け加えることがソリューションになります。軸となるソリューションとしては、ユーザー インターフェイスとドメイン モデルの間に新しいコントロール レイヤーを挿入します。このアプローチの説明となるモデル ビュー コントローラという基本設計パターンがあります。

Web ページまたは Windows フォームからドメイン モデルに直接通信させないで、コントローラが 2 つの他のレイヤー間の通信を管理します。適正なレイヤー方式では、ドメイン モデルがコントローラやビューに気付くことはまったくありません。コントローラは、ドメイン モデルを使用していてもビューには気付かず、ビューは、コントローラを使用していてもドメイン モデルには気付きません。どのレイヤーも、自身の利用者には気付きませんが、変更があったときはそれらの利用者に知らせるためのイベントを生成することができます。

モデル ビュー コントローラを実装する代わりに、UIP Application Block を使用することができます。この再利用可能なコード モジュールには、モデル ビュー コントローラが巧妙に実装されていて、User Interface Process (UIP) Application Block (英語) に掲載されています。

UIP を使用すれば、多種多様なシナリオにあわせてコントローラを作成できます。この後お見せする例では、Martin Fowler の Patterns of Enterprise Application Architecture (Addison-Wesley 2002年版) に説明されているとおりの、アプリケーション コントローラの設計パターンに忠実に準拠した方式で UIP を使用しています。

太陽系の周遊旅行

宇宙旅行への関心が高まりつつある時代なので、太陽系内のさまざまな目的地へのチケットを販売する旅行会社を想定してみます。営業担当者が顧客の旅行要求を記録するための予約アプリケーションが必要になります。 UIP の使用法の例として、シンプルな予約アプリケーションを作成して単体テストする方法を示します。予約アプリケーションのワークフローは、図1に示されています。アプリケーションは、初期状態においては新規の予約プロセスを待機するだけです。そのときに、顧客情報が最初のステップとして記録されます。次に、旅行の目的地が選択されます。 3 番目のステップとして、そこまでの旅行の詳細を示した要約画面が表示されます。この旅行詳細画面からは、旅行目的地を増やしたり、旅行の予約を入れたり、操作全体をキャンセルしたりすることができます。サンプル アプリケーションについてお話しするときは、コントローラに重点を置いています。それは、大半の読者にとって、ドメイン ロジックやユーザー インターフェイスの作成はなじみが深いはずだからです。

図 1 予約アプリケーション UI フロー

コントローラの作成

コントローラは、ビジネス ロジック レイヤーではなく、ビジネス ロジック レイヤーの上位のシン アダプタです。このアダプタは基本的に、抜き取りと状態管理の実装のために、ユーザー インターフェイスからの呼び出しを横取りします。コントローラの作成と構成は若干込み入った作業であることはあらかじめご存じでしょうが、やりがいのある作業です。最初に行うことは、コントローラ クラスの作成と、アプリケーションの構成ファイルのセットアップです。コントローラの作成は、次のようにとても簡単です。

public class BookingController : ControllerBase
{
    public BookingController(Navigator context) : base(context) {}
}

BookingController は、Microsoft.ApplicationBlocks.UIProcess.ControllerBase を継承元とし、1 つのコンストラクタを実装します。このコンストラクタは、唯一のパラメータとしてナビゲータ オブジェクトをとります。 UIP では、ナビゲータ オブジェクトは、ユーザー インターフェイス プロセスのコントロール フローを管理します。ご自分のニーズによっては、UIP 内で別々のナビゲータを使用してビューの対話を指示してもかまいません。この予約サンプル アプリケーションでは、ビューからビューへの移行が的確に定義されているので、Graph Navigator という名前のナビゲータ オブジェクトを使用します。

Graph Navigator は、アプリケーションの構成ファイルを使って、どのビューを使用できるかと、どの移行が適正かを判別します (ナビゲーション グラフでは、構成 XML でエンコードされ明示的に有限な状態マシンへのユーザー対話のワークフローが省略されています)。 図 2 は、UIP 構成の navigationGraph セクションを示しています。 図 1 の各ビューと移行が、構成でどのように定義されているかに注目してください。 他の構成データと一緒にこの情報が UIP で使用されて、ビューからビューへの移行がコントロールされます。

コントローラは、ビューどうしの間の通信を管理する一方で、ビューからビューへ状態を通知するなんらかの手段を必要とします。 UIP は、各コントローラ内に状態オブジェクトを入れることで、それに対処します。基本的にはプロパティ数を増やしたシリアライズ可能な辞書オブジェクトであるデフォルトの状態クラスを使用できますが、開発を少ししやすくするには、それに代わる強く型付けされた状態クラスの作成のほうが通常は望ましいと思います。デフォルトの状態クラスと同じ機能を達成できることに注意してください。すなわち、強く型付けされた状態クラスの主な目的は、コードの判読性と保守容易性を高めることにあります。

UIP 用の強く型付けされた状態クラスの作成は決して難しくありません。それについては、他の資料に詳しく説明されているので、ここでは詳細は取り上げません。状態クラスとは、単に、以下のパブリック API を備えた強く型付けされたプロパティ バッグであると考えてください。

public class BookingState : State {
    public event StateChangedEventHandler StateChanged;
    public void Reset();
    public string FirstName { get; set; }
    public bool IsBooked { get; set; }
    public string LastName { get; set; }
    public StringCollection SelectedDestinations { get; }
    public TravelClass TravelClass { get; set; }
    ...
}

BookingState クラスの実装方法についてもっと詳しく知りたい場合、MSDN Magazine Web サイトに掲載されている、この記事に付随したダウンロード資料中の解説を参照してください。

BookingState クラスと BookingController クラスを使用するように UIP に指示する必要があります。そのためには、アプリケーションの構成ファイル内で次のようにします。

<objectTypes>
    ...
    <state name="State" type="BookingUip.BookingState, BookingUip,
        Version=null, Culture=neutral, PublicKeyToken=null" />
    <controller name="BookingController"
        type="BookingUip.BookingController, BookingUip, Version=null,
        Culture=neutral, PublicKeyToken=null" />
    ...
</objectTypes>

このコードは、BookingController の状態プロパティに BookingState クラスのインスタンスを移植するよう UIP に指示することになります。このプロパティにアクセスしてそれを BookingState にキャストすることはできますが、それではこのコードの判読性を高めることにはならないので、便宜上 BookingController クラス上にプロパティを作成して、強く型付けされた状態クラスにより簡単にアクセスできるようにしました。

public BookingState BookingState
{
    get { return (BookingState)this.State; }
}

これで、BookingController の実際のロジックの作成を開始する準備ができました。まず手始めに、新しい予約プロセスを開始するメソッドを作成します。これは、図 1 の中の最初の「新規旅行を作成する」への移行に対応します。

public void CreateNewTrip()
{
    this.BookingState.Reset();
    this.BookingState.Save();
    this.Navigate("createNewTrip");
}

見てお分かりのとおり、かなり単純なメソッドです。これは新規の予約プロセスなので、内部状態をリセットするのが順当であると思われます。 UIP 内の状態を修正した後は、必ず Save メソッドを呼び出す必要があります。このアプリケーションの場合、私はメモリー内状態を使用しています。これを永続可能な状態に変更することにした場合、Save メソッドを呼び出して、基盤のストレージに対してその新しい状態を永続化する必要があります。

コードの最後の行は、ナビゲーション アクションを実行するよう UIP に指示しています。どのビューへナビゲートするかを指示しているのではなく、どの移行を実行するかを指示していることに注意してください。もう一度図 2 をご覧になれば、createNewTrip の移行によって、Start ビューから PassengerInformation ビューに移動していることが分かります。

単体テスト プロジェクトの準備

さらにメソッドをコントローラに追加する必要があるのは明らかですが、その前に、CreateNewTrip メソッド用の単体テストを作成したいと思います。コントローラの作成と同様に、単体テスト プロジェクトをセットアップするのは多少の努力が必要です。現在、私は単体テスト用に NUnit 2.2 (www.nunit.org (英語) に掲載されています) を使用していて、常に単体テスト用に別のプロジェクトを作成します。該当する参照をこの新プロジェクトに追加した後、すべての UIP 構成エントリを収容する構成ファイルを作成する必要があります。

単体テスト プロジェクトはライブラリ プロジェクトであるので、Visual Studio .NET は、プロジェクト フォルダから出力フォルダに App.config ファイルをコピーすることも、その名前を正しく変更することもありません。というわけで、これらを行うよう手動で Visual Studio に指示する必要があります。そのためには、以下のビルド後のイベントを単体テスト プロジェクトに追加します。

copy "$(ProjectDir)App.config" "$(TargetDir)$(TargetFileName).config"

これで、App.config ファイルが出力フォルダにコピーされて、名前が変更されます。ここでの単体テスト プロジェクトは BookingUipUnitTests という名前なので、デバッグ モードではこのファイルは、\bin\debug\BookingUipUnitTests.dll.config にコピーされます。その後、NUnit は、他のすべてのアプリケーション構成ファイルと同様に、構成ファイルを取得して使用します。

開発中の実際のアプリケーションの構成設定の大半は再利用可能ですが、単体テストの諸条件下で機能させるには、いくつかの設定に修正を施す必要があります。単体テストに関する考え方はおしなべて、可能なかぎり少数の依存関係しかもたない分離済みのライブラリをテストしようということなので、UI プロジェクトへのすべての参照を構成ファイルから取り除く必要があります。

構成ファイル中の UIP セクションには、各ビューをどのように実装するかを定義するセクションがあります。実際の表示レイヤー アプリケーションでは、そのセクションは、どの Windows フォームまたはWeb ページがどのビューに対応するかを指定するのに使用します。次に UIP はその情報を使って、構成済みのナビゲーション グラフに基づいて正しいビューを活動化して表示します。

実際のアプリケーションでは、各ビューごとに Windows Form クラスを定義しましたが、ここでの単体テストではそのようなクラスへの依存関係があることは望ましくありません。さらに、単体テストでは、どの UI にも信頼を置くことはできません。それは、この単体テストをスケジュール済みのジョブとして実行したいからです。ここでもやはりソリューションは、実際の UI の代わりに活動化できるスタブの作成にあります。

スタブを作成するために、私は、TestForm という新規ウィンドウ フォームを単体テスト プロジェクトに追加しました。このフォームには機能性はまったく備わっていません。私が加えた唯一の変更は、その WindowState プロパティを Minimized にしたことだけです。それによって、テストの実行中にフリッカーのオン/オフが繰り返されるのを目にしなくて済みます。

次に、このフォームを使って、スタブ フォームのみを使用するように構成ファイルのビュー セクションを定義することができます。 図 3 でお分かりのとおり、4 つのすべてのビューで、同じ TestForm クラスを使用することが定義されています。ここでは、テストの実行時に UIP は、この構成ファイルを読み込み、ビューを活動化する必要が生じるたびに、TestForm のインスタンスを作成、表示、処分しますが、それは瞬時に行われるので、気付くことはほとんどありません。このアプローチが技巧的すぎると思われる方は、テスト スタブ ビューの実装方法に関するサイドバーを参照してください。

コントローラの単体テスト

ここまでは、単体テスト プロジェクトをセットアップして構成しただけで、まだテスト コードはまったく作成していません。構成全体が整ったので、最後に、図 4 に示されているとおり、単体テストを収容するスケルトン クラスを作成することができます。この時点では、初期化メソッドといくつかのヘルパー プロパティを作成しただけです。

CreateController メソッドを活用すれば、初期化済みの BookingController オブジェクトをいつでも使用できるようになります。このコントローラ オブジェクトは、GraphNavigator コンストラクタに対するグラフ名の提供時に、図 2 に指定されているグラフ ナビゲータを使って初期化されます。2 つのプライベート プロパティは、このテスト コードの残りの部分の判読性を少し向上させるためのヘルパーにすぎません。

CreateNewTrip メソッドをテストするために、私は図 5 のテスト メソッドを作成しました。まずしなければならないのは、それに関連したナビゲータ オブジェクトをなんらかの方法で初期化することです。これを行う方法はいくつかありますが、テストしようとしている新規の予約プロセスの初期化なので、GraphNavigator 上で StartTask メソッドを呼び出すことができます。それによって、構成ファイルに定義されているとおりに新しいナビゲーション タスクを開始するよう UIP に指示が出されます。また、構成済みのナビゲーション グラフには、開始ビューは Start という名前のビューであると定義されているので、そのビューが、StartTask メソッドの呼び出し後の最初のビューになります。

次に、CreateNewTrip メソッドを起動し、所定の値をコントローラがもっていることを検証します。テストの最後のアサーションでは、CreateNewTrip の呼び出し後に、やはり構成ファイルに指定されているとおりに、コントローラが PassengerInformation ビューに移行したことを検証します。基本的に、コントローラ上でメソッドを単体テストする場合に必要なことは、これですべてです。

UI フロー内部のテスト

最初に示したテストでは、ナビゲーション タスクの冒頭でコントローラをテストする方法を明らかにしました。ただし、UI フローの途中でコントローラがどのように作動するかをテストしたい場合、すなわち、ユーザーが [Select Destination] ビューのリストに目的地を追加する場合などには、それとは異なる展開になります。

目的地を追加するコントローラのメソッドを以下に示してあります。

public void ConfirmDestinationSelection(string selectedDestination)
{
    this.BookingState.SelectedDestinations.Add(selectedDestination);
    this.BookingState.Save();
    this.Navigate("confirmSelection");
}

これは単に、新規の目的地を内部目的地リストに追加し、状態を保管し、confirmSelection ビューへの移行を UIP に指示しているだけです。このメソッドをテストするために、私は図 6 の単体テストを作成しました。 ConfirmDestinationSelection メソッドを起動する前に、[Select Destination] ビューに対応する状態にコントローラを置く必要があります。そのためには、どのビューが現行ビューかを UIP に知らせて、それに関連した内部状態オブジェクトの部分を初期化する必要があります。

私のテスト シナリオではまだ予約プロセスの途中なので、StartTask メソッドを呼び出して GraphNavigator を初期化することはできません。なぜなら、そのようにすると、[Start] ビューを使った新規の予約プロセスが開始してしまうからです。運良く、私はナビゲータの状態にアクセスして、現行ビューの名前を通知することでナビゲータを直接操作することができます。

本当はその必要はないのですが、初期状態において「地球」という目的地がすでに選択されているシナリオをテストすることにしました。それは、すでに選択済みのどの目的地も、新規の目的地によってオーバーライドされないことをテストできるからです。

このテストの初期状態をセットアップした後、コントローラ上で ConfirmDestinationSelection メソッドを起動しました。それによって、2 つのことが発生するはずです。つまり、新規の目的地が内部目的地リストに追加され、ビューは TripDetails ビューに変更されたはずです。これらの条件が整った場合、アサーションは失敗しないので、テストは通ります。

イベントの生成とテスト

適切なオブジェクト指向設計であれば、コンポーネントはクライアントについて何も知る必要はありませんが、何か重要なことが起きたらイベントを生成する必要はあります。 UIP では、コントローラの状態オブジェクトは、状態の変更につながる何かが起きるたびに、StateChanged イベントを生成します。 State を継承元とする BookingState クラスは、同じ動作を実装しています。つまり、プロパティの設定ごとに、StateChanged イベントを生成します。一般的に、予約アプリケーションは、そのようなイベントに関心をもたないので、単に無視するだけですが、[Trip Details] ビューの場合は、このビュー内のコントロールを更新できるように、予約の結果として状態が変更したかどうかを知りたいと思います。旅行の予約は、次のように BookTrip メソッドを使って行います。

public void BookTrip()
{
    /* 本当は、旅行の完全情報をドメイン モデル レイヤーに書き込む
     * 必要がありますが、分かりやすいように省略しました。*/
    this.BookingState.IsBooked = true;
    this.BookingState.Save();
    this.Navigate("bookTrip");
}

ここではあまり多くのことは行われませんが、IsBooked プロパティを設定したために、StateChanged イベントの生成が必要になります。これをテストするために、図 7 の単体テストを作成しました。 BookTrip メソッドを起動する前に、eventWasRaised_ member 変数を false に設定し、StateChanged イベントにサブスクライブしました。 BookTrip を呼び出すと、これがイベント ハンドラを起動するはずです。そこで私は、イベント データが見込んでいたとおりかどうかを検証します。すべてを調べ終わったら、eventWasRaised_ member 変数を true に設定します。イベント ハンドラの実行が完了したら、制御権はテスト メソッドに戻されるので、さらに検査を行って、イベントが実際に生成されて、IsTripBooked プロパティは true になったことを検証します。

シン ユーザー インターフェイス

BookingController の残りの部分を実装して単体テストする方法の詳細は取り上げませんが、関心のある方は、サンプル コードのダウンロードによって完全なコントローラを入手することができます。

予約アプリケーション用の UI の作成はこれでかなり簡単になりました。それは、UI の大半のコードは、作業を BookingController に委任するからです。 UIP 内のビューは IView インターフェイスを実装する必要がありますが、さいわい Application Block には、Windows フォームと Web アプリケーション用の IView の実装が付帯しています。たとえば、[Start] ビューを作成するには、次のように、WindowsFormView を継承元とする必要があるだけです。

public class StartForm : WindowsFormView
   { /* 実装... */ }

WindowsFormView は、通常はサブクラスである Form クラスを継承元とするので、この場合もやはり Windows フォーム アプリケーションを作成することになります。

[Start] ビューには、ボタンが 1 つだけ備えられていて、これを押すと予約プロセスが開始するので、[Start] フォームの実装は簡単です。 StartForm の継承元である WindowsFormView クラスには、実際の作業を代行させるコントローラ プロパティが入っています。 StartForm では、次のように、1 つのボタンのイベント ハンドラを実装することができます。

private void createNewTripButton__Click(
    object sender, System.EventArgs e)
{
    ((BookingController)this.Controller).CreateNewTrip();
}

ここでボタンをクリックすると、実際の作業は CreateNewTrip メソッドに委任されて、その作業が遂行されます。その後で、メソッドに定義されているとおりに、新規ビューへの移行を UIP が管理できるようにします。ここの例はけた外れにシンプルに見えるかもしれないので、図 8 に示されている、総合アプリケーション内の最も複雑なビューである [Trip Details] ビューを見てみましょう。このビューは、既存データのロード、3 種類のボタンの処理、および状態に応じたそれらのボタンのいずれかの有効化と無効化を行うことができるはずです。このビューの実装法は、図 9 に示されています。

既存のデータのロードと、ボタンのクリック イベントの処理は形式的です。なぜなら、それは、すべてのロジックを BookingController に委任するからです。ボタンの状態の管理はそれよりは少し複雑ですが、実装はやはり簡単です。旅行の予約が完了しないかぎり、3 つのボタンはすべて有効になっていなければなりません。旅行を予約したら、予約済みの旅行はもう編集できなくなるので、[Add Leg to Trip] ボタンと[Book] ボタンを無効にしなければなりません。そういうわけで、イベント ハンドラを StateChanged イベントに追加して、コントローラの基盤となる状態の変更にビューがいつでも対応できるようにしました。そのようなイベントが生成された場合、コードはそのイベントが IsBooked プロパティに関連付けられているかどうかを調べ、もし関連付けられていれば、必要に応じてボタンを有効または無効にするための SetButtonState メソッドを呼び出します。

結論

UIP を使用すると、いくつかの利点があります。すなわち、ユーザー インターフェイス コードの複雑な部分を、単体テスト可能なライブラリ中にプルし、ビューどうしの間の通信に対して配慮しなくて済むようにし、1 つのアプリケーションに対する複数のビューの実装を簡単に作成できるようにします。私は、サンプルの予約プログラムを Windows フォーム アプリケーションとして作成するつもりでしたが、作成の完了時点で ASP.NET アプリケーションに作成し直しました。それらは基本的に同じものであることが判明し、私が書く必要のあったコードも少量でした。


図8 The Trip Details FormM

ASP.NET アプリケーションの作成には数時間を要しましたが、UIP によってビューどうしの間のすべての通信が取り去られたので、ページ間に関与するクエリ ストリングや非表示のフォーム フィールドはなくなりました。 UIP は、一見したところ複雑でとっつきにくいテクノロジであるかのように見えますが、習得して使い始めるのに 1 日もかからないので、時間と労力を投入するだけの価値があることに賛同いただけると確信しています。

この記事で取り上げたトピックの詳細は、 www.nunit.org (英語) の NUnit にアクセスして、User Interface Process (UIP) Application Block (英語) の中の User Interface Process Application Block バージョン 2 を参照してください。


Mark Seemann コペンハーゲン (デンマーク) の Microsoft Consulting Services に所属し、Microsoft の顧客およびパートナの社内アプリケーションの構築、設計、および開発の支援に携わっています。連絡先は mseemann@microsoft.com (英語) です。


 この記事は、 MSDN マガジン - 2005 年 8 月号からの翻訳です。 .