Prova de caixa negra d'una aplicació Go amb RSpec

Les proves automatitzades estan de moda en el desenvolupament web actualment i s’utilitzen a tota la indústria. Una prova ben escrita redueix el risc de danyar accidentalment una aplicació a mesura que afegiu noves funcions o solucioneu errors. Quan teniu un sistema complex format per múltiples components que interactuen entre si, és increïblement difícil provar com interactuen cada component amb altres components.

Vegem com escriure bones proves automatitzades per desenvolupar components a Go i com fer-ho mitjançant la biblioteca RSpec de Ruby on Rails.

Afegeix Anar a la pila de tecnologia del nostre projecte

Un dels projectes en què treballo a la meva empresa, eTeam, es pot desglossar en una finestra d’administració, un tauler d’usuaris, un generador d’informes i un processador de requisits que gestiona els requisits de diversos serveis integrats a l’aplicació.

La part del projecte que gestiona les sol·licituds és la més important. Per tant, vam haver de maximitzar la seva fiabilitat i disponibilitat.

Com a part d’una aplicació monolítica, hi ha un risc elevat que el processador de sol·licituds es vegi afectat per un error, fins i tot si el codi canvia en parts de l’aplicació que no hi estan relacionades. També hi ha el risc que el processador de sol·licitud falli quan s’utilitzen altres components. El nombre de treballadors de Ngnix per a l'aplicació és limitat, cosa que pot provocar problemes a mesura que augmenta la càrrega de treball. Per exemple, si s'obren diverses pàgines que requereixen recursos al mateix temps a l'àrea d'administració, el processador alenteix tota l'aplicació o fins i tot la bloqueja.

Aquests riscos, així com la maduresa del sistema en qüestió (no vam haver de fer cap canvi important durant mesos), van convertir aquesta aplicació en un candidat ideal per crear un servei independent per gestionar les sol·licituds.

Vam decidir escriure el servei separat a Go que compartia l'accés a la base de dades amb l'aplicació Rails, que encara era responsable dels canvis en l'estructura de la taula. Amb només dues aplicacions, aquest esquema funciona bé amb una base de dades compartida. Això és el que semblava:

Vam escriure i proporcionar el servei en una instància de Rails independent. D’aquesta manera, no us heu de preocupar que el processador de sol·licituds es vegi afectat cada vegada que implementeu l’aplicació Rails. El servei accepta sol·licituds HTTP directament sense Ngnix i no utilitza molta memòria. Es podria anomenar una aplicació minimalista.

El problema amb les proves unitàries a Go

Hem creat proves unitàries per a l'aplicació Go que es burlaven de totes les sol·licituds de base de dades. A més d'altres arguments a favor d'aquesta solució, l'aplicació principal Rails era la responsable de l'estructura de la base de dades, de manera que l'aplicació Go no tenia la informació necessària per crear una base de dades de prova. La meitat del processament era lògica empresarial, l’altra meitat eren consultes de bases de dades, totes es burlaven.

Els objectes burlats són molt menys llegibles a Go que a Ruby. Sempre que s’afegia una nova funcionalitat per llegir les dades de la base de dades, havíem d’afegir objectes burlats durant moltes de les proves fallides que havien funcionat anteriorment. En última instància, aquestes proves unitàries no van resultar ser molt efectives i eren extremadament fràgils.

la nostra solució

Per compensar aquests desavantatges, vam decidir cobrir el servei amb proves funcionals a l’aplicació Rails i provar el servei a Go com una caixa negra. No hi havia manera de funcionar les proves de caixa blanca perquè era impossible entrar al servei amb Ruby i veure si es cridava a un mètode.

Això també significa que no es poden burlar les sol·licituds enviades a través del servei de prova. Per tant, necessitàvem una altra aplicació per gestionar i escriure aquestes proves. Alguna cosa com RequestBin funcionaria, però havia de funcionar localment. Ja havíem escrit una utilitat que feia el truc. Així que vam decidir utilitzar-lo.

Aquesta va ser la configuració resultant:

  1. RSpec compilarà i executarà el binari Go amb la configuració que especifica l'accés a la base de dades de prova juntament amb un port específic per rebre sol·licituds HTTP, és a dir, H. 8082.
  2. També executa la utilitat que registra les sol·licituds HTTP que entren al port 8083.
  3. Escrivim proves regulars a RSpec. Això crearà les dades necessàries a la base de dades i enviarà una sol·licitud a localhost: 8082 com si fos un servei extern com HTTParty.
  4. Analitzem la resposta, revisem els canvis a la base de dades, rebem una llista de sol·licituds registrades pel delegat de RequestBin i les revisem.

Detalls de la implementació

Així ho vam fer. Per demostrar-ho, truqueu al servei de proves TheService i creeu un embolcall:

Val a dir que la càrrega automàtica de fitxers s'ha de configurar a la carpeta d'assistència quan s'utilitza RSpec:

Dir [Rails.root.join ('spec / support / ** / *. Rb')]. Cada {| f | requereixen f

El mètode inicial:

  • Llegeix la informació de configuració necessària per iniciar TheService. Aquesta informació pot variar de desenvolupador a desenvolupador i, per tant, queda exclosa de Git. La configuració conté els paràmetres necessaris per iniciar el programa. Totes aquestes configuracions diferents es troben en un sol lloc, de manera que no haureu de crear fitxers innecessaris.
  • S'ha compilat i passa per córrer
  • Selecciona cada segon i espera que TheService estigui a punt per acceptar les sol·licituds.
  • Registra l'identificador de cada procés perquè no es pugui repetir res i es pugui aturar un procés.

La configuració en si:

El mètode d'aturada simplement atura el procés. Però hi ha un GOTCHA! Ruby executa una ordre "go run" que compila TheService i inicia un fitxer binari en un procés fill amb un identificador desconegut. Si acabem d’aturar el procés a Ruby, el procés fill no s’aturarà automàticament i es continuarà fent servir el port. Per aturar TheService, cal executar l'identificador del grup de processos:

A continuació, preparem el "context_compartit" definint les variables estàndard, iniciant TheService, si encara no s'ha iniciat, i apaguem temporalment la gravadora de vídeo perquè la gravadora de vídeo veuria el que fem com a sol·licitud de servei extern, però no volem VCR per burlar les consultes en aquest moment:

I ara podem veure com les especificacions estan escrites per elles mateixes:

El servei pot fer sol·licituds HTTP a serveis externs. El podem configurar per redirigir les sol·licituds a la utilitat local que les registra. També hi ha un embolcall d'inici i parada per a aquesta utilitat que és similar a TheServiceControl, excepte que aquesta utilitat només es pot iniciar com a fitxer binari sense recopilació.

Altres aspectes destacats

L'aplicació Go s'ha escrit per enviar tots els registres i informació de depuració a STDOUT. Durant la producció, aquesta sortida s’envia a un fitxer. En iniciar RSpec, el registre es mostra a la consola, cosa que és molt útil a l’hora de depurar.

En concret, si executeu les especificacions que no requereixen TheService, no s’iniciarà.

Per no perdre temps en iniciar TheService cada cop que canvia una especificació, podeu iniciar TheService manualment al terminal durant el procés de desenvolupament i simplement no apagar-lo. Fins i tot podeu iniciar-lo en mode de depuració IDE sempre que ho necessiteu. A continuació, les especificacions ho preparen tot, envien la sol·licitud al servei, el procés s'atura i el podeu depurar fàcilment. Això fa que l’enfocament TDD sigui molt pràctic.

Conclusió

Fa aproximadament un any que utilitzem aquesta configuració i no hi hem vist cap error. Les especificacions són molt més llegibles a Go que les proves unitàries i no es basen en conèixer l'estructura interna del servei. Si per qualsevol motiu hem de reescriure el servei en un idioma diferent, no cal que canviem les especificacions. Només cal reescriure els embolcalls que s’utilitzen per iniciar el servei de prova amb una altra ordre.