Blogg
Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på LinkedIn
Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på LinkedIn
Här följer en kondenserad redogörelse över ett par föredrag jag lyssnade på under sista dagen på ArchConf i förra veckan.
Utifrån premissen att det är klurigt att på ett tillförlitligt sätt integrationstesta ett komplett microserviceslandskap, så berättade Craig Walls från Pivotal om hur man gärna skriver tester på en viss microservice och sedan stubbar ut dess anrop mha ramverk såsom MockMVC. I ännu högre grad vill man rimligen stubba ut anrop till helt externa API:er också.
Craig Walls från Pivotal höll ett antal föredrag relaterade till Spring under konferensen
Spring Cloud Contract är ett försök från Springs håll att “vända lite” på hur man deklarerar och ger stubbar ett visst beteende genom att låta tjänsten som skall anropas vara den som publicerar färdiga stubbar i form av Maven-artefakter. Kallas mer formellt för “Consumer-Driven-Contracts”.
Ansatsen att låta producentsystemet tillhandahålla en stubbe är visserligen inte unik, men oftast så brukar man ju i sina tester själv ansvara för att stubba ut en tjänst som man indirekt beror på snarare än att dra in ett test-beroende där tjänsten själv har tillsett att du skall kunna testa mot den. Om API-producenten därtill täckt in de vanligaste fallen en konsument kan tänkas vilja ha med i sina testfall så kan ju mycket vara vunnet.
Stubbarna och deras beteenden deklareras med en Groovy-DSL, i princip “givet en request som ser ut så här så svara med en response som ser ut så här”. Ett vanligt mönster man kan känna igen från t.ex. nock från NodeJS-världen.
Exempel:
request {
description("User is not old enough to buy beer")
method 'POST'
url '/check'
body(age: value(consumer(regex('[0-1][0-9]'))))
headers {
header 'Content-Type', 'application/json'
}
}
response {
status 200
body( "{"status": "NOT_OK"}")
headers {
header(
'Content-Type', value(consumer('application/json'),producer(regex('application/json.*')))
)
}
}
Ganska tydligt? Notera regexpen som kollar om “age” är 0-19 år - dvs ingen öl.
Det som gör det extra intressant är att när du släpper en ny version av ditt API så kan du samtidigt släppa en kompletterande jar-fil med stubbar för den nya versionen. Föreläsaren drog ett exempel med Facebook. Facebook har massvis med API:er som ändras ofta, där deras API-ändringar kommuniceras mha release notes. Man skulle kunna tänka sig att Facebook säger - “Ok, version 6.0 av annons-API:et släpps om tre månader. Här har ni stubbarna för den versionen så ni kan utveckla och skriva era tester god tid i förväg.”. Det skulle säkerligen kunna underlätta för många utvecklare.
I testkod kör man JUnit eller motsvarande precis som vanligt, man pekar ut maven-artefakten med stubklasser man vill ha med mha @AutoConfigureStubRunner.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureStubRunner(ids = {"com.example:http-server:+:stubs:8080"})
public class BeerApplicationServiceTests {
Personligen tycker jag tanken bakom Spring Cloud Contracts är ganska intressant och definitivt något jag skulle överväga att tillhandahålla om jag jobbade med API:er inom den förvisso något snäva teknikstack (Spring, MockMVC, RestAssuredMockMvc etc) som detta berör. Jag leker också med tanken att applicera detta som ett mönster mellan mina Go-microservices där man enkelt kan dra in källkod från andra microservices i landskapet i form av gock-mockar för HTTP-trafik.
Även här var det Craig Walls som föreläste om dokumentation av REST-api:er. Spring REST docs är en ansats för att generera REST-dokumentation som härstammar från att någon icke namngiven person på Spring-teamet tycker att Swagger är bedrövligt - för mycket annoteringar, svårt att generera vettig dokumentation osv.
Jag lägger mig inte i påståendet i sig, men konstaterar att detta rör sig om “Test-driven documentation”. Ja, ni läste rätt - man skall alltså låta tester generera dokumentationen. Det intressanta i sammanhanget är att man faktiskt kommer få failande test om dokumentationen som genereras inte överensstämmer med den request/response som testet ifråga exekverar. Man behöver en plug-in i sitt bygge, exemplifierat med Maven, som instrumenterar ens kod utifrån den DSL som hör till projektet och som spottar ur sig olika snippets i formatet asciidoc.
Tanken är att man sedan inkluderar dessa snippets (exempelvis utseende för request/response, curl-kommando för att köra request osv), som man kan använda i ett annan asciidoc dokument som är den handskrivna dokumentationen av API:et, där man alltså länkar in en massa snippets. I ett test med MockMvc inkluderar man extra DSL, se från “.andDo(document(…..)” nedan:
this.mockMvc.perform(
post("/notes").contentType(MediaTypes.HAL_JSON).content(
this.objectMapper.writeValueAsString(note)))
.andExpect(
status().isCreated()
)
.andDo(document("notes-create-example",
requestFields(
fieldWithPath("title").description("The title of the note"),
fieldWithPath("body").description("The body of the note"),
fieldWithPath("tags").description("An array of tag resource URIs")
)
)
);
Man chainar på saker mha DSL:en - länkar (HATEOAS-style), beskrivningar osv. Man kan även återanvända saker mellan tester genom att deklarera exempelvis ett uttryck för headers man alltid förväntar sig skall finnas.
Jag ser ett tydligt värde i att koppla doc-genereringen till tester även om det kan kännas lite bakvänt. Om jag förstod det hela rätt är man inte strikt kopplad till just MockMVC utan detta går att göra även med exempelvis RestAssured.
Sedan skall man ha klart för sig att Spring REST docs på intet sätt ersätter Swagger som helhet - Spring REST docs är enbart till för dokumentation och ingenting annat. Dessutom ser jag ett visst jobb med att själv länka in genererade snippets i .adoc-filerna man har som bas för sin API-dokumentation, även om det rimligen bör finnas templates att återanvända.
Om du är nyfiken på mer detaljer så titta gärna på Sylvain Lemoines bloggpost som går genom det hela ordentligt.