Täydellinen opas end-to-end-API-testaukseen Dockerilla

Testaus on kipua yleensä. Jotkut eivät näe järkeä. Jotkut näkevät sen, mutta ajattelevat sen ylimääräisenä askeleena, joka hidastaa heitä. Joskus testit ovat olemassa, mutta ne ovat hyvin pitkiä tai epävakaita. Tässä artikkelissa näet, kuinka voit suunnitella testejä itsellesi Dockerilla.

Haluamme nopeita, mielekkäitä ja luotettavia testejä, jotka on kirjoitettu ja ylläpidetty pienellä vaivalla. Se tarkoittaa testejä, jotka ovat hyödyllisiä sinulle kehittäjänä päivittäin. Niiden tulisi parantaa tuottavuuttasi ja parantaa ohjelmistosi laatua. Testien tekeminen, koska kaikki sanovat "sinun pitäisi olla testejä", ei ole hyvä, jos se hidastaa sinua.

Katsotaanpa, kuinka saavuttaa tämä ei niin paljon vaivaa.

Esimerkki, jonka aiomme testata

Tässä artikkelissa aiomme testata Node / express-sovelluksella rakennettua sovellusliittymää ja käyttää testaukseen chai / mokkaa. Olen valinnut JS'y-pinon, koska koodi on erittäin lyhyt ja helppo lukea. Käytetyt periaatteet pätevät kaikkiin teknisiin pinoihin. Jatka lukemista, vaikka Javascript sairastaisi sinua.

Esimerkki kattaa yksinkertaisen joukon CRUD-päätepisteitä käyttäjille. Se on enemmän kuin tarpeeksi ymmärtää käsite ja soveltaa API: n monimutkaisempaan liiketoimintalogiikkaan.

Aiomme käyttää melko vakioympäristöä API: lle:

  • Postgres-tietokanta
  • Redis-klusteri
  • Sovellusliittymämme käyttää muita ulkoisia sovellusliittymiä työnsä suorittamiseen

Sovellusliittymäsi saattaa tarvita toisen ympäristön. Tässä artikkelissa käytetyt periaatteet pysyvät ennallaan. Käytät erilaisia ​​Docker-peruskuvia ajaaksesi tarvitsemasi komponentin.

Miksi Docker? Ja itse asiassa Docker Compose

Tässä osassa on paljon argumentteja, jotka kannattavat Dockerin käyttöä testauksessa. Voit ohittaa sen, jos haluat päästä tekniseen osaan heti.

Tuskalliset vaihtoehdot

Voit testata sovellusliittymääsi lähellä tuotantoympäristöä. Voit pilkata ympäristöä kooditasolla tai suorittaa testit oikealla palvelimella, johon tietokanta jne. On asennettu.

Kaiken pilkkaaminen kooditasolla häiritsee API: n koodia ja kokoonpanoa. Se ei myöskään usein ole kovin edustava siitä, miten sovellusliittymä käyttäytyy tuotannossa. Asioiden suorittaminen todellisessa palvelimessa on infrastruktuurin raskasta. Se on paljon asennusta ja ylläpitoa, eikä se ole mittakaavassa. Jaetun tietokannan avulla voit suorittaa vain yhden testin kerrallaan varmistaaksesi, että testiajot eivät häiritse toisiaan.

Docker Compose antaa meille mahdollisuuden saada molempien maailmojen parhaat puolet. Se luo "kontti" versiot kaikista käytetyistä ulkoisista osista. Se on pilkkaa, mutta koodin ulkopuolella. API: n mielestä se on todellisessa fyysisessä ympäristössä. Docker Compose luo myös eristetyn verkon kaikille säiliöille tietyn testiajon aikana. Tämän avulla voit suorittaa useita niistä rinnakkain paikallisella tietokoneellasi tai CI-isännällä.

Ylilyönti?

Saatat ihmetellä, onko Docker-säveltämisellä suoritettu loppuun asti testejä lainkaan ylimielisesti. Entä vain yksikkötestien suorittaminen sen sijaan?

Viimeisten 10 vuoden aikana suuret monoliittisovellukset on jaettu pienempiin palveluihin (suuntaus kohti vilkkaita "mikropalveluja"). Annettu API-komponentti perustuu useampiin ulkoisiin osiin (infrastruktuuri tai muut sovellusliittymät). Palvelujen pienentyessä integraatiosta infrastruktuuriin tulee suurempi osa työtä.

Sinun tulisi pitää pieni ero tuotantosi ja kehitysympäristösi välillä. Muuten ongelmia syntyy tuotannon käyttöönoton yhteydessä. Määritelmän mukaan nämä ongelmat ilmenevät pahimmalla mahdollisella hetkellä. Ne johtavat kiireisiin korjauksiin, laadun heikkenemiseen ja turhautumiseen joukkueelle. Kukaan ei halua sitä.

Saatat miettiä, jos Docker-komposiittikokeet kestävät loppuun asti kuin perinteiset yksikkötestit. Ei oikeastaan. Alla olevasta esimerkistä näet, että voimme pitää testit helposti alle minuutissa ja suurella hyödyllä: testit heijastavat sovelluskäyttäytymistä todellisessa maailmassa. Tämä on arvokkaampaa kuin tietää, toimiiko luokkasi jonnekin sovelluksen keskellä kunnossa vai ei.

Lisäksi, jos sinulla ei ole testejä juuri nyt, alusta loppuun antaa sinulle suuria etuja pienestä vaivasta. Tiedät, että kaikki sovelluksen pinot toimivat yhdessä yleisimmissä tilanteissa. Se on jo jotain! Sieltä voit aina tarkentaa strategiaa testata sovelluksesi kriittisiä osia.

Ensimmäinen testi

Aloitetaan helpoimmasta osasta: sovellusliittymästä ja Postgres-tietokannasta. Suoritetaan yksinkertainen CRUD-testi. Kun tämä kehys on paikallaan, voimme lisätä uusia ominaisuuksia sekä komponenttiin että testiin.

Tässä on minimaalinen sovellusliittymä, jolla on GET / POST käyttäjien luomiseksi ja luetteloimiseksi:

const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const config = require('./config'); const db = require('knex')({ client: 'pg', connection: { host : config.db.host, user : config.db.user, password : config.db.password, }, }); const app = express(); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use(cors()); app.route('/api/users').post(async (req, res, next) => { try { const { email, firstname } = req.body; // ... validate inputs here ... const userData = { email, firstname }; const result = await db('users').returning('id').insert(userData); const id = result[0]; res.status(201).send({ id, ...userData }); } catch (err) { console.log(`Error: Unable to create user: ${err.message}. ${err.stack}`); return next(err); } }); app.route('/api/users').get((req, res, next) => { db('users') .select('id', 'email', 'firstname') .then(users => res.status(200).send(users)) .catch(err => { console.log(`Unable to fetch users: ${err.message}. ${err.stack}`); return next(err); }); }); try { console.log("Starting web server..."); const port = process.env.PORT || 8000; app.listen(port, () => console.log(`Server started on: ${port}`)); } catch(error) { console.error(error.stack); }

Tässä ovat testimme kirjoitettu chain kanssa. Testit luovat uuden käyttäjän ja hakevat sen takaisin. Voit nähdä, että testejä ei ole yhdistetty millään tavalla API: n koodiin. SERVER_URLMuuttuja määrittää päätepiste testi. Se voi olla paikallinen tai syrjäinen ympäristö.

const chai = require("chai"); const chaiHttp = require("chai-http"); const should = chai.should(); const SERVER_URL = process.env.APP_URL || "//localhost:8000"; chai.use(chaiHttp); const TEST_USER = { email: "[email protected]", firstname: "John" }; let createdUserId; describe("Users", () => { it("should create a new user", done => { chai .request(SERVER_URL) .post("/api/users") .send(TEST_USER) .end((err, res) => { if (err) done(err) res.should.have.status(201); res.should.be.json; res.body.should.be.a("object"); res.body.should.have.property("id"); done(); }); }); it("should get the created user", done => { chai .request(SERVER_URL) .get("/api/users") .end((err, res) => { if (err) done(err) res.should.have.status(200); res.body.should.be.a("array"); const user = res.body.pop(); user.id.should.equal(createdUserId); user.email.should.equal(TEST_USER.email); user.firstname.should.equal(TEST_USER.firstname); done(); }); }); });

Hyvä. Testataksemme sovellusliittymäämme määritetään nyt Docker-kirjoitusympäristö. Nimetyssä tiedostossa docker-compose.ymlkuvataan Dockerin suorittamat kontit.

version: '3.1' services: db: image: postgres environment: POSTGRES_USER: john POSTGRES_PASSWORD: mysecretpassword expose: - 5432 myapp: build: . image: myapp command: yarn start environment: APP_DB_HOST: db APP_DB_USER: john APP_DB_PASSWORD: mysecretpassword expose: - 8000 depends_on: - db myapp-tests: image: myapp command: dockerize -wait tcp://db:5432 -wait tcp://myapp:8000 -timeout 10s bash -c "node db/init.js && yarn test" environment: APP_URL: //myapp:8000 APP_DB_HOST: db APP_DB_USER: john APP_DB_PASSWORD: mysecretpassword depends_on: - db - myapp

Joten mitä meillä täällä on. Kontteja on 3:

  • db kehittää uutta PostgreSQL-esiintymää. Käytämme julkista Postgres-kuvaa Docker Hubista. Asetamme tietokannan käyttäjänimen ja salasanan. Käskemme Dockeria paljastamaan portin 5432, jota tietokanta kuuntelee, jotta muut kontit voivat muodostaa yhteyden
  • myapp on säilö, joka käyttää API: ta. buildKomento kertoo satamatyöläinen todella rakentaa säiliö kuvan meidän lähteestä. Loput ovat kuin db-säilö: ympäristömuuttujat ja portit
  • myapp-testit on kontti, joka suorittaa testimme . Se käyttää samaa kuvaa kuin myapp, koska koodi on jo siellä, joten sitä ei tarvitse rakentaa uudelleen. Säiliössä node db/init.js && yarn testsuoritettava komento alustaa tietokannan (luo taulukoita jne.) Ja suorittaa testit. Käytämme dockerizea odottamaan kaikkien vaadittujen palvelimien olevan toiminnassa. depends_onVaihtoehtoja varmistaa, että säiliöt alkavat tietyssä järjestyksessä. Se ei takaa, että db-säilön sisällä oleva tietokanta on todella valmis hyväksymään yhteydet. Eikä API-palvelimemme ole jo toiminnassa.

Ympäristön määritelmä on kuin 20 riviä erittäin helposti ymmärrettävää koodia. Ainoa järkevä osa on ympäristön määrittely. Käyttäjätunnusten, salasanojen ja URL-osoitteiden on oltava yhdenmukaisia, jotta säilöt voivat todella toimia yhdessä.

Yksi asia on huomata, että Docker-kirjoitus asettaa luomiensa säilöjen isännän säilön nimeksi. Joten tietokanta ei ole käytettävissä osoitteessa localhost:5432but db:5432. Samalla tavalla kuin API-palvelumme palvellaan myapp:8000. Täällä ei ole minkäänlaista paikallista isäntää.

Tämä tarkoittaa, että sovellusliittymän on tuettava ympäristömuuttujia ympäristön määrittelyssä. Ei kovakoodattuja juttuja. Mutta sillä ei ole mitään tekemistä Dockerin tai tämän artikkelin kanssa. Määritettävä sovellus on 12-tekijäisen sovelluksen manifestin kohta 3, joten sinun pitäisi tehdä se jo.

Viimeinen asia, jonka meidän on kerrottava Dockerille, on kuinka rakentaa kontti myapp . Käytämme Docker-tiedostoa kuten alla. Sisältö on omaa teknistä pinoasi varten, mutta idea on niputtaa sovellusliittymäsi suoritettavaksi palvelimeksi.

Seuraava esimerkki solmun sovellusliittymästä asentaa Dockerizen, asentaa sovellusliittymän riippuvuudet ja kopioi API: n koodin säilöön (palvelin on kirjoitettu raakana JS: nä, joten sitä ei tarvitse kääntää).

FROM node AS base # Dockerize is needed to sync containers startup ENV DOCKERIZE_VERSION v0.6.0 RUN wget //github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz RUN mkdir -p ~/app WORKDIR ~/app COPY package.json . COPY yarn.lock . FROM base AS dependencies RUN yarn FROM dependencies AS runtime COPY . .

Tyypillisesti riviltä WORKDIR ~/appja alapuolelta suoritat komentoja, jotka rakentavat sovelluksesi.

Ja tässä on komento, jota käytämme testien suorittamiseen:

docker-compose up --build --abort-on-container-exit

Tämä komento kertoo Dockerille, että komposiitti kehittää docker-compose.ymltiedostossa määritellyt komponentit . --buildLippu laukaisee rakentaa ja MyApp säiliön suorittamalla sisältö Dockerfileedellä. --abort-on-container-exitKertoo satamatyöläinen säveltää sammuttaa ympäristöön heti yhden säiliön poistuu.

Se toimii hyvin, koska ainoa komponentti, jonka on tarkoitus poistua, on testisäiliön myapp-testit testien suorittamisen jälkeen. Kirsikka kakkulla, docker-composekomento poistuu samalla poistumiskoodilla kuin poistumisen käynnistäneellä kontilla. Tämä tarkoittaa, että voimme tarkistaa, onnistuivatko testit komentoriviltä. Tämä on erittäin hyödyllistä automatisoiduille rakennuksille CI-ympäristössä.

Eikö se ole täydellinen testiasetus?

Täydellinen esimerkki on täällä GitHubissa. Voit kloonata arkiston ja suorittaa telakointikomennon:

docker-compose up --build --abort-on-container-exit

Tietenkin tarvitset Dockerin asennettuna. Dockerilla on hankala taipumus pakottaa sinut kirjautumaan tilille vain lataamaan asia. Mutta sinun ei todellakaan tarvitse. Siirry julkaisutiedotteisiin (linkki Windowsille ja linkki Macille) ja lataa uusin versio, mutta juuri oikea. Tämä on suora latauslinkki.

Testien ensimmäinen ajo on tavallista pidempi. Tämä johtuu siitä, että Dockerin on ladattava konttien peruskuvat ja välimuisti muutama asia. Seuraavat ajot ovat paljon nopeampia.

Ajon lokit näyttävät alla olevilta. Voit nähdä, että Docker on tarpeeksi viileä laittaa lokit kaikista komponenteista samalle aikajanalle. Tämä on erittäin kätevää, kun etsit virheitä.

Creating tuto-api-e2e-testing_db_1 ... done Creating tuto-api-e2e-testing_redis_1 ... done Creating tuto-api-e2e-testing_myapp_1 ... done Creating tuto-api-e2e-testing_myapp-tests_1 ... done Attaching to tuto-api-e2e-testing_redis_1, tuto-api-e2e-testing_db_1, tuto-api-e2e-testing_myapp_1, tuto-api-e2e-testing_myapp-tests_1 db_1 | The files belonging to this database system will be owned by user "postgres". redis_1 | 1:M 09 Nov 2019 21:57:22.161 * Running mode=standalone, port=6379. myapp_1 | yarn run v1.19.0 redis_1 | 1:M 09 Nov 2019 21:57:22.162 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. redis_1 | 1:M 09 Nov 2019 21:57:22.162 # Server initialized db_1 | This user must also own the server process. db_1 | db_1 | The database cluster will be initialized with locale "en_US.utf8". db_1 | The default database encoding has accordingly been set to "UTF8". db_1 | The default text search configuration will be set to "english". db_1 | db_1 | Data page checksums are disabled. db_1 | db_1 | fixing permissions on existing directory /var/lib/postgresql/data ... ok db_1 | creating subdirectories ... ok db_1 | selecting dynamic shared memory implementation ... posix myapp-tests_1 | 2019/11/09 21:57:25 Waiting for: tcp://db:5432 myapp-tests_1 | 2019/11/09 21:57:25 Waiting for: tcp://redis:6379 myapp-tests_1 | 2019/11/09 21:57:25 Waiting for: tcp://myapp:8000 myapp_1 | $ node server.js redis_1 | 1:M 09 Nov 2019 21:57:22.163 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. db_1 | selecting default max_connections ... 100 myapp_1 | Starting web server... myapp-tests_1 | 2019/11/09 21:57:25 Connected to tcp://myapp:8000 myapp-tests_1 | 2019/11/09 21:57:25 Connected to tcp://db:5432 redis_1 | 1:M 09 Nov 2019 21:57:22.164 * Ready to accept connections myapp-tests_1 | 2019/11/09 21:57:25 Connected to tcp://redis:6379 myapp_1 | Server started on: 8000 db_1 | selecting default shared_buffers ... 128MB db_1 | selecting default time zone ... Etc/UTC db_1 | creating configuration files ... ok db_1 | running bootstrap script ... ok db_1 | performing post-bootstrap initialization ... ok db_1 | syncing data to disk ... ok db_1 | db_1 | db_1 | Success. You can now start the database server using: db_1 | db_1 | pg_ctl -D /var/lib/postgresql/data -l logfile start db_1 | db_1 | initdb: warning: enabling "trust" authentication for local connections db_1 | You can change this by editing pg_hba.conf or using the option -A, or db_1 | --auth-local and --auth-host, the next time you run initdb. db_1 | waiting for server to start....2019-11-09 21:57:24.328 UTC [41] LOG: starting PostgreSQL 12.0 (Debian 12.0-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit db_1 | 2019-11-09 21:57:24.346 UTC [41] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" db_1 | 2019-11-09 21:57:24.373 UTC [42] LOG: database system was shut down at 2019-11-09 21:57:23 UTC db_1 | 2019-11-09 21:57:24.383 UTC [41] LOG: database system is ready to accept connections db_1 | done db_1 | server started db_1 | CREATE DATABASE db_1 | db_1 | db_1 | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/* db_1 | db_1 | waiting for server to shut down....2019-11-09 21:57:24.907 UTC [41] LOG: received fast shutdown request db_1 | 2019-11-09 21:57:24.909 UTC [41] LOG: aborting any active transactions db_1 | 2019-11-09 21:57:24.914 UTC [41] LOG: background worker "logical replication launcher" (PID 48) exited with exit code 1 db_1 | 2019-11-09 21:57:24.914 UTC [43] LOG: shutting down db_1 | 2019-11-09 21:57:24.930 UTC [41] LOG: database system is shut down db_1 | done db_1 | server stopped db_1 | db_1 | PostgreSQL init process complete; ready for start up. db_1 | db_1 | 2019-11-09 21:57:25.038 UTC [1] LOG: starting PostgreSQL 12.0 (Debian 12.0-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit db_1 | 2019-11-09 21:57:25.039 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 db_1 | 2019-11-09 21:57:25.039 UTC [1] LOG: listening on IPv6 address "::", port 5432 db_1 | 2019-11-09 21:57:25.052 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" db_1 | 2019-11-09 21:57:25.071 UTC [59] LOG: database system was shut down at 2019-11-09 21:57:24 UTC db_1 | 2019-11-09 21:57:25.077 UTC [1] LOG: database system is ready to accept connections myapp-tests_1 | Creating tables ... myapp-tests_1 | Creating table 'users' myapp-tests_1 | Tables created succesfully myapp-tests_1 | yarn run v1.19.0 myapp-tests_1 | $ mocha --timeout 10000 --bail myapp-tests_1 | myapp-tests_1 | myapp-tests_1 | Users myapp-tests_1 | Mock server started on port: 8002 myapp-tests_1 | ✓ should create a new user (151ms) myapp-tests_1 | ✓ should get the created user myapp-tests_1 | ✓ should not create user if mail is spammy myapp-tests_1 | ✓ should not create user if spammy mail API is down myapp-tests_1 | myapp-tests_1 | myapp-tests_1 | 4 passing (234ms) myapp-tests_1 | myapp-tests_1 | Done in 0.88s. myapp-tests_1 | 2019/11/09 21:57:26 Command finished successfully. tuto-api-e2e-testing_myapp-tests_1 exited with code 0

Voimme nähdä, että db on pisin alustava säilö. Käydä järkeen. Kun se on tehty, testit alkavat. Kannettavan tietokoneeni kokonaisaika on 16 sekuntia. Verrattuna testien tosiasiallisiin suorittamiseen käytettyihin 880 ms: iin, se on paljon. Käytännössä alle minuutin mittaiset testit ovat kultaa, koska se on melkein välitöntä palautetta. 15 sekunnin yleiskustannukset ovat ostoaika, joka on vakio, kun lisäät lisää testejä. Voit lisätä satoja testejä ja silti pitää suoritusajan alle minuutissa.

Voilà! Testikehyksemme on valmis ja käynnissä. Todellisessa projektissa seuraavat vaiheet ovat API: n toiminnallisen kattavuuden parantaminen useammalla testillä. Tarkastellaan CRUD-operaatioita. On aika lisätä lisää elementtejä testiympäristöön.

Redis-klusterin lisääminen

Lisätään toinen elementti API-ympäristöön ymmärtämään mitä se vie. Spoilerivaroitus: se ei ole paljon.

Kuvitellaan, että sovellusliittymämme pitää käyttäjäistunnot Redis-klusterissa. Jos mietit, miksi tekisimme niin, kuvittele 100 sovellusliittymän tuotantoa. Käyttäjät osuvat yhteen tai toiseen palvelimeen pyöreän robin-kuormituksen tasapainottamisen perusteella. Jokainen pyyntö on todennettava.

Tämä edellyttää käyttäjäprofiilidataa oikeuksien ja muun sovelluskohtaisen liiketoimintalogiikan tarkistamiseksi. Yksi tapa edetä on tehdä edestakainen matka tietokantaan tietojen noutamiseksi aina, kun tarvitset sitä, mutta se ei ole kovin tehokasta. Muistitietokantaryhmän käyttäminen tekee tiedoista kaikkien palvelimien saataville paikallisen muuttujan luettavaksi.

Näin parannat Docker-kirjoitusympäristöäsi lisäpalvelulla. Lisätään Redis-klusteri virallisesta Docker-kuvasta (olen säilyttänyt vain tiedoston uudet osat):

services: db: ... redis: image: "redis:alpine" expose: - 6379 myapp: environment: APP_REDIS_HOST: redis APP_REDIS_PORT: 6379 ... myapp-tests: command: dockerize ... -wait tcp://redis:6379 ... environment: APP_REDIS_HOST: redis APP_REDIS_PORT: 6379 ... ...

Voit nähdä, että se ei ole paljon. Lisäsimme uuden kontin nimeltä redis . Se käyttää virallista minimaalista redis-kuvaa nimeltä redis:alpine. Lisäsimme Redis-isäntä- ja porttimääritykset API-säilöön. Ja olemme tehneet testejä odottamaan sitä sekä muita kontteja ennen testien suorittamista.

Muutetaan sovellustamme käyttämään Redis-klusteria todella:

const redis = require('redis').createClient({ host: config.redis.host, port: config.redis.port, }) ... app.route('/api/users').post(async (req, res, next) => { try { const { email, firstname } = req.body; // ... validate inputs here ... const userData = { email, firstname }; const result = await db('users').returning('id').insert(userData); const id = result[0]; // Once the user is created store the data in the Redis cluster await redis.set(id, JSON.stringify(userData)); res.status(201).send({ id, ...userData }); } catch (err) { console.log(`Error: Unable to create user: ${err.message}. ${err.stack}`); return next(err); } });

Vaihdetaan nyt testimme tarkistaaksemme, että Redis-klusterissa on oikeat tiedot. Siksi myapp-testisäiliö saa myös Redis-isännän ja portin kokoonpanon docker-compose.yml.

it("should create a new user", done => { chai .request(SERVER_URL) .post("/api/users") .send(TEST_USER) .end((err, res) => { if (err) throw err; res.should.have.status(201); res.should.be.json; res.body.should.be.a("object"); res.body.should.have.property("id"); res.body.should.have.property("email"); res.body.should.have.property("firstname"); res.body.id.should.not.be.null; res.body.email.should.equal(TEST_USER.email); res.body.firstname.should.equal(TEST_USER.firstname); createdUserId = res.body.id; redis.get(createdUserId, (err, cacheData) => { if (err) throw err; cacheData = JSON.parse(cacheData); cacheData.should.have.property("email"); cacheData.should.have.property("firstname"); cacheData.email.should.equal(TEST_USER.email); cacheData.firstname.should.equal(TEST_USER.firstname); done(); }); }); });

Katso kuinka helppoa tämä oli. Voit rakentaa testeihisi monimutkaisen ympäristön, kuten kokoat Lego-tiilet.

Voimme nähdä toisen hyödyn tällaisesta täyden ympäristötestauksen kontista. Testit voivat todella tutkia ympäristön komponentteja. Testimme eivät voi vain tarkistaa, että sovellusliittymä palauttaa oikeat vastauskoodit ja tiedot. Voimme myös tarkistaa, että Redis-klusterin tiedoilla on oikeat arvot. Voisimme myös tarkistaa tietokannan sisällön.

API-mallien lisääminen

API-komponenttien yhteinen elementti on kutsua muita API-komponentteja.

Oletetaan, että sovellusliittymämme on tarkistettava, onko käyttäjälle roskapostia luodessaan käyttäjää. Tarkastus tehdään kolmannen osapuolen palvelulla:

const validateUserEmail = async (email) => { const res = await fetch(`${config.app.externalUrl}/validate?email=${email}`); if(res.status !== 200) return false; const json = await res.json(); return json.result === 'valid'; } app.route('/api/users').post(async (req, res, next) => { try { const { email, firstname } = req.body; // ... validate inputs here ... const userData = { email, firstname }; // We don't just create any user. Spammy emails should be rejected const isValidUser = await validateUserEmail(email); if(!isValidUser) { return res.sendStatus(403); } const result = await db('users').returning('id').insert(userData); const id = result[0]; await redis.set(id, JSON.stringify(userData)); res.status(201).send({ id, ...userData }); } catch (err) { console.log(`Error: Unable to create user: ${err.message}. ${err.stack}`); return next(err); } });

Nyt meillä on ongelma testata mitään. Emme voi luoda käyttäjiä, jos roskapostiviestien tunnistamiseen tarkoitettu sovellusliittymä ei ole käytettävissä. API: n muokkaaminen tämän vaiheen ohittamiseksi testitilassa on vaarallinen sekoitus koodia.

Vaikka voisimme käyttää todellista kolmannen osapuolen palvelua, emme halua tehdä sitä. Testien ei pitäisi pääsääntöisesti riippua ulkoisesta infrastruktuurista. Ensinnäkin, koska suoritat testisi todennäköisesti paljon osana CI-prosessiasi. Ei ole niin hienoa käyttää toista tuotantosovellusliittymää tähän tarkoitukseen. Toiseksi, sovellusliittymä saattaa olla väliaikaisesti poissa käytöstä, jos testisi epäonnistuvat vääristä syistä.

Oikea ratkaisu on pilkata ulkoisia sovellusliittymiä testeissämme.

Ei tarvitse mitään hienoja puitteita. Rakennamme yleisen pilkun vanilja JS: ään ~ 20 koodirivillä. Tämä antaa meille mahdollisuuden hallita, mitä sovellusliittymä palauttaa komponenttiimme. Sen avulla voidaan testata virheskenaarioita.

Tehostetaan nyt testejä.

const express = require("express"); ... const MOCK_SERVER_PORT = process.env.MOCK_SERVER_PORT || 8002; // Some object to encapsulate attributes of our mock server // The mock stores all requests it receives in the `requests` property. const mock = { app: express(), server: null, requests: [], status: 404, responseBody: {} }; // Define which response code and content the mock will be sending const setupMock = (status, body) => { mock.status = status; mock.responseBody = body; }; // Start the mock server const initMock = async () => { mock.app.use(bodyParser.urlencoded({ extended: false })); mock.app.use(bodyParser.json()); mock.app.use(cors()); mock.app.get("*", (req, res) => { mock.requests.push(req); res.status(mock.status).send(mock.responseBody); }); mock.server = await mock.app.listen(MOCK_SERVER_PORT); console.log(`Mock server started on port: ${MOCK_SERVER_PORT}`); }; // Destroy the mock server const teardownMock = () => { if (mock.server) { mock.server.close(); delete mock.server; } }; describe("Users", () => { // Our mock is started before any test starts ... before(async () => await initMock()); // ... killed after all the tests are executed ... after(() => { redis.quit(); teardownMock(); }); // ... and we reset the recorded requests between each test beforeEach(() => (mock.requests = [])); it("should create a new user", done => { // The mock will tell us the email is valid in this test setupMock(200, { result: "valid" }); chai .request(SERVER_URL) .post("/api/users") .send(TEST_USER) .end((err, res) => { // ... check response and redis as before createdUserId = res.body.id; // Verify that the API called the mocked service with the right parameters mock.requests.length.should.equal(1); mock.requests[0].path.should.equal("/api/validate"); mock.requests[0].query.should.have.property("email"); mock.requests[0].query.email.should.equal(TEST_USER.email); done(); }); }); });

Testit tarkistavat nyt, että ulkoiseen sovellusliittymään on löydetty oikeat tiedot sovellusliittymäämme kutsun aikana.

Voimme lisätä myös muita testejä, jotka tarkistavat API: n käyttäytymisen ulkoisten API-vastauskoodien perusteella:

describe("Users", () => { it("should not create user if mail is spammy", done => { // The mock will tell us the email is NOT valid in this test ... setupMock(200, { result: "invalid" }); chai .request(SERVER_URL) .post("/api/users") .send(TEST_USER) .end((err, res) => { // ... so the API should fail to create the user // We could test that the DB and Redis are empty here res.should.have.status(403); done(); }); }); it("should not create user if spammy mail API is down", done => { // The mock will tell us the email checking service // is down for this test ... setupMock(500, {}); chai .request(SERVER_URL) .post("/api/users") .send(TEST_USER) .end((err, res) => { // ... in that case also a user should not be created res.should.have.status(403); done(); }); }); });

Se, miten käsittelet sovelluksen kolmannen osapuolen sovellusliittymien virheitä, on tietysti sinun tehtäväsi. Mutta ymmärrät.

Näiden testien suorittamiseksi meidän on kerrottava kontille myapp, mikä on kolmannen osapuolen palvelun perus-URL:

 myapp: environment: APP_EXTERNAL_URL: //myapp-tests:8002/api ... myapp-tests: environment: MOCK_SERVER_PORT: 8002 ...

Päätelmä ja muutama muu ajatus

Toivottavasti tämä artikkeli antoi sinulle maun siitä, mitä Docker Compose voi auttaa puolestasi API-testauksessa. Täydellinen esimerkki on täällä GitHubissa.

Docker compose -ominaisuuden avulla testit suoritetaan nopeasti tuotantoympäristössä. Se ei vaadi komponenttikoodin mukauttamista. Ainoa vaatimus on tukea ympäristömuuttujiin perustuvaa kokoonpanoa.

Tämän esimerkin komponenttilogiikka on hyvin yksinkertainen, mutta periaatteet koskevat mitä tahansa sovellusliittymää. Testisi ovat vain pitempiä tai monimutkaisempia. Ne koskevat myös mitä tahansa teknistä pinoa, joka voidaan laittaa astian sisälle (se on kaikki ne). Ja kun olet siellä, olet yhden askeleen päässä käyttämästä konttejasi tuotantoon tarvittaessa.

Jos sinulla ei ole testejä juuri nyt, suosittelen, että sinun on aloitettava: testaus loppuun asti Docker compose -ohjelmalla. Se on niin yksinkertaista, että ensimmäinen testi voidaan suorittaa muutamassa tunnissa. Ota rohkeasti yhteyttä minuun, jos sinulla on kysyttävää tai tarvitset neuvoja. Autan mielelläni.

Toivottavasti pidit tästä artikkelista ja aloitat sovellusliittymiesi testaamisen Docker Compose -ohjelmalla. Kun testit ovat valmiit, voit suorittaa ne jatkossa Fire CI -integraatioalustallamme.

Viimeinen idea menestyä automaattisella testauksella.

Suurten testipakettien ylläpitämisen kannalta tärkein piirre on, että testit ovat helposti luettavissa ja ymmärrettäviä. Tämä on avain motivoida tiimiä pitämään testit ajan tasalla. Monimutkaisia ​​testikehyksiä ei todennäköisesti käytetä asianmukaisesti pitkällä aikavälillä.

API: n pinosta riippumatta saatat haluta harkita chai / mokan käyttöä testien kirjoittamiseen sille. Saattaa tuntua epätavalliselta, että eri ajonaikaisia ​​koodeja ja testikoodeja on, mutta jos se saa työn suoritettua ... Kuten näette tämän artikkelin esimerkeistä, REST-sovellusliittymän testaaminen chai / mokalla on niin yksinkertaista kuin se saa . Oppimiskäyrä on lähellä nollaa.

Joten jos sinulla ei ole lainkaan testejä ja sinulla on REST-sovellusliittymä testattavaksi Java-, Python-, RoR-, .NET- tai muussa pinossa, voit harkita chai / mokan kokeilua.

Jos mietit, miten aloittaa jatkuva integrointi ollenkaan, olen kirjoittanut siitä laajemman oppaan. Tässä se on: Kuinka aloittaa jatkuva integrointi

Alun perin julkaistu Fire CI -blogissa.