Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Bezpieczne mockowanie internal methods.

Trafił mi się dość skomplikowany proces biznesowy, który ma być uruchamiany metodą void Process(). W celu uproszczenia, rozbiłem ciało tej metody na wiele pomniejszych metod.

   1: public void Process()
   2: {
   3:     List<TelesalesCompanyInfoApplication> applicationsFromDb = this.GetApplicationsFromDatabaseForProcessing();
   4:     if (applicationsFromDb == null)
   5:     {
   6:         throw new CriticalBusinessLogicException("Warstwa bazy danych, podczas podawania listy zgloszen nie powinna zwrócic null.");
   7:     }
   8:  
   9:     this.SetLockStatus(applicationsFromDb);
  10:  
  11:     foreach (TelesalesCompanyInfoApplication application in applicationsFromDb)
  12:     {
  13:         if (this.IsOriginalDateOlderThanApplicationDate(application))
  14:         {
  15: ...

Dzięki temu zabiegowi kod stał sie czytelniejszy oraz łatwiej będzie napisać testy jednostkowe. Chciałbym jednak uniknąć upubliczniania wszystkich metod w tej klasie. Do testów wystarczy że ustawimy je jako “internal” oraz zaufamy naszemu projektowi testującemu. Wystarczy w jakimkolwiek pliku dać wpis jak:

   1: [assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Services.UnitTests, PublicKey=A3DS...")]

Teraz bez problemu w naszej klasie testowej możemy testować “pomniejsze”, “internal” metody:

   1: [Test]
   2: public void IsOriginalDateOlderThanApplicationDate_ApplicationVerificationDateIsGreaterThanSurveyEndDate_ReturnTrue()
   3: {
   4:     TelesalesCompanyInfoApplication application = TelesalesCompanyInfoApplicationObjectMother.Make
   5:         .Customize(delegate(TelesalesCompanyInfoApplication x) { x.VerifySourceDocumentDate = DateTime.Now.AddDays(-2); })
   6:         .WithSurveyAs(CompanyInfoSurveyObjectMother.Make.WithDuration(DateTime.Now.AddDays(-10), DateTime.Now.AddDays(-8)));
   7:  
   8:     Assert.GreaterThan(application.VerifySourceDocumentDate, application.Survey.SurveyEnd);
   9:  
  10:     DefaultTelesalesCompanyInfoApplicationImportProcessingService service = new DefaultTelesalesCompanyInfoApplicationImportProcessingService();
  11:     Assert.IsTrue(service.IsOriginalDateOlderThanApplicationDate(application));
  12: }

Natomiast do  przetestowania metody “Process” najlepiej wykorzystać “partial mocking” np. tak:

   1: // Klasa pomocnicza do mockowania
   2: public abstract class MockedTestCase
   3: {
   4:     private MockRepository mockery;
   5:  
   6:     protected MockRepository Mockery
   7:     {
   8:         get { return this.mockery; }
   9:     }
  10:  
  11:     public virtual void SetUp()
  12:     {
  13:         this.mockery = new MockRepository();
  14:     }
  15: }
  16:  
  17: // Bazowa klasa do testowania logiki biznesowej
  18: public class ServiceTestCase<TService> : MockedTestCase where TService : IBaseService
  19: {
  20:     protected Func<TService> sutCreator;
  21:  
  22:     private TService sut;
  23:  
  24:     protected TService Sut
  25:     {
  26:         get { return this.sut; }
  27:         set { this.sut = value; }
  28:     }
  29:  
  30:     public static IUserSession GetStubbedUserSession()
  31:     {
  32:         IUserSession session = new UserSessionStub();
  33:         User sampleUser = new User();
  34:         sampleUser.LoginName = "test";
  35:         session.CurrentUser = sampleUser;
  36:  
  37:         return session;
  38:     }
  39:  
  40:     [SetUp]
  41:     public override void SetUp()
  42:     {
  43:         base.SetUp();
  44:         if (this.sutCreator != null)
  45:         {
  46:             this.Sut = this.sutCreator();
  47:         }
  48:     }
  49:  
  50:     public class UserSessionStub : BaseUserSession
  51:     {
  52:     }
  53: }
  54:  
  55: // Nasza klasa testujaca
  56: [TestFixture]
  57: public class DefaultTelesalesCompanyInfoApplicationImportProcessingServiceTests : ServiceTestCase<DefaultTelesalesCompanyInfoApplicationImportProcessingService>
  58: {
  59:     public DefaultTelesalesCompanyInfoApplicationImportProcessingServiceTests()
  60:     {
  61:         this.sutCreator = delegate { return Mockery.PartialMock<DefaultTelesalesCompanyInfoApplicationImportProcessingService>(); };
  62:     }
  63:  
  64:     [Test]
  65:     public void Process_GetApplicationsFromDatabaseForProcessingReturnsNull_ThrowsCriticalBusinessLogicException()
  66:     {
  67:         using (Mockery.Record())
  68:         {
  69:             Expect
  70:                 .Call(this.Sut.GetApplicationsFromDatabaseForProcessing())
  71:                 .Return(null);
  72:         }
  73:  
  74:         using (Mockery.Playback())
  75:         {
  76:             Assert.Throws<CriticalBusinessLogicException>(delegate { this.Sut.Process(); });
  77:         }
  78:     }
  79: ...

Niestety nie da sie zrobić w taki sposób “partial mocking” dla metod oznaczonych jako “internal”. Musiały by one być “public virtual”, a tego chciałbym uniknąć. Jednym ze sposobów jest ustawienie aby główny projekt “Service” zaufał bibliotece DynamicProxy2 z projektu Castle, która jest niepodpisana. Nie wygląda to zachęcająco. Ale możemy to zrobić pośrednio: “Service” ufa “Service.UnitTests” a ten ufa “DynamicProxy2”. Zatem do “Service.UnitTests” dodajemy:

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")]

oraz tworzymy pośrednia klasę która dziedziczy po naszej klasie testowanej. Należy pamiętać ze musi ona nadpisywać metody które uczestniczą w procesie partial mocking:

   1: public class TrustedDefaultTelesalesCompanyInfoApplicationImportProcessingService : DefaultTelesalesCompanyInfoApplicationImportProcessingService
   2: {
   3:     internal override List<TelesalesCompanyInfoApplication> GetApplicationsFromDatabaseForProcessing()
   4:     {
   5:         return base.GetApplicationsFromDatabaseForProcessing();
   6:     }
   7: }

oraz zmieniamy sposób tworzenia instancji testowanej klasy:

   1: // z
   2:  
   3: public DefaultTelesalesCompanyInfoApplicationImportProcessingServiceTests()
   4: {
   5:     this.sutCreator = delegate { return Mockery.PartialMock<DefaultTelesalesCompanyInfoApplicationImportProcessingService>(); };
   6: }
   7:  
   8: // na
   9:  
  10: public DefaultTelesalesCompanyInfoApplicationImportProcessingServiceTests()
  11: {
  12:     this.sutCreator = delegate { return Mockery.PartialMock<TrustedDefaultTelesalesCompanyInfoApplicationImportProcessingService>(); };
  13: }
Opublikowane 30 czerwca 2009 12:42 przez rod
Filed under: ,

Komentarze:

30 czerwca 2009 15:59 by dotnetomaniak.pl

# Bezpieczne mockowanie internal methods

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

Komentarze anonimowe wyłączone