Java >> Java tutorial >  >> Java

Fortæl os, hvad du ønsker, og vi vil gøre det til det:forbrugerdrevet kontrakttest til beskeder

For noget tid siden har vi talt om forbrugerdrevet kontrakttestning ud fra perspektivet af REST(ful) web-API'erne generelt og deres projektion i Java (JAX-RS 2.0-specifikation) i særdeleshed. Det ville være rimeligt at sige, at REST stadig dominerer web-API-landskabet, i det mindste med hensyn til offentlige API'er, men skiftet mod mikrotjenester eller/og servicebaseret arkitektur ændrer justeringen af ​​kræfter meget hurtigt. En af sådanne forstyrrende tendenser er meddelelser.

Moderne REST(ful) API'er implementeres for det meste over HTTP 1.1 protokol og er begrænset af dens anmodning/svar kommunikationsstil. HTTP/2 er her for at hjælpe, men alligevel passer ikke alle use case ind i denne kommunikationsmodel. Ofte kunne jobbet udføres asynkront, og det faktum, at det var færdigt, kunne udsendes til interesserede senere. Sådan fungerer de fleste af tingene i det virkelige liv, og brug af beskeder er et perfekt svar på det.

Messaging-området er virkelig fyldt med en forbløffende mængde af beskedmæglere og mæglerløse muligheder. Vi vil ikke tale om det i stedet for at fokusere på et andet vanskeligt emne:budskabet trækker sig sammen. Når producenten udsender besked eller begivenhed, lander den i køen/emnet/kanalen, klar til at blive forbrugt. Det er kommet for at blive et stykke tid. Det er klart, at producenten ved, hvad den udgiver, men hvad med forbrugerne? Hvordan ville de vide, hvad de kunne forvente?

I dette øjeblik ville mange af os skrige:brug skemabaseret serialisering! Og faktisk, Apache Avro, Apache Thrift, Protocol Buffers, Message Pack, … er her for at løse det. I slutningen af ​​dagen bliver sådanne beskeder og begivenheder en del af udbyderkontrakten sammen med REST(ful) web-API'erne, hvis nogen, og skal kommunikeres og udvikles over tid uden at ødelægge forbrugerne. Men … du ville blive overrasket over at vide, hvor mange organisationer, der fandt deres nirvana i JSON og bruger det til at sende beskeder og begivenheder rundt, og kaste sådanne klatter på forbrugerne, intet skema overhovedet! I dette indlæg skal vi se på, hvordan forbrugerdrevet kontrakttestteknik kan hjælpe os i en sådan situation.

Lad os overveje et simpelt system med to tjenester, Ordreservice og Forsendelsesservice . Ordretjenesten udgiver meddelelserne/begivenhederne til meddelelseskøen og Forsendelsesservice fortærer dem derfra.

Siden Ordreservice er implementeret i Java, er begivenhederne kun POJO-klasser, serialiseret i JSON, før de ankommer til meddelelsesmægleren ved hjælp af et af de mange biblioteker derude. Ordrebekræftet er en af ​​sådanne begivenheder.

01020304050607080910 public class OrderConfirmed {      private UUID orderId;      private UUID paymentId;      private BigDecimal amount;      private String street;      private String city;      private String state;      private String zip;      private String country; }

Som det ofte sker, er Forsendelsesservice teamet fik udleveret prøve-JSON-uddraget eller pegede på noget dokumentationsstykke eller reference til Java-klassen, og det er i bund og grund det. Hvordan Forsendelsesservice team kunne sætte gang i integrationen, mens de er sikre på, at deres fortolkning er korrekt, og at beskedens data, de har brug for, ikke pludselig forsvinder? Forbrugerdrevet kontrakttest til undsætning!

Forsendelsesservice team kunne (og bør) starte med at skrive testcaserne mod OrderConfirmed budskab, indlejring af den viden, de har, og vores gamle ven Pact framework (for at være præcis, Pact JVM) er det rigtige værktøj til det. Så hvordan kan testsagen se ud?

010203040506070809101112131415161718192021222232425262728293031323633435> public class OrderConfirmedConsumerTest {      private static final String PROVIDER_ID = "Order Service" ;      private static final String CONSUMER_ID = "Shipment Service" ;           @Rule      public MessagePactProviderRule provider = new MessagePactProviderRule( this );      private byte [] message;      @Pact (provider = PROVIDER_ID, consumer = CONSUMER_ID)      public MessagePact pact(MessagePactBuilder builder) {          return builder              .given( "default" )              .expectsToReceive( "an Order confirmation message" )              .withMetadata(Map.of( "Content-Type" , "application/json" ))              .withContent( new PactDslJsonBody()                  .uuid( "orderId" )                  .uuid( "paymentId" )                  .decimalType( "amount" )                  .stringType( "street" )                  .stringType( "city" )                  .stringType( "state" )                  .stringType( "zip" )                  .stringType( "country" ))              .toPact();      }      @Test      @PactVerification (PROVIDER_ID)      public void test() throws Exception {          Assert.assertNotNull(message);      }      public void setMessage( byte [] messageContents) {          message = messageContents;      } }

Det er usædvanligt enkelt og ligetil, ingen kedelplade tilføjet. Testcasen er designet lige fra JSON-repræsentationen af ​​OrderConfirmed besked. Men vi er kun halvvejs igennem, Forsendelsesservice team bør på en eller anden måde bidrage med deres forventninger tilbage til Ordreservice så producenten ville holde styr på, hvem og hvordan der bruger OrderConfirmed besked. Pagtens testsele tager sig af det ved at generere pagtfilerne (sæt af aftaler eller pagter) ud af de enkelte JUnit-testsager til 'målet/pagterne' folder. Nedenfor er et eksempel på den genererede Shipment Service-Order Service.json pact-fil efter at have kørt OrderConfirmedConsumerTest test suite.

001002003004005006007008009010011012013014015016017018019020021022023024025026027028029030031032033034035036037038039040041042043044045046047048049050051052053054055056057058059060061062063064065066067068069070071072073074075076077078079080081082083084085086087088089090091092093094095096097098099100101102103104105106107108109 {    "consumer" : {      "name" : "Shipment Service"    },    "provider" : {      "name" : "Order Service"    },    "messages" : [      {        "description" : "an Order confirmation message" ,        "metaData" : {          "contentType" : "application/json"        },        "contents" : {          "zip" : "string" ,          "country" : "string" ,          "amount" : 100 ,          "orderId" : "e2490de5-5bd3-43d5-b7c4-526e33f71304" ,          "city" : "string" ,          "paymentId" : "e2490de5-5bd3-43d5-b7c4-526e33f71304" ,          "street" : "string" ,          "state" : "string"        },        "providerStates" : [          {            "name" : "default"          }        ],        "matchingRules" : {          "body" : {            "$.orderId" : {              "matchers" : [                {                  "match" : "regex" ,                  "regex" : "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"                }              ],              "combine" : "AND"            },            "$.paymentId" : {              "matchers" : [                {                  "match" : "regex" ,                  "regex" : "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"                }              ],              "combine" : "AND"            },            "$.amount" : {              "matchers" : [                {                  "match" : "decimal"                }              ],              "combine" : "AND"            },            "$.street" : {              "matchers" : [                {                  "match" : "type"                }              ],              "combine" : "AND"            },            "$.city" : {              "matchers" : [                {                  "match" : "type"                }              ],              "combine" : "AND"            },            "$.state" : {              "matchers" : [                {                  "match" : "type"                }              ],              "combine" : "AND"            },            "$.zip" : {              "matchers" : [                {                  "match" : "type"                }              ],              "combine" : "AND"            },            "$.country" : {              "matchers" : [                {                  "match" : "type"                }              ],              "combine" : "AND"            }          }        }      }    ],    "metadata" : {      "pactSpecification" : {        "version" : "3.0.0"      },      "pact-jvm" : {        "version" : "4.0.2"      }    } }

Det næste trin for Forsendelsesservice teamet skal dele denne pagtfil med Ordreservice team, så disse fyre kunne køre pagt-verifikationer på udbydersiden som en del af deres testsuiter.

01020304050607080910111213141516171819202122232425296225t@RunWith (PactRunner. class ) @Provider (OrderServicePactsTest.PROVIDER_ID) @PactFolder ( "pacts" ) public class OrderServicePactsTest {      public static final String PROVIDER_ID = "Order Service" ;      @TestTarget      public final Target target = new AmqpTarget();      private ObjectMapper objectMapper;           @Before      public void setUp() {          objectMapper = new ObjectMapper();      }      @State ( "default" )      public void toDefaultState() {      }           @PactVerifyProvider ( "an Order confirmation message" )      public String verifyOrderConfirmed() throws JsonProcessingException {          final OrderConfirmed order = new OrderConfirmed();                   order.setOrderId(UUID.randomUUID());          order.setPaymentId(UUID.randomUUID());          order.setAmount( new BigDecimal( "102.33" ));          order.setStreet( "1203 Westmisnter Blvrd" );          order.setCity( "Westminster" );          order.setCountry( "USA" );          order.setState( "MI" );          order.setZip( "92239" );          return objectMapper.writeValueAsString(order);      } }

Testselen vælger alle pagtfilerne fra @PactFolder og kør testene mod @TestTarget , i dette tilfælde forbinder vi AmqpTarget , leveret ud af æsken, men du kan nemt tilslutte dit eget specifikke mål.

Og det er i bund og grund det! Forbrugeren (Forsendelsesservice ) få deres forventninger udtrykt i testcaserne og delt med producenten (Ordreservice ) i form af pagtfilerne. Producenterne har egne tests for at sikre, at dens model matcher forbrugernes opfattelse. Begge sider kunne fortsætte med at udvikle sig uafhængigt og stole på hinanden, så vidt pagterne ikke bliver fordømt (forhåbentlig aldrig).

For at være retfærdig er Pact ikke det eneste valg til at lave forbrugerdrevet kontrakttest, i det kommende indlæg (allerede i arbejde) vil vi tale om endnu en fremragende mulighed, Spring Cloud Contract.

Hvad angår i dag, er de komplette projektkilder tilgængelige på Github.

Java tag