例から始めましょう。 Webページを非同期にダウンロードするAPIがあります。 ページを取得するには、イベント[ 1 ]にサブスクライブして、アサーションが実際に存在する別のメソッドを作成します。 当然、テストメソッド自体をブロックする必要があります。 そうでない場合、テストランナーはコールバックを待たずに終了します。
[TestFixture]<br/>
public class MyTests<br/>
{<br/>
private ManualResetEvent waitHandle;<br/>
[Test] <br/>
public void TestAsyncPageDownloading()<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
⋮<br/>
wdp.GetWebDataCompleted += wdp_GetWebDataCompleted;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
waitHandle.WaitOne(); // assert'
}<br/>
void wdp_GetWebDataCompleted( object sender, GetWebDataCompletedEventArgs e)<br/>
{<br/>
StreamReader sr = new StreamReader(e.Stream);<br/>
string s = sr.ReadToEnd();<br/>
// test runner'
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
waitHandle.Set(); //
}<br/>
}<br/>
残念ながら、このアプローチでは、テストが「フリーズ」し、それによってテストプロセスが遅くなる状況を考慮していません。 これを行うには、
WaitOne()
呼び出しにタイムアウトを追加できますが、コールバックがまったく呼び出されない可能性もテストする必要があります。 これを行うには、
bool
型の別の変数を追加する必要があります。 結果は次のとおりです。
private bool callbackInvoked;<br/>
⋮<br/>
[Test]<br/>
public void TestAsyncPageDownloading()<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
wdp.GetWebDataCompleted += wdp_GetWebDataCompleted;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
waitHandle.WaitOne( 5000 ); // 5 assert'
Assert.IsTrue(callbackInvoked, "Callback method was never called" ); <br/>
}<br/>
void wdp_GetWebDataCompleted( object sender, GetWebDataCompletedEventArgs e)<br/>
{<br/>
callbackInvoked = true ; //
StreamReader sr = new StreamReader(e.Stream);<br/>
string s = sr.ReadToEnd();<br/>
// test runner'
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
waitHandle.Set(); //
}<br/>
テストフィクスチャ内のメソッドと変数の数を増やすことを避けるために、「非同期テスター」自体をマネージャーとして使用できる別のクラスに配置すると便利です。
public class EventTester<SenderType, ArgumentType> : IDisposable<br/>
{<br/>
private readonly ManualResetEvent waitHandle;<br/>
private readonly Action<SenderType, ArgumentType> postHocTests;<br/>
private bool called;<br/>
private IAsyncResult waitToken;<br/>
public EventTester(Action<SenderType, ArgumentType> postHocTests)<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
this .postHocTests = postHocTests;<br/>
}<br/>
public void Handler(SenderType sender, ArgumentType args)<br/>
{<br/>
waitHandle.Set();<br/>
waitToken = postHocTests.BeginInvoke(sender, args, null , null );<br/>
}<br/>
public void Wait( int mullisecondsTimeout)<br/>
{<br/>
called = waitHandle.WaitOne(mullisecondsTimeout);<br/>
}<br/>
public void Dispose()<br/>
{<br/>
Assert.IsTrue(called, "The event was never handled" );<br/>
postHocTests.EndInvoke(waitToken);<br/>
}<br/>
}<br/>
このテスターは何をしますか? まず、ポストホックテストで呼び出す必要があるコールバックメソッドをキャッシュします。 さらに、
Wait()
メソッドを提供します。このメソッドは
ManualResetEvent
委任されるため、イベントハンドラーが呼び出されるまでしばらく待つことができます。 呼び出されるとすぐに、最終テストをすぐに開始します。 そして-ちょっとしたトリック
IDisposable()
を実装し、
Dispose()
メソッドでハンドラーが呼び出されたかどうかを確認します。 アサートは実行を中断するため、後続の
EndInvoke()
は適切な場合にのみ呼び出されます。
テスト自体については、次のようになりました。
[Test]<br/>
public void BetterAsyncTest()<br/>
{<br/>
using ( var eventTester = new EventTester< object , GetWebDataCompletedEventArgs>(<br/>
(o, args) =><br/>
{<br/>
StreamReader sr = new StreamReader(args.Stream);<br/>
string s = sr.ReadToEnd();<br/>
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
}))<br/>
{<br/>
wdp.GetWebDataCompleted += eventTester.Handler;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
eventTester.Wait(5000);<br/>
}<br/>
}<br/>
これは確かに世界で最も読みやすいテストではありませんが、少なくともクラス本体に追加のフィールドとメソッドを生成しません。 イベントのテストは、
BeginXxx()/EndXxx()
ペアのテストと同様に、予測可能な2つの要素(呼び出しとハンドラー、開始と終了)が常に予測されるため、単純なタスクです。 ■
注釈
- ↑この場合、
MicrosoftSubscription.Sync.WebDataProvider
クラスをテストしていMicrosoftSubscription.Sync.WebDataProvider
-これは、有名なphotoSuruアプリケーションを使用するフレームワークであるSyndicated Client Experiences SDKの一部です。 - ↑このアプローチは、私のブログページのコメントで特定のvansickleによって提案されました。