spock mocking stubbing
Spott, stubbing och spionering med Spock:
Parameteriserad testning i Spock Framework förklarades i detalj i detta Serie med utbildningshandledning om Spock .
Mocking och Stubbing är en av de viktigaste byggstenarna i omfattande enhetstester. Stöd för hån och subbing är som körsbäret på kakan för ett ramverk.
För befintliga ramverk som JUnit, JBehave, etc. kommer stödet för mocks och stubs inte ur lådan, det kräver därför en utvecklare att använda tredjepartsbibliotek som Mockito, PowerMock, EasyMock, etc. för att kunna använda dem i enhetstester.
För att förstå mocks och stubs och deras användningsfall kan du ta en titt på vår serie av Mockito-handledning .
I den här handledningen kommer vi att lära oss mer om de inbyggda Mocking- och Stubbing-funktionerna integrerade i själva Spock-biblioteket, vilket i sin tur gör det möjligt att använda den enklare Groovy-syntaxen och därmed minskar behovet av att lägga till / inkludera andra 3rdpartibibliotek.
Du kan alltid inkludera andra Mocking-ramar i dina tester, eftersom all giltig Java-kod också är giltig Groovy-kod.
Vad du kommer att lära dig:
- Ansökan under test
- Hånfull i Spock
- Stubbing i Spock
- Spionerar i Spock
- Slutsats
- Källkod för applikationen
- Rekommenderad läsning
Ansökan under test
Låt oss först definiera ett Java-applikationsexempel, som vi kommer att testa med mocks och stubs i Spock-ramverket.
Vi kommer att arbeta med en StudentGradeCalculator-app som tar den totala poängen från en abstrakt databas för ett givet student-ID och har en enkel logik för betygsuppgift beroende på värdet av totalpoängen. Vi använder ett databasgränssnitt som har få metoder för att hämta och uppdatera studenternas poäng och betyg.
Koden för applikationen kommer att finnas tillgänglig i det sista avsnittet i denna handledning.
Hånfull i Spock
Video-handledning
I det här avsnittet kommer vi att se hur man kan initiera och initialisera Mocks i Spock-ramverket och hur man validerar interaktioner på mocken, dvs. valideringen av samtalen till mockarna skedde enligt förväntningarna på metoden som testades.
Med Mocks behöver du inte göra många inställningar, men du kan validera de interaktioner som hände med de mock-objekt som levererades till applikationen som testades.
Med mocks kan du göra saker som:
- Vilka argument kallades hånarna med?
- Vad var det totala antalet anrop etc?
- Bestämma ordningen på hånar.
Låt oss se ett enkelt exempel på StudentGradeCalculator, där vi levererar det hånade databasimplementeringsobjektet och validerar interaktionerna med Mock. Vi kommer att försöka förstå spottfunktionerna med enkla exempel.
Observera att alla interaktionsvalideringar ska ske i 'då' -blocket enligt konvention.
Nedan följer koden för metoden som testas (som kommer att kallas i ” när: ”Block)
public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; }
# 1) Validera interaktioner med exakta argument: Låt oss först validera interaktioner med exakt förväntade argument. Här förväntar vi oss att de hånade metoderna anropas med exakta argument (enligt metodens exekveringsflöde).
Här “ studentDatabas ”Är Mock av ett databasgränssnitt för vilket vi validerar interaktionerna.
def 'illustrate mocks for interaction verification with arguments'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade('123','C') 1*studentDatabase.getStudentGrade('123') }
Som visas ovan validerar vi med de exakta argumenten, så att den hånade implementeringen måste ha anropats med. Eventuella ändringar av dessa argument kommer att få testet att misslyckas och felloggen visar lämplig orsak.
Låt oss försöka ändra betyget i ' updateStudentGrade ”Till” A ”istället för den faktiskt kallade” C ”och se vilket fel vi får när testet utförs.
Too few invocations for: 1*studentDatabase.updateStudentGrade('123','A') (0 invocations) Unmatched invocations (ordered by similarity): 1 * studentDatabase.updateStudentGrade('123', 'C') 1 * studentDatabase.getStudentScores('123')
Det kommer att visa ett fel som 'För få anrop' eftersom det inte kan hitta Mock-anrop med de medföljande argumenten.
lägg till en array i java
#två) Låt oss nu se hur man validerar Mock-interaktioner utan att tillhandahålla de faktiska argumentvärdena, det vill säga det vi är intresserade av är bara att veta att mocken åberopades på metoden men inte med vilka argument.
Denna typ av krav är vanligast när man skriver enhetstester för den faktiska produktionskoden, eftersom det inte alltid är lätt att identifiera de faktiska argumenten som i huvudsak beror på kärnverksamhetslogiken för den applikation som testas.
Syntaxen är enkel, du behöver bara använda en understrykning “_” för ett argument där det verkliga värdet inte är känt.
Till exempel, för att kontrollera om något strängvärde kan du bara nämna “_ Som sträng ”I stället för ett argument i testet och det ska passera för alla strängvärden (på samma sätt för andra primitiva såväl som anpassade datatyper).
Låt oss förstå detta med ett exempel
def 'illustrate mocks for interaction verification with generic matchers'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) 1*studentDatabase.getStudentGrade('123') }
En viktig punkt att notera här är att du alltid kan mixa och matcha för vilka argument som är kända och vad som inte är kända. I exemplet nedan validerar vi till exempel interaktionen mellan en mock med de faktiska argumenten och den andra med de lösa matcharna.
# 3) Slutligen, låt oss se ett scenario där vi kan fastställa ordningen på mock-anrop, dvs vilken ordning mocks kallades när testet utförs.
Ibland är det viktigt att validera flödet av händelser när det finns flera samarbetspartners / mocks involverade i applikationen som testas och det är användbart att förstå och validera att metoderna anropades i en förutbestämd sekvens.
def 'illustrate mocks for validating order'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.getStudentGrade('123') then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) }
Detta kan uppnås genom att helt enkelt använda flera ”then:” -block i ordningen efter Mock-sekvensförväntningar. Om den nämnda sekvensen inte uppfyllde den faktiska beställningsordningen, kastas ett fel som beskriver 'Fel anropsordning'.
Till exempel om jag ändrar ordningen på ovanstående sedan uttalanden kommer testkörningen att kasta ett fel som visas nedan.
Wrong invocation order for: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) (1 invocation) Last invocation: studentDatabase.updateStudentGrade('123', 'C')
Stubbing i Spock
Video-handledning
Vi utforskade allt om Mocking, nu ska vi se hur man definierar Stubs på de hånade objekten. Stubbing är inget annat än att ställa in fördefinierade eller konserverade svar på Mock-anropen för att testa olika flöden / scenarier för den applikation som testas.
Tänk på det som att programmera en mock för att returnera ett fördefinierat värde när det anropades. Vi fortsätter med samma StudentGradeCalculator-app och stubbar databasgränssnittsanropen för att testa olika scenarier.
En Stub är som en Mock som på ett sätt efterliknar beteendet hos det verkliga objektet. Du kan helt enkelt kalla det som en programmerad Mock.
Stubbande syntax
Syntaxen för stubbning är två högerskiftoperatörer - dvs “ >> '
För att ställa in en stub på ett samtal kan du definiera det enligt följande:
StubbedObject.StubbedMethod(//argumentList) >> “Stubbed Response”
Låt oss nu förstå de olika stubbscenarierna med exempel.
# 1) Stubbing med faktiska parametrar: Om argumenten är kända i förväg eller om du bara vill ställa in stubben när anropet är med angivna argument kan detta sätt att specificera stubbar användas.
def 'illustrate stubs with exact matchers'() { given: studentDatabase.getStudentScores('123') >> (20F, 30F, 50F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' }
Här kan du se att stubben har ställts in med ett exakt argument, dvs StudentId i detta fall som “123” (för något annat värde kommer stubben inte att åberopas och det kommer att returneras ett standardsvar).
# 2) Stubbing med mjuka matchare: Om argumenten inte är kända (eller inte är viktiga), kan vi nämna dem löst som vi gjorde för hånar och syntaksen förblir densamma, dvs. understrykningen “_”.
def 'illustrate stubs with loose matchers'() { given: studentDatabase.getStudentScores(_ as String) >> (20F, 30F, 10F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' }
# 3) Låt oss se ett annat snabbt exempel där vi ställer in stub för att kasta ett undantag.
Dessa scenarier är mycket användbara för att validera felhanteringslogiken för en applikation som testas (som i den verkliga världen är det faktiskt inte möjligt att generera alla undantag men en enkel stubbe kan ställas in för att returnera vilket undantag vi vill ha och sedan hävda det i dåvarande blocket).
bästa gratis YouTube-videohämtaren för Windows 10
def 'illustrate stubs with exceptions thrown'() { given: studentDatabase.getStudentScores(_ as String) >> {throw new RuntimeException()} when: studentReportGenerator.calculateStudentGrade('123') then: thrown(RuntimeException.class) }
Spionerar i Spock
Spioner är baserade på verkliga föremål dvs. de behöver gränssnittsimplementeringen och inte själva det abstrakta gränssnittet. Spioner är kraftfulla och de kan låta dig få riktiga metoder som krävs för applikationen som testas och verifiera vilka argument metoderna krävdes.
Spioner tillåter också att definiera partiella mocks på spionerade objektinstanser. antar att du vill definiera beteendet hos vissa metoder på objektet, då kan du och låta resten kallas som riktiga metodsamtal.
Dessa är vanligtvis användbara i en situation där det kan finnas vissa gränssnittsmetoder som inte implementeras och det finns få andra som är helt funktionella. Därför kan du som utvecklare välja att stoppa de icke-implementerade och kalla de verkliga implementeringarna av de funktionella metoderna.
Det bör noteras att för Spied-objekt, om inte stubbar definieras, är standardbeteendet att anropa den verkliga implementeringen. Med detta sagt bör spioner inte kallas ofta och hela scenariotäckningen kan uppnås med mocks och stubs och en kombination av dem.
Låt oss se några exempel som använder Spies i Spock-ramverket med samma exempel på StudentGradeCalculator (Vi har skapat en verklig implementering av Studentdatabas vilket är en implementering i minnet med HashMap för att illustrera anropa riktiga metoder och returnera data. Koden kommer att finnas tillgänglig i det sista avsnittet i handledningen):
# 1) Spionera med en kombination av stub och riktiga metodsamtal
def 'illustrate spies'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' 1*spiedStudentDatabase.getStudentGrade(_ as String) >> 'A' }
Ovanstående exempel illustrerar syntaxen för att skapa Spy med hjälp av Spock-ramverket. Stubben definieras vid själva deklarationstiden.
Dessutom kan de spionerade samtalen verifieras som illustreras i det dåvarande blocket (med lösa argumentmatchare som kan definieras för alla specifika argument).
# 2) Spionera med alla riktiga metodsamtal
def 'illustrate spies with real method call'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' 1*spiedStudentDatabase.getStudentGrade('123') }
I ovanstående exempel, eftersom vi inte har nämnt något stubbat beteende, kommer alla samtal att gå till den verkliga implementeringen.
Slutsats
I den här handledningen lärde vi oss allt om de inbyggda teknikerna för att Mock Stub och Spy med hjälp av Spock-ramverket. Spock gör det enkelt genom att kombinera dessa funktioner som en del av själva ramverket med en mer läsbar groovy syntax tillsammans med den mindre pannkodskoden.
Mocks, Stubs och Spies används i stor utsträckning vid enhetstestning för att öka täckningen och testa eller validera kärnverksamhetslogiken för den applikation som testas.
Källkod för applikationen
StudentReportGenerator.java - detta är metoden / applikationen som testas
package app.studentScores; import java.util.List; public class StudentReportGenerator { public IStudentDatabase studentDatabase; public StudentReportGenerator(IStudentDatabase studentDatabase) { this.studentDatabase = studentDatabase; } public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; } }
IStudentDatabase.java - Databasgränssnitt
package app.studentScores; import java.util.List; public interface IStudentDatabase { List getStudentScores(String studentId); void updateStudentGrade(String studentId, String grade); String getStudentGrade(String studentId); }
StudentDatabase.java - InMemory-implementering av IStudentDatabase.java-gränssnittet
package app.studentScores; import java.util.*; public class StudentDatabase implements IStudentDatabase { private Map scoreMap; private Map gradeMap; public StudentDatabase() { this.scoreMap = new HashMap(); this.gradeMap = new HashMap(); scoreMap.put('123', Arrays.asList(40F, 30F, 30F)); scoreMap.put('456', Arrays.asList(10F, 10F, 30F)); gradeMap.put('123', 'C'); gradeMap.put('456', 'A'); } @Override public List getStudentScores(String studentId) { return scoreMap.get(studentId); } @Override public void updateStudentGrade(String studentId, String grade) { gradeMap.put(studentId,grade); } @Override public String getStudentGrade(String studentId) { return gradeMap.get(studentId); } }
I vår kommande handledning kommer vi att se hur man integrerar Spock-ramverket med andra testramar och tekniker.
PREV-handledning | NÄSTA självstudie
Rekommenderad läsning
- Skrivenhetstester med Spock Framework
- Spock intervjufrågor med svar (mest populära)
- Spock för integration och funktionstestning med selen
- Datadriven eller parametrerad testning med Spock Framework
- Spock Tutorial: Testing With Spock And Groovy
- Bästa GRATIS C # -handledningsserie: Den ultimata C # -guiden för nybörjare
- Lasttestning med HP LoadRunner-handledning
- Funktioner för datum och tid i C ++ med exempel