UPDATE: I stand corrected.  Belært af Mark og Rasmus (se kommentarer nedenfor) har jeg opdateret mit kodeeksempel med nyeste Rhino Mocks funktionalitet.  Tak for det.

Jeg ser flere og flere referencer til AAA på nettet (se f.eks. RasmusKL og Roy Osherove).  Jeg har indtil nu været ganske tilfreds med Record/Playback metoden for Rhino Mocks, men jeg kan sagtens se pointen bag AAA-stilen, så det er tid til at skifte vane.

Jeg har indtil fornylig brugt Rhino Mocks v3.4.  Det viser sig, at der lidt udfordringer i at følge AAA med den måde, Rhino Mocks 3.4 fungerer.  Problemet ligger i måden, man sætter expectations op.

Antag vi har følgende interface ITest, som vi ønsker at mocke:

public interface ITest
{
    int DoStuff(int a);
}

 

Vi kan sætte expectations op på følgende måde:

ITest testMock = mocks.CreateMock<ITest>();
Expect.Call(testMock.DoStuff(11)).Return(42);

Expectations bør ligge i Arrange-blokken, men vi forventer, at DoStuff bliver kaldt med værdien 11, hvilket faktisk er en Assert.  Ønsker man at sætte yderliger constraints op på parametre, skal det også ske i Record fasen, som hører under Arrange-blokken, men constraints bør ligge i Assert-blokken.

Rhino mocks v3.5 giver en helt ny API, som gør, at vi nu kan skrive vores test på en helt anden måde (tak til Rasmus):

// Arrange
ITest testMock = MockRepository.GenerateStub<ITest>();
testMock.Stub(x =>; x.DoStuff(Arg<int>.Is.Anything)).Return(42);

// Act
testMock.DoStuff(11);

// Assert
testMock.AssertWasCalled(x => x.DoStuff(Arg.Is(11)));

Testen er nu delt op i de tre AAA-blokke som ønsket.

Kommentarer

Mark Seemann Denmark siger:

8. juni 2009 21:50

Det kan godt være at man på den måde mere analt overholder AAA (eller Four-Phase Test, som jeg normalt foretrækker at tale om) på den måde, men du ofrer type safety, og dermed test maintainability, og det er efter min mening et rigtigt dårligt bytte.

Opfat kaldet til VerifyAllExpectations som din Verification/Assertion, og alt andet som Fixture Setup/Arrange.

Det er alligvel kun hvis du har brug for at foretage Behavior Verification at du har brug for en Mock (StrictMock i dit eksempel). I langt de fleste tilfælde er en Stub alt du har brug for, og så kan du benytte løsere argument constraints.

Rasmus Kromann-Larsen Denmark siger:

8. juni 2009 22:31

Hey igen,

jamen så vil jeg da også lige komme med lidt kommentarer på din post. Nu har jeg som sagt ikke fået rodet så meget med Rhino.Mocks 3.5 AAA syntax, da jeg normalt arbejder med C# 2.0, men jeg er ret sikker på at du ikke har behov for replay og verify kaldene mere når du bruger AAA?

Jeg har tilladt mig at skrive Moq versionen af din test, bare som eksempel, synes det er et lækkert framework fordi man kan udtrykke det meget konsist uden for meget urelateret pladder:

// Arrange
ITest testMock = new Mock<ITest>();
testMock.Setup(x => x.DoStuff(It.IsAny<int>())).Returns(42);

// Act
testMock.DoStuff(11);

// Assert
testMock.Verify(x => x.DoStuff(It.Is<int>(i => i == 11)));

Bemærk især It.Is syntaxen - og den forskellige måde den er brugt i setup (Arrange) og verify (Assert).

Er der nogen speciel grund til at du bruger strict mocks? Smile

Rasmus Kromann-Larsen Denmark siger:

8. juni 2009 22:31

Hov,

Act i min test skal være testMock.Object.DoStuff(11) Smile

t4rzsan Denmark siger:

9. juni 2009 01:17

@Mark:
Jeg er enig i, GetArgumentsForCallsMadeOn ikke er for køn.  Returværdien er IList<object[]>.  Mit eksempel er meget tyndt, men jeg bruger ofte mocks i stedet for stubs for at kunne verificere.

@Rasmus:
Jeg kender intet til Moq, men jeg synes faktisk, at dit Moq eksempel er pænere end mit Rhino mocks.  Is.It finder jeg pænere end Assert.AreEqual(11, arguments[0][0]).  Mine kommentarer i din blog drejer sig netop om Rhino 3.4, hvor man ikke har GetArgumentsForCallsMadeOn, men jeg var lidt dårlig til at forklare mig, hvilket forhåbentligt er lykkedes lidt bedre med denne blog entry Smile  Jeg bruger StrictMock, da CreateMock er obsolete.  Jeg kunne også have brugt en stub (som Mark nævner), hvilket ville have overflødiggjort VerifyAllExpectations.  ReplayAll er nødvendig for at skifte tilstand for mocks.

Mark Seemann Denmark siger:

9. juni 2009 09:28

Med Rhino Mocks benytter jeg stort set kun det ny(ere) lambda-baserede API - både for Mocks og Stubs. Der er ikke noget record/replay involveret længere. Her er en blog post med et eksempel nederst: http://blog.ploeh.dk/2009/05/28/DelegatesAreAnonymousInterfaces.aspx.

Rasmus Kromann-Larsen Denmark siger:

9. juni 2009 12:31

Faktisk så har du en bedre mulighed som minder om It.Is i Rhino.Mocks 3.5.

Se: http://ayende.com/Wiki/Rhino+Mocks+3.5.ashx#Inlineconstraints

Dette skal så bruges sammen med AssertWasCalled og AssertWasNotCalled (nye extension metoder)

Det ville også løse dit problem med type-stærkhed som Mark nævnte.

Ud fra dokumentationen ser det ud til at du kan skrive:

// Assert
testMock.AssertWasCalled(x => x.DoStuff(Arg<int>.Is(11)));


Min kommentar mht. strict mocks var at jeg stort set er gået helt væk fra at bruge dem, da jeg gerne vil teste 1 ting af gangen, så jeg laver eksplicitte forventninger om hvad der er vigtigt i denne test - og bruger så en loose mock i stedet (DynamicMock i RM). Dette gør at alle mine tests ikke behøver slavisk expecte alt hvad der bliver kaldt, men kan nøjes med at lave expectations for præcis de kald jeg er interesseret i blev kaldt. Dertil bruger jeg så også negative expectations med AssertWasNotCalled når jeg er interesseret i at noget ikke bliver kaldt.

På den måde bliver mine tests mere eksplicitte og faktisk også mere robuste. Der er selvfølgelig stadig nogle få steder hvor jeg bruger strict mocks, men ofte ikke.








Rasmus Kromann-Larsen Denmark siger:

9. juni 2009 12:39

Skrev lige hele din test om så den bruger alt det nye smarte fra Rhino Mocks 3.5:

// Arrange
ITest testMock = MockRepository.GenerateStubk<ITest>();
testMock.Stub(x => x.DoStuff(Arg<int>.Is.Anything)).Return(42);

// Act
testMock.DoStuff(11);

// Assert
testmock.AssertWasCalled(x => x.DoStuff(Arg<int>.Is(11)));

Ligner Moq en hel del Smile

Mark Seemann Denmark siger:

9. juni 2009 13:42

Det Rasmus skriver er meget fornuftigt. I Roy Osherove's nys udkomne bog vil man også kunne læse netop det råd, at enhver test højst bør have en enkelt Mock i spil - de resterende Test Doubles kan så være Stubs (jeg benytter xUnit Test Patterns-terminologien).

Rhino Mocks giver os gudskelov mulighed for at benytte både Mocks og Stubs - jeg formoder at Moq kan noget lignende?

t4rzsan Denmark siger:

9. juni 2009 14:44

Nu begynder det at ligne noget.  Jeg kan godt lide AssertWasCalled og Arg<> funktionaliteten.

Rasmus Kromann-Larsen Denmark siger:

9. juni 2009 15:36

@Mark

Yep. De to er meget lignende. Jeg bruger begge, men har mest lavet AAA med Moq. Det jeg godt kan lide ved Moq er at det er designet efter C# 3.5 hvor jeg synes at Rhino Mocks er tynget lidt ned af alt sin bagudkompabilitet - man kan skrive tingene på alt for mange måder med Rhino efterhånden. Ville ønske at de lavede et light-weight Rhino Mocks som kun indeholdt det nye.

Men deres AAA syntax er stort set identisk.

Mark Seemann Denmark siger:

9. juni 2009 18:49

@Rasmus

Det er en god pointe vedr. Rhino Mocks - gad vide hvad Ayende ville sige hvis han havde hørt at hans mest berømte 'produkt'er tynget lidt ned af alt sin bagudkompabilitet Smile

Kommentarerne er lukkede