2017-03-31 15 views
0

私はマルチスレッドアプリケーションで作業していますが、同時に1つのスレッドだけが実行するコードの一部があります。複雑ではありません。私はそれを同期するためにロックを使用します。それは人生システムで働いていますが、私は、ただ1つのスレッドがクリティカルセクションにあるかどうかをチェックする単体テストを書いています。私は1つ書いたが、それは動作していたが停止します:) 私は正しい方法でテストを書く方法を把握することはできません。 NSubstituteを使ってモックを作成します。試験にスレッド同期のユニットテスト(ロック)

クラス:

public interface IMultiThreadClass 
{ 
    void Go(); 
} 
public class Lock02 : IMultiThreadClass 
{ 
    private readonly IProcessor _processor; 
    private readonly string _threadName; 

    private static readonly object Locker = new Object(); 

    public Lock02(IProcessor processor, string threadName) 
    { 
     _processor = processor; 
     _threadName = threadName; 
    } 

    public void Go() 
    { 
     //critical section 
     lock (Locker) 
     { 
      _processor.Process(_threadName); 
     } 
    } 
} 

試験:

[TestMethod()] 
public void Run_Test() 
{ 
    //Only one thread should run Processor.Process, but we allow max 2 threads to catch locking erorrs 
    SemaphoreSlim semaphore = new SemaphoreSlim(1, 2); 

    //Semaphore to synchronize asserts 
    SemaphoreSlim synchroSemaphore = new SemaphoreSlim(0, 1); 

    IProcessor procesor = Substitute.For<IProcessor>(); 
    procesor.When(x => x.Process(Arg.Any<string>())).Do(y => 
    { 
     //increment counter to check if method was called 
     Interlocked.Increment(ref _counter); 

     //release synchro semaphore 
     synchroSemaphore.Release(); 

     //stop thread and wait for release 
     semaphore.Wait(); 
    }); 

    Lock02 locker1 = new Lock02(procesor, "1"); 
    Lock02 locker2 = new Lock02(procesor, "2"); 
    Lock02 locker3 = new Lock02(procesor, "3"); 

    Task.Run(() => locker1.Go()); 
    Task.Run(() => locker2.Go()); 
    Task.Run(() => locker3.Go()); 

    //ASSERT 
    //Thread.Sleep(1000); 
    synchroSemaphore.Wait(); 
    Assert.AreEqual(1, _counter); 

    semaphore.Release(1); 
    synchroSemaphore.Wait(); 
    Assert.AreEqual(2, _counter); 

    semaphore.Release(1); 
    synchroSemaphore.Wait(); 
    Assert.AreEqual(3, _counter); 

    semaphore.Release(1); 
} 

答えて

1

可能(シンプルだが防弾ではない)方法は、ユニットテストの一部のスレッド/タスクを起動する各フェッチと一時的に記憶しますint変数(静的な可能性もある)、ビット待ち(遅延)、値のインクリメント、変数への書き戻しなどがあります。スレッド同期(ロック)がなければ、すべてのスレッドではないにしても多くのスレッドが同じ数を取得し、スレッド/タスクの数と同じではありません。

すべてのトレッドは、各待つすることは(私に)は非常に低いと思われるが、それは再現性のない作り競合状態が、(臭いのコードは50ミリ秒の遅延である)まだあるので、これは防弾ではありません他の方法で完璧な結果を生み出します。

これは厄介な回避策だと考えていますが、それは簡単で機能します。

[TestMethod] 
    public async Task APossibleTest() 
    { 
     int importantNumber = 0; 

     var proc = Substitute.For<IProcessor>(); 
     proc.WhenForAnyArgs(processor => processor.Process(Arg.Any<string>())) 
      .Do(callInfo => 
      { 
       int cached = importantNumber; 
       // Wait for other threads to fetch the number too (if they were not synchronized). 
       Thread.Sleep(TimeSpan.FromMilliseconds(50)); 
       // This kind of incrementation will check the thread synchronization. 
       // Using a thread-safe Interlocked or other here does not make sense. 
       importantNumber = cached + 1; 
      }); 

     var locker = new Locker(proc, "da horror"); 

     // Create 10 tasks all attempting to increment the important number. 
     Task[] tasks = 
      Enumerable 
       .Range(0, 10) 
       // You could create multiple lockers here (with their own processors). 
       .Select(i => Task.Run(() => locker.Go())) 
       .ToArray(); 
     await Task.WhenAll(tasks); 

     Assert.AreEqual(10, importantNumber, "Exactly 10 increments were expected since we have 10 tasks."); 
    }