Invest in people before investing in tools

Il blog di Matteo Baglini
posts - 118, comments - 95, trackbacks - 697

Usare RhinoMocks per testare applicazioni multithreading

Interessato dal post di Antonio sull'uso del TDD per progettare applicazi multithreading ho deciso di riprovare senguendo il percorso logico da lui espresso, usando RhinoMocks come framework di mocking piuttosto che i mocks manuali usati da Antonio per non complicare il codice di test. La versione di RhinoMocks utilizzata è la 3.5 RC che potete scaricare a questo url, questa versione della libreria supporta la sintassi Arrange/Act/Assert permettendo di scrivere test molto più chiari, per maggiori info visitate questa pagina.

Adesso vediamo e commentiamo i vari metodi di test, cominciamo con il primo:

   1:  public void When_Enqueue_A_Log_The_LogWriter_Is_Invoked()
   2:  {
   3:      var log = new Log();
   4:      var writer = MockRepository.GenerateMock<ILogWriter>();
   5:      var thread = new LogThread(writer);
   6:   
   7:      thread.Enqueue(log);
   8:   
   9:      writer.AssertWasCalled(x => x.Write((Arg<Log>.Is.Equal(log))));
  10:  }

Le differenze sostaziali sono, alla riga 4 e 9, nella prima generiamo un mock dell'interfaccia ILogWriter e nella seconda "chiediamo" al mock object di verificare se è stato chiamato il metodo Write e se il parametro in ingresso era uguale a log. Proseguiamo con il secondo test:

   1:  public void The_Calls_To_The_Writer_Are_Asynchronous()
   2:  {
   3:      var log = new Log();
   4:      var writer = MockRepository.GenerateMock<ILogWriter>();
   5:      var thread = new LogThread(writer);
   6:      int writerCallCount = 0;
   7:      var resetEvent = new ManualResetEvent(false);
   8:   
   9:      writer.Expect(x => x.Write((Arg<Log>.Is.Anything))).Do(new Action<Log>(l =>
  10:      {
  11:          writerCallCount++;
  12:          resetEvent.WaitOne();
  13:          writerCallCount--;
  14:      }));
  15:   
  16:      thread.Enqueue(log);
  17:      while (writerCallCount == 0)
  18:      {
  19:          Thread.Sleep(0);
  20:      }
  21:      Assert.AreEqual(1, writerCallCount);            
  22:      resetEvent.Set();
  23:  }

La parte interessante in questo test sono le righe dalla numero 9 alla 14, dove istruisco il mock object. In pratica non faccio altro che "dire" al mock object "quando viene invocato il metodo Write con una qualsiasi istanza di Log, esegui la seguente Action". Il delegate passato come Action al metodo Do esegue la solita logica del mock manuale usato da Antonio. Per il resto il codice di test è sostanzialmente identico. La logica del terzo metodo di test è la solita quindi vi posto solo il codice:

   1:  public void Two_Logs_Enqueued_Are_Served_One_At_Time()
   2:  {
   3:      var writer = MockRepository.GenerateMock<ILogWriter>();
   4:      var thread = new LogThread(writer);
   5:      int writerCallCount = 0;
   6:      var resetEvent = new ManualResetEvent(false);
   7:   
   8:      writer.Expect(x => x.Write((Arg<Log>.Is.Anything))).Do(new Action<Log>(l =>
   9:      {
  10:          writerCallCount++;
  11:          resetEvent.WaitOne();
  12:          writerCallCount--;
  13:      }));
  14:   
  15:      Log log1 = new Log();
  16:      thread.Enqueue(log1);
  17:      while (writerCallCount < 1)
  18:      {
  19:          Thread.Sleep(0);
  20:      }
  21:   
  22:      Log log2 = new Log();
  23:      thread.Enqueue(log2);
  24:      while (thread.RunningThreadCount < 2)
  25:      {
  26:          Thread.Sleep(0);
  27:      }
  28:   
  29:      Assert.AreEqual(1, writerCallCount);
  30:   
  31:      resetEvent.Set();
  32:      while (writerCallCount > 0)
  33:      {
  34:          Thread.Sleep(0);
  35:      }
  36:  }

In fine passiamo all'ultimo metodo di test:

   1:  public void When_The_Writer_Throws_An_Exception_The_QueueLength_Is_Decreased()
   2:  {
   3:      var writer = MockRepository.GenerateMock<ILogWriter>();
   4:      var thread = new LogThread(writer);
   5:      bool isWriting = false;
   6:   
   7:      writer.Expect(x => x.Write((Arg<Log>.Is.Anything))).Do(new Action<Log>(l =>
   8:      {
   9:          isWriting = true;
  10:          throw new Exception();
  11:      }));
  12:   
  13:      thread.Enqueue(new Log());
  14:      while (!isWriting)
  15:      {
  16:          Thread.Sleep(0);
  17:      }
  18:      while (thread.RunningThreadCount > 0)
  19:      {
  20:          Thread.Sleep(0);
  21:      }
  22:      Assert.AreEqual(0, thread.RunningThreadCount);
  23:  }

Anche qui la parte interessante è la configurazione del mock object, in questo test la sintassi del mock rimane uguale ai test precedenti, quello che cambia è la logica contenute nell' Action delegate passato al metodo Do, il quale scatena un' eccezione.

Nel mio repository personale ho creato tre proggeti uno con la versione manuale dei mocks, uno che usa RhinoMocks ed in fine uno che usa Moq, il tutto è reperibile a questo url: http://makesimple.googlecode.com/svn/trunk/UnitTestMultithreading

Print | posted on lunedì 25 agosto 2008 14:19 | Filed Under [ .NET Agile ]

Powered by:
Powered By Subtext Powered By ASP.NET