Leder du efter en måde at lave test på en nemmere måde? Så har vi noget til dig! Se den følgende artikel og lær, hvordan du gør det muligt.
Moderne applikationsudvikling er baseret på en enkel regel:
Brug sammensætning
Vi sammensætter klasser, funktioner og tjenester til større stykker software. Det sidste element er grundlaget for mikrotjenester og Sekskantet arkitektur. Vi vil gerne bruge eksisterende løsninger, integrere dem med vores software og gå direkte i gang. marked.
Vil du håndtere kontoregistrering og gemme brugerdata? Du kan vælge en af OAuth-tjenesterne. Måske tilbyder din applikation en form for abonnement eller betaling? Der er mange tjenester, der kan hjælpe dig med at håndtere dette. Har du brug for analyser på dit website, men forstår ikke GDPR? Du er velkommen til at vælge en af de færdige løsninger.
Noget, der gør udvikling så let fra et forretningsmæssigt synspunkt, kan give dig hovedpine - i det øjeblik, hvor du skal skrive en simpel test.
De fantastiske dyr: Køer, databaser og hvordan man tester dem
Unit testing er ret enkelt. Hvis du bare følger reglerne, så vil dit testmiljø og Kode er sunde. Hvilke regler er det?
Let at skrive - En enhedstest skal være nem at skrive, fordi man skriver mange af dem. Mindre indsats betyder, at der skrives flere tests.
Læsbar - Testkoden skal være let at læse. Testen er en historie. Den beskriver softwarens opførsel og kan bruges som en genvej til dokumentation. En god enhedstest hjælper dig med at rette fejl uden at debugge koden.
Pålidelig - Testen bør kun fejle, hvis der er en fejl i det system, der testes. Er det indlysende? Ikke altid. Nogle gange består tests, hvis du kører dem en efter en, men fejler, når du kører dem som et sæt. De består på din maskine, men fejler på CI (Virker på min maskine). En god enhedstest har kun én grund til at fejle.
Hurtig - tests skal være hurtige. Forberedelse til kørsel, start og selve testudførelsen skal være meget hurtig. Ellers vil du skrive dem, men ikke køre dem. Langsomme tests betyder mistet fokus. Man venter og kigger på statuslinjen.
Uafhængig - Endelig skal testen være uafhængig. Denne regel udspringer af de foregående. Kun virkelig uafhængige tests kan blive til en enhed. De forstyrrer ikke hinanden, kan køres i vilkårlig rækkefølge, og potentielle fejl afhænger ikke af resultaterne af andre tests. Uafhængig betyder også, at man ikke er afhængig af eksterne ressourcer som databaser, beskedtjenester eller filsystem. Hvis du har brug for at kommunikere med eksterne ressourcer, kan du bruge mocks, stubs eller dummies.
Alt bliver kompliceret, når vi vil skrive nogle integrationstests. Det er ikke så slemt, hvis vi gerne vil teste nogle få tjenester sammen. Men når vi skal teste tjenester, der bruger eksterne ressourcer som databaser eller beskedtjenester, så beder vi om problemer.
For at køre testen skal du installere...
For mange år siden, da vi ville lave nogle integrationstest og bruge f.eks. databaser, havde vi to muligheder:
Vi kan installere en database lokalt. Opsæt et skema og opret forbindelse fra vores test;
Vi kan oprette forbindelse til en eksisterende instans "et eller andet sted i rummet".
Begge havde fordele, begge havde ulemper. Men begge introducerer yderligere niveauer af kompleksitet. Nogle gange var det teknisk kompleksitet som følge af visse værktøjers egenskaber, f.eks. installation og styring af Oracle DB på din localhost. Nogle gange var det en ulempe i processen, f.eks. at man skal være enig med testen. hold om JMS-brug ... hver gang du vil køre tests.
Containere til undsætning
I løbet af de sidste 10 år har ideen om containerisering vundet anerkendelse i branchen. Så det er en naturlig beslutning at vælge containere som løsning på vores integrationstestproblem. Det er en enkel og ren løsning. Du kører bare dit proces-build, og så virker det hele! Kan du ikke tro det? Se på denne enkle konfiguration af et maven-build:
.
com.dkanejs.maven.plugins.
docker-compose-maven-plugin.
4.0.0
up
test-compile
op
${projekt.basedir}/docker-compose.yml.
true.
down
efter-integrationstest
down
${project.basedir}/docker-compose.yml.
true.
.
Og den docker-compose.yml Filen ser også ret flot ud!
Eksemplet ovenfor er meget enkelt. Bare en postgres-database, pgAdmin og det er alt. Når du kører
bash
$ mvn clean verify
så starter maven-plugin'et containerne og slukker dem efter testene. Problemerne begynder, når projektet vokser, og vores compose-fil også vokser. Hver gang skal du starte alle containere, og de vil være i live gennem hele buildet. Du kan gøre situationen lidt bedre ved at ændre konfigurationen af plugin-udførelsen, men det er ikke nok. I værste fald udnytter dine containere systemets ressourcer, før testene starter!
Og det er ikke det eneste problem. Du kan ikke køre en eneste integrationstest fra dit IDE. Før det skal du starte containerne manuelt. Desuden vil den næste maven-kørsel rive disse containere ned (se på ned udførelse).
Så denne løsning er som et stort fragtskib. Hvis alt fungerer godt, er det ok. Enhver uventet eller usædvanlig adfærd fører os til en form for katastrofe.
Test containere - kør containere fra tests
Men hvad nu, hvis vi kunne køre vores containere fra tests? Ideen ser god ud, og den er allerede ved at blive implementeret. TestcontainereFordi vi taler om dette projekt, er her en løsning på vores problemer. Ikke ideel, men ingen er perfekt.
Dette er en Java biblioteket, som understøtter JUnit- og Spock-tests og giver lette og nemme måder at køre Docker-containeren på. Lad os tage et kig på det og skrive noget kode!
Forudsætninger og konfiguration
Før vi går i gang, skal vi tjekke vores konfiguration. Testbeholdere behov:
Docker i version v17.09,
Java minimum version 1.8,
Adgang til netværk, især til docker.hub.
Du kan læse mere om kravene til specifikke OS og CI her i dokumentation.
Nu er det tid til at tilføje nogle linjer til pom.xml.
org.testcontainers
testcontainers-bom.
${testcontaines.version}
pom
import
org.postgresql
postgresql.
kørselstid.
org.testcontainers
postgresql.
test
org.testcontainers
junit-jupiter.
test
Jeg bruger Testbeholdere version 1.17.3men du er velkommen til at bruge den nyeste.
Test med Postgres-container
Det første skridt er at forberede vores instans af en container. Du kan gøre det direkte i testen, men det ser bedre ud med en uafhængig klasse.
public class Postgres13TC extends PostgreSQLContainer {
private static final Postgres13TC TC = new Postgres13TC();
private Postgres13TC() {
super("postgres:13.2");
}
public static Postgres13TC getInstance() {
return TC;
}
@Override
public void start() {
super.start();
System.setProperty("DB_URL", TC.getJdbcUrl());
System.setProperty("DB_USERNAME", TC.getUsername());
System.setProperty("DB_PASSWORD", TC.getPassword());
}
@Override
public void stop() {
// gør ingenting. Dette er en delt instans. Lad JVM håndtere denne operation.
}
}
I begyndelsen af testene opretter vi en forekomst af Postgres13TC. Denne klasse kan håndtere oplysninger om vores container. Det vigtigste her er databasens forbindelsesstrenge og legitimationsoplysninger. Nu er det tid til at skrive en meget simpel test.
Jeg bruger JUnit 5 her. Annotation @Testcontainere er en del af de udvidelser, der styrer containere i testmiljøet. De finder alle felter med @Container annotation og henholdsvis start- og stopcontainere.
Test med Spring Boot
Som jeg nævnte før, bruger jeg Spring Boot i projektet. I dette tilfælde er vi nødt til at skrive lidt mere kode. Det første skridt er at oprette en ekstra konfigurationsklasse.
Denne klasse tilsidesætter de eksisterende egenskaber med værdier fra Testbeholder. De første tre egenskaber er standard Spring-egenskaber. De næste fem er yderligere, brugerdefinerede egenskaber, der kan bruges til at konfigurere andre ressourcer og udvidelser som f.eks. liquibase:
@SpringBootTest(webEnvironment = RANDOM_PORT) - markerer testen som en Spring Boot-test og starter Spring-konteksten.
@AutoConfigureTestDatabase(replace = NONE) - Disse annotationer siger, at Spring Test Extension ikke bør erstatte postgres-databasekonfigurationen med H2 i hukommelseskonfigurationen.
@ContextConfiguration(initializers = ContainerInit.class) - en ekstra forårskontekst konfiguration, hvor vi opsætter egenskaber fra Testbeholdere.
@Testcontainere - Som tidligere nævnt styrer denne annotation containerens livscyklus.
I dette eksempel bruger jeg reaktive repositorier, men det fungerer på samme måde med almindelige JDBC- og JPA-repositorier.
Nu kan vi køre denne test. Hvis det er første gang, den køres, skal motoren hente billeder fra docker.hub. Det kan tage et øjeblik. Derefter vil vi se, at to containere har kørt. Den ene er postgres, og den anden er Testcontainers controller. Den anden container styrer de kørende containere, og selv hvis JVM'en uventet stopper, slukker den containerne og rydder op i miljøet.
Lad os opsummere
Testbeholdere er meget brugervenlige værktøjer, som hjælper os med at skabe integrationstest, der bruger Docker-containere. Det giver os mere fleksibilitet og øger udviklingshastigheden. Korrekt opsætning af testkonfiguration reducerer den tid, det tager at sætte nye udviklere i gang. De behøver ikke at opsætte alle afhængigheder, de skal bare køre de skrevne tests med udvalgte konfigurationsfiler.