Sådan testes en sort boks en Go-app med RSpec

Automatiseret test er alt det rasende i webudvikling i disse dage og foregår i hele branchen. En velskrevet test reducerer dramatisk risikoen for, at et program ved et uheld brydes, når du tilføjer nye funktioner eller løser fejl. Når du har et komplekst system, der er bygget af flere komponenter, der interagerer med hinanden, er det utroligt svært at teste, hvordan hver komponent interagerer med andre komponenter.

Lad os se på, hvordan man skriver gode automatiske test til udvikling af komponenter i Go, og hvordan man gør det ved hjælp af RSpec-biblioteket i Ruby on Rails.

Tilføjelse Gå til vores projekts tech stack

Et af de projekter, som jeg arbejder på hos min virksomhed, eTeam, kan opdeles i et admin-panel, brugerpanel, rapportgenerator og anmodningsprocessor, der håndterer anmodninger fra forskellige tjenester integreret i applikationen.

Den del af projektet, der behandler anmodninger, er den vigtigste, derfor har vi brug for at maksimere dets pålidelighed og tilgængelighed.

Som en del af en monolitisk applikation er der en stor risiko for, at en fejl påvirker anmodningsprocessoren, selv når der er ændringer i kode i dele af appen, der ikke er relateret til den. Ligeledes er der risiko for, at anmodningsprocessoren går ned, når andre komponenter er under en tung belastning. Antallet af Ngnix-medarbejdere til appen er begrænset, hvilket kan forårsage problemer, når belastningen øges. For eksempel, når et antal ressourceintensive sider åbnes på én gang i admin-panelet, bremser processoren ned eller nedbrud endda hele appen.

Disse risici såvel som modenheden for det pågældende system - vi behøvede ikke at foretage større ændringer i måneder - gjorde denne app til en ideel kandidat til at oprette en separat tjeneste til at håndtere anmodningshåndtering.

Vi besluttede at skrive den separate service i Go, der delte databaseadgangen med Rails-applikationen, som forblev ansvarlig for ændringer i tabellstrukturen. Med kun to applikationer fungerer et sådant skema med en delt database fint. Sådan så det ud:

Vi skrev og implementerede tjenesten i en separat Rails-forekomst. På denne måde var der ingen grund til at bekymre sig om, at anmodningsprocessoren ville blive påvirket, når Rails-appen blev installeret. Tjenesten accepterer direkte HTTP-anmodninger uden Ngnix og bruger ikke meget hukommelse. Du kan kalde det en minimalistisk app!

Problemet med enhedstest i Go

Vi oprettede enhedstest til Go-applikationen, hvor alle databaseanmodninger blev hånet. Ud over andre argumenter for denne løsning var den vigtigste Rails-applikation ansvarlig for databasestrukturen, og Go-applikationen havde således ikke oplysningerne til at oprette en testdatabase. Halvdelen af ​​behandlingen var forretningslogik, mens den anden halvdel var databaseforespørgsler, som alle blev hånede.

Spottede objekter er meget mindre læsbare i Go end i Ruby. Hver gang der blev tilføjet nye funktioner til læsning af data fra databasen, måtte vi tilføje hånede objekter under mange mislykkede tests, der tidligere havde fungeret. I sidste ende viste sådanne enhedstests ikke meget effektive og var ekstremt skrøbelige.

Vores løsning

For at kompensere for disse ulemper besluttede vi at dække tjenesten med funktionelle test i applikationen Rails og teste tjenesten i Go som en sort boks. Hvidboks-testning fungerer under ingen omstændigheder, da det var umuligt at bruge Ruby til at komme ind i tjenesten og se, om der kaldes en metode.

Det betyder også, at anmodninger, der blev sendt gennem testtjenesten, også var umulige at bespotte, og derfor havde vi brug for en anden applikation til styring og skrivning af disse test. Noget som RequestBin ville fungere, men det måtte arbejde lokalt. Vi havde allerede skrevet et værktøj, der gjorde det trick, så vi besluttede at prøve at bruge det.

Dette var den resulterende opsætning:

  1. RSpec kompilerer og kører Go-binæren med den konfiguration, hvor adgang til testdatabasen er specificeret sammen med en bestemt port til modtagelse af HTTP-anmodninger, dvs. 8082.
  2. Det kører også værktøjet, der registrerer HTTP-anmodninger, der kommer til port 8083.
  3. Vi skriver regelmæssige tests i RSpec. Dette opretter de nødvendige data i databasen og sender en anmodning til localhost: 8082, som om det var en ekstern tjeneste, såsom HTTParty.
  4. Vi analyserer svaret, kontrollerer ændringer i databasen, modtager en liste over anmodninger, der blev optaget af RequestBin-substituttet og kontrollerer dem.

Detaljer om gennemførelsen

Sådan implementerede vi dette. Lad os som en demonstration ringe til testtjenesten TheService og oprette en indpakning:

Det er værd at nævne, at autoloading-filer skal konfigureres i supportmappen, når du bruger RSpec:

Dir [Rails.root.join ('spec / support / ** / *. Rb')]. Hver {| f | kræver f}

Startmetoden:

  • Læser de konfigurationsoplysninger, der er nødvendige for at starte TheService. Disse oplysninger kan variere mellem forskellige udviklere og er derfor udelukket fra Git. Konfigurationen indeholder de nødvendige indstillinger til start af programmet. Alle disse forskellige konfigurationer er ét sted, så du ikke behøver at oprette unødvendige filer.
  • Kompilerer og kører gennem go run
  • Afstemning hvert sekund og venter, indtil TheService er klar til at acceptere anmodninger.
  • Registrerer identifikatoren for hver proces for ikke at gentage noget og have evnen til at stoppe en proces.

Selve konfigurationen:

Metoden “stop” stopper simpelthen processen. Der er dog en gotcha! Ruby kører en “go run” -kommando, som kompilerer TheService og starter en binær i en børneproces med et ukendt ID. Hvis vi bare stopper processen, der kører i Ruby, stopper børneprocessen ikke automatisk, og porten forbliver i brug. At stoppe TheService skal derfor gennemgå Process Group ID:

Derefter forbereder vi “shared_context”, hvor vi definerer standardvariablerne, starter TheService, hvis den ikke allerede er lanceret og slukker VCR midlertidigt, da VCR ville se, hvad vi laver som en ekstern serviceanmodning, men vi ønsker ikke VCR til at hån forespørgsler på dette tidspunkt:

Og nu kan vi se på at skrive specifikationerne selv:

The Service kan fremsætte HTTP-anmodninger til eksterne tjenester. Vi kan konfigurere det til at omdirigere anmodninger til det lokale værktøj, der logger dem. Til dette værktøj er der også en indpakning til at starte og stoppe det, der ligner 'TheServiceControl', bortset fra at dette værktøj bare kan startes som et binært uden kompilering.

Yderligere højdepunkter

Go-applikationen blev skrevet, så alle logfiler og fejlfindingsoplysninger ville blive sendt til STDOUT. Ved produktion sendes denne output til en fil. Ved start fra RSpec vises loggen i konsollen, hvilket virkelig hjælper med fejlfinding.

Hvis du specifikt kører de specifikationer, der ikke har brug for TheService, starter den ikke.

For ikke at spilde tid på at starte TheService hver gang, når en spec ændres, kan du under udviklingsprocessen starte TheService manuelt i terminalen og simpelthen ikke slukke for den. Når det er nødvendigt, kan du endda starte det i en IDE-debugging-tilstand. Derefter forbereder specs alt, send anmodningen til tjenesten, det stopper, og du kan let debug det. Dette gør TDD-fremgangsmåden virkelig praktisk.

Konklusion

Vi har brugt denne opsætning i cirka et år nu og har ikke oplevet nogen fejl med det. Specifikationerne kommer langt mere læsbare end enhedsprøvning i Go, og de er ikke afhængige af at kende den interne struktur i tjenesten. Hvis vi af en eller anden grund har brug for at omskrive tjenesten på et andet sprog, behøver vi ikke ændre specifikationerne. Kun indpakningerne, der bruges til at starte testtjenesten med en anden kommando, skulle omskrives.