2016-06-16 5 views
1

クラスの変数を変更すると、ユニットテストをしようとしているイベントが非同期で実行されるという設定があります。ユニットテスト非同期イベント

class A { 

    Action<int> OnIntChange; 

    private int _a; 
    public int a { 
     set { 
      OnIntChange(value); 
     } 
    } 
} 

class B { 

    RaiseAsyncEvent(int value) { 
     Task.Factory.StartNew(() => {c = value;}); 
    } 

    int c; 
} 

以前はすべてが同期だったので、私はかなり簡単にユニットテストできます

B b = new B(); 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

しかし、これは今、失敗します。私はManualResetEventsを使用することはできません。なぜなら、私はこのイベントを直接提起しているわけではなく、コードの再構成をしなければできないからです。私の唯一の選択肢は、A.a = 10を呼び出した後にSystem.Threading.Thread.Sleep()を追加することですが、他のオプションを探しています。提案?

~~~~~~~~~~編集~~~~~~~~私が見てい

一つの潜在的解決策はRaiseAsyncEventは、それが作成したタスクを返し、それにフィールドを設定することです仕事。ような何か:

class B { 

    Task task; 

    RaiseAsyncEvent(int value) { 
     task = Task.Factory.StartNew(() => {c = value;}); 
    } 

    int c; 
} 


B b = new B(); 
A.OnIntChange += b.RaiseAsyncEvent; 
b.task.Wait(); 
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

~~~~~~~~~~さらに編集~~~~~~~~~~私は、これは次のようにBを変えた解決するためにやってしまった何を

あなたはBのコンストラクタでのカスタムバージョンをを渡すことができ

B b = new B(); 
b.RunAsync = false; 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

答えて

2

class B { 

    bool RunAsync = true; 

    RaiseAsyncEvent(int value) { 
     Task task = Task.Factory.StartNew(() => {c = value;}); 
     if(!RunAsync) { task.Wait();} 
    } 

    int c; 
} 

とにテストを変更TaskSchedulerは、ユニットテストですべてのタスクの完了を待つことができます。

class B { 
    public B(TaskScheduler taskScheduler) 
    { 
     _taskScheduler = taskScheduler; 
    } 


    public B(): this(TaskScheduler.Default) 
    { 
    } 


    public void RaiseAsyncEvent(int value) 
    { 
     Task.Factory.StartNew(() => {c = value;}, CancellationToken.None, 
     TaskCreationOptions.DenyChildAttach, _taskScheduler); 
    } 

    TaskScheduler _taskScheduler; 

} 

ConcurrentExclusiveSchedulerPairは、私たちが探している完了を待つ機能があります。

// Unit test 

var schedulerPair = new ConcurrentExclusiveSchedulerPair(); 

B b = new B(schedulerPair.ConcurrentScheduler); 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 

schedulerPair.Complete(); 
schedulerPair.Completion.Wait(); 

Assert.AreEqual(10, A.b.c); 

複数の非同期操作に目を維持する必要がある場合には、より高度なシナリオにスケール:

B b1 = new B(schedulerPair.ConcurrentScheduler); 
B b2 = new B(schedulerPair.ConcurrentScheduler); 
B b3 = new B(schedulerPair.ConcurrentScheduler); 

A.OnIntChange += b1.RaiseAsyncEvent;  
A.OnIntChange += b2.RaiseAsyncEvent;  
A.OnIntChange += b3.RaiseAsyncEvent;  
A.a = 10; 

schedulerPair.Complete(); 
schedulerPair.Completion.Wait(); 

Assert.AreEqual(10, b1.c); 
Assert.AreEqual(10, b2.c); 
Assert.AreEqual(10, b3.c); 

EDIT:TaskSchedulerインスタンスは、この場合も、静的プロパティを使用して共有することができますBコンストラクタの署名は変更されません。

static class EventScheduler 
{ 

    public static TaskScheduler TaskScheduler 
    { 
     get {return _taskScheduler; } 
     set {_taskScheduler = value; } 
    } 

    static TaskScheduler _taskScheduler = TaskScheduler.Default; 

    public static Task RunAsync(Action<T> action) 
    { 
     return Task.Factory.StartNew(() => {c = value;}, 
     CancellationToken.None, TaskCreationOptions.DenyChildAttach, 
     _taskScheduler); 
    } 
} 

class B 
{ 
    public void RaiseAsyncEvent(int value) 
    { 
     EventScheduler.RunAsync(()=>{c = value;}); 
    } 
} 

// Unit test 

var schedulerPair = new ConcurrentExclusiveSchedulerPair(); 
EventScheduler.TaskScheduler = schedulerPair.ConcurrentScheduler; 

B b = new B(); 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 

schedulerPair.Complete(); 
schedulerPair.Completion.Wait(); 

Assert.AreEqual(10, A.b.c); 
+0

私はこの回答が好きで、私は受け入れられた回答としてマークしています - 私が質問した方法で私の問題を絶対に処理しなければならない場合、これは正しいでしょう。 しかし、私がやり遂げたのは、Bに "RunAsync"ブールフィールドを追加して同期させてタスクを強制的に実行することでした。 if(!RunAsync){ task.wait(); } RaiseAsyncEvent内にあり、b.RunAsync = falseに設定します。 TestInitializeで。一番美しいものではありませんが、本当に複雑な問題であったのは簡単な修正です。 –

+0

コンストラクタのシグネチャを変更しないようにするには、すべてのイベントで共有されるTaskScheduler(シングルトン)の静的インスタンスを考慮します。次に、すべてのオブジェクトに対してRunAsyncプロパティを設定するのではなく、単にConcurrentSchedulerを静的プロパティに割り当てます。 – alexm

+0

私が扱っているコードは、私がここに示しているよりはるかに複雑です - 私は実際に自分の静的CallbackManagerを実装しています。これは、イベントを渡して、2つの要因によって非同期または同期的に実行される新しいタスクを生成します。ハンドルを保存し、リターンロジックを処理します。私が追加しているブールでは、私は非同期ロジックをバイパスして、すべてのユニットテストがまだ通過する最初から同期させることができます。マネージャは静的なので、テスト開始時にRunAsync boolに1回以上の変更を加える必要はありません。うまく動作します。 –

関連する問題