spock mocking stubbing
Mocking, stubbing og spionage med Spock:
Parametreret test i Spock Framework blev forklaret detaljeret i dette Series of Training Tutorials on Spock .
hvordan man tilføjer elementer i en array-java
Mocking og Stubbing er en af de mest vigtige byggesten i omfattende enhedstests. Støtte til hån og subbing er som kirsebæret på kagen for en ramme.
For eksisterende rammer som JUnit, JBehave osv. Kommer support til mocks og stubs ikke ud af kassen, hvorfor det kræver en udvikler at bruge tredjepartsbiblioteker som Mockito, PowerMock, EasyMock osv. For at kunne bruge dem i enhedstest.
For at forstå mocks og stubs og deres brugssager kan du tage et kig på vores serie af Mockito tutorial .
I denne vejledning lærer vi mere om de indbyggede Mocking- og Stubbing-funktioner, der er integreret i selve Spock-biblioteket, hvilket igen gør det muligt at bruge den lettere Groovy-syntaks og derved reducerer behovet for at tilføje / inkludere andre 3rdfestbiblioteker.
Du kan altid medtage andre Mocking-rammer i dine tests, da al gyldig Java-kode også er gyldig Groovy-kode.
Hvad du vil lære:
- Ansøgning under test
- Hånende i Spock
- Stubning i Spock
- Spionere i Spock
- Konklusion
- Kildekode til applikationen
- Anbefalet læsning
Ansøgning under test
Lad os først definere en prøve Java-applikation, som vi vil teste ved hjælp af mocks og stubs i Spock-rammen.
Vi arbejder på en StudentGradeCalculator-app, der tager den samlede score fra en abstrakt database for et givet studerende-id og har en simpel logik af karakteropgave afhængigt af værdien af den samlede score. Vi bruger en database-interface, der har få metoder til at hente og opdatere de studerendes scores og karakterer.
Koden til applikationen vil være tilgængelig i sidste afsnit af denne vejledning.
Hånende i Spock
Video-tutorial
I dette afsnit vil vi se, hvordan man initierer og initialiserer Mocks i Spock-rammen, og hvordan man validerer interaktioner på mocken, dvs. validering af opkald til mocks skete i henhold til forventningerne til den testede metode.
Med Mocks behøver du ikke lave mange opsætninger, men du kan validere de interaktioner, der skete med de mock-objekter, der blev leveret til applikationen under test.
Med mocks kan du gøre ting som:
- Hvilke argumenter blev mocks kaldt med?
- Hvad var det samlede antal invokationer osv.?
- At fastslå rækkefølgen af mocks.
Lad os se et simpelt eksempel på StudentGradeCalculator, hvor vi leverer det mocked databaseimplementeringsobjekt og validerer interaktionerne med Mock. Vi prøver at forstå de spottende funktioner med enkle eksempler.
Vær opmærksom på, at alle interaktionsvalideringer skal ske i 'derefter' -blokken ved konvention.
Nedenfor er koden til den metode, der testes (som kaldes i “ hvornår: ”Blok)
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) Validering af interaktionerne med nøjagtige argumenter: Lad os først validere interaktionerne med de nøjagtigt forventede argumenter. Her forventer vi, at de hånede metoder kaldes med de nøjagtige argumenter (pr. Metodeudførelsesflow).
Her ' studentDatabase ”Er Mock af en database-grænseflade, som vi validerer interaktionerne for.
def 'illustrate mocks for interaction verification with arguments'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade('123','C') 1*studentDatabase.getStudentGrade('123') }
Som vist ovenfor validerer vi med de nøjagtige argumenter, så den hånede implementering skal være kaldet med. Enhver ændring af disse argumenter får testen til at mislykkes, og fejlloggen viser den passende årsag.
Lad os prøve at ændre karakteren i ' updateStudentGrade ”Til” A ”i stedet for den faktisk kaldte“ C ”og se, hvilken fejl vi får, når testen udføres.
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 viser en fejl som 'For få påkald', da den ikke kan finde Mock-påkaldelsen med de medfølgende argumenter.
hvad udløser port vs videresendelse af havn
#to) Lad os nu se, hvordan vi validerer Mock-interaktionerne uden at levere de faktiske argumentværdier, dvs. hvad vi er interesseret i, er bare at vide, at mocken blev påberåbt på metoden, men ikke med hvilke argumenter.
Denne type krav er mest almindelige, når man skriver enhedstest for den faktiske produktionskode, da det ikke altid er let at identificere de faktiske argumenter, der i det væsentlige afhænger af kerneforretningslogikken i den testede applikation.
Syntaksen er enkel, du skal bare bruge en understregning “_” til et argument, hvor den faktiske værdi ikke er kendt.
For eksempel, for at kontrollere, om der er en strengværdi, kan du bare nævne “_ Som streng ”I stedet for et argument i testen, og det skal passere for enhver strengværdi (på samme måde for andre primitive såvel som brugerdefinerede datatyper).
Lad os forstå dette med et eksempel
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') }
Et vigtigt punkt at bemærke her er, at du altid kan mixe og matche for hvilke argumenter der er kendt og hvad der ikke er kendt. For eksempel i eksemplet nedenfor validerer vi interaktionen mellem en mock med de faktiske argumenter og den anden med de løse matchere.
# 3) Endelig, lad os se et scenario, hvor vi kan fastslå rækkefølgen af mock-påkald, dvs. hvilken rækkefølge mocks blev kaldt, når testen blev udført.
Det er undertiden vigtigt at validere strømmen af begivenheder, når der er flere samarbejdspartnere / mocks involveret i applikationen under test, og det er nyttigt at forstå og validere, at metoderne blev kaldt i en forudbestemt rækkefølge.
def 'illustrate mocks for validating order'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.getStudentGrade('123') then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) }
Dette kan opnås ved blot at bruge flere “then:” -blokke i rækkefølgen af Mock-sekvensforventninger. Hvis den nævnte rækkefølge ikke opfyldte den faktiske rækkefølge af indkaldelse, kastes en fejl, der beskriver 'Forkert indkaldelsesordre'.
For eksempel hvis jeg ændrer rækkefølgen af ovenstående derefter udsagn, udfører testudførelsen en fejl som vist nedenfor.
Wrong invocation order for: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) (1 invocation) Last invocation: studentDatabase.updateStudentGrade('123', 'C')
Stubning i Spock
Video-tutorial
Vi udforskede alt om Mocking, lad os nu se, hvordan vi definerer stubs på de mocked objekter. Stubning er intet andet end at opsætte foruddefinerede eller dåse svar på Mock-opkaldene for at teste de forskellige strømme / scenarier for den applikation, der testes.
Tænk på det som at programmere en mock til at returnere en foruddefineret værdi, da den blev kaldt. Vi fortsætter med den samme StudentGradeCalculator-app og stopper opkaldene til databasegrænsefladen for at teste forskellige scenarier.
En stub er som en Mock, der på en måde efterligner den virkelige objekts opførsel. Du kan bare kalde det som en programmeret Mock.
Stubbende syntaks
Syntaksen for stubbing er 2 højre skiftoperatører - dvs. “ >> '
For at indstille en stub på ethvert opkald kan du definere det som følger:
StubbedObject.StubbedMethod(//argumentList) >> “Stubbed Response”
Lad os nu forstå de forskellige stubbescenarier med eksempler.
# 1) Stubbing med faktiske parametre: Hvis argumenterne er kendt på forhånd, eller hvis du kun vil indstille stub, når påkaldelsen er med specificerede argumenter, kan denne måde at specificere stubber bruges på.
def 'illustrate stubs with exact matchers'() { given: studentDatabase.getStudentScores('123') >> (20F, 30F, 50F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' }
Her kan du se, at stubben er indstillet med et nøjagtigt argument, dvs. StudentId i dette tilfælde som '123' (for enhver anden værdi påkræves stubben ikke, og der returneres et standardsvar).
# 2) Stubbing med lempelige matchere: Hvis argumenterne ikke er kendt (eller ikke er vigtige), kan vi nævne dem løst, som vi gjorde for mocks, og syntaksen forbliver den samme, dvs. understregningen “_”.
def 'illustrate stubs with loose matchers'() { given: studentDatabase.getStudentScores(_ as String) >> (20F, 30F, 10F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' }
# 3) Lad os se et andet hurtigt eksempel, hvor vi opretter stub til at kaste en undtagelse.
Disse scenarier er meget nyttige til at validere fejlhåndteringslogikken for en applikation, der testes (som i den virkelige verden er det faktisk ikke muligt at generere alle undtagelser, men der kan oprettes en simpel stub til at returnere den undtagelse, vi ønsker, og derefter hævde det i den daværende blok).
program til at tage skærmbilleder på computeren
def 'illustrate stubs with exceptions thrown'() { given: studentDatabase.getStudentScores(_ as String) >> {throw new RuntimeException()} when: studentReportGenerator.calculateStudentGrade('123') then: thrown(RuntimeException.class) }
Spionere i Spock
Spioner er baseret på virkelige objekter dvs. de har brug for interfaceimplementeringen og ikke selve den abstrakte interface. Spioner er magtfulde, og de kan give dig mulighed for at få ægte metoder kaldet til applikationen under test og kontrollere, hvilke argumenter metoderne blev krævet.
Spioner tillader også at definere delvise mocks på de spionerede objektforekomster. dvs. antag, at du vil definere opførslen af nogle metoder på objektet, så kan du og lade resten blive kaldt som virkelige metodeopkald.
Disse er typisk nyttige i en situation, hvor der kan være nogle metoder til grænseflade, der ikke implementeres, og der er få andre, der er fuldt funktionelle. Derfor kan du som udvikler vælge at stoppe de ikke-implementerede og kalde de virkelige implementeringer af de funktionelle metoder.
Det skal bemærkes, at for Spied-objekter, medmindre stubbe er defineret, vil standardadfærden være at kalde den reelle implementering. Når det er sagt, skal spioner ikke kaldes hyppigt, og al scenariedækning kan opnås ved hjælp af mocks og stubs og en kombination af dem.
Lad os se få eksempler på Spies i Spock-rammen ved hjælp af det samme eksempel StudentGradeCalculator (Vi har oprettet en reel implementering af StudentDatabase som er en implementering i hukommelsen ved hjælp af HashMap for at illustrere kalde reelle metoder og returnere data. Koden vil være tilgængelig i sidste afsnit af vejledningen):
# 1) Spionering ved hjælp af en kombination af stub og ægte metodeopkald
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' }
Ovenstående eksempel illustrerer syntaksen til oprettelse af Spy ved hjælp af Spock-rammen. Stubben defineres på selve erklæringstiden.
De spionerede opkald kan også verificeres som illustreret i den daværende blok (med løse argumentmatchere, der kan defineres for alle specifikke argumenter).
# 2) Spionage ved hjælp af alle rigtige metodeopkald
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 ovenstående eksempel, da vi ikke har nævnt nogen stubed opførsel, vil alle opkald gå til den reelle implementering.
Konklusion
I denne vejledning lærte vi alt om de indbyggede teknikker til Mock Stub og Spy ved hjælp af Spock-rammen. Spock gør det let ved at kombinere disse funktioner som en del af selve rammen med en mere læselig groovy syntaks sammen med den mindre kedelpladekode.
Mocks, Stubs og Spies bruges i vid udstrækning til enhedstest for at øge dækningen og teste eller validere kerneforretningslogikken for den applikation, der testes.
Kildekode til applikationen
StudentReportGenerator.java - dette er metoden / applikationen, der testes
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 - Databasegrænseflade
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 af IStudentDatabase.java-grænsefladen
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 vores kommende vejledning vil vi se, hvordan vi integrerer Spock-rammen med andre testrammer og teknologier.
PREV-vejledning | NÆSTE vejledning
Anbefalet læsning
- Skrivningstest med Spock Framework
- Spock Interview-spørgsmål med svar (mest populære)
- Spock til integration og funktionel test med selen
- Datadrevet eller parametreret test med Spock Framework
- Spock Tutorial: Test med Spock og Groovy
- Bedste GRATIS C # tutorialsserie: Den ultimative C # guide til begyndere
- Load Testing med HP LoadRunner-vejledninger
- Funktioner til dato og tid i C ++ med eksempler