Java >> Java tutorial >  >> Java

Introduktion til Java TDD – del 1

Velkommen til en introduktion i Testdrevet udvikling (TDD) serie. Vi vil tale om Java og JUnit i forbindelse med TDD, men det er kun værktøjer. Hovedformålet med artiklen er at give dig en omfattende forståelse af TDD uanset programmeringssprog og testramme.

Hvis du ikke bruger TDD i dit projekt, er du enten doven, eller også ved du simpelthen ikke, hvordan TDD fungerer. Undskyldninger om manglende tid gælder ikke her.

Om dette indlæg

I dette indlæg vil jeg forklare, hvad TDD er, og hvordan det kan bruges i Java. Hvilken plads enhedstest tager i TDD. Hvad du skal dække med dine enhedstests. Og endelig hvilke principper du skal overholde for at kunne skrive gode og effektive enhedstests.

Hvis du allerede ved alt om TDD i Java, men du er interesseret i eksempler og tutorials, anbefaler jeg dig at springe denne del over og fortsætte med en næste (den vil blive offentliggjort om en uge efter denne).

Hvad er TDD?

Hvis nogen beder mig om at forklare TDD med få ord, siger jeg, at TDD er en udvikling af test før en funktionsimplementering. Du kan argumentere:det er svært at teste ting, som ikke eksisterer endnu. Og sandsynligvis vil Kent Beck give dig en lussing for dette.

Så hvordan er det muligt? Det kan beskrives ved følgende trin:

1. Du læser og forstår kravene til en bestemt funktion.
2. Du udvikler sæt af tests, som kontrollerer funktionen. Alle testene er røde på grund af manglende funktionsimplementering.
3. Du udvikler funktionen, indtil alle tests bliver grønne.
4. Refaktorering af koden.

TDD kræver en anden måde at tænke på, så for at begynde at arbejde efter den skal du glemme en måde, du har udviklet en kode på før. Denne proces er meget hård. Og det er endnu sværere, hvis du ikke ved, hvordan man skriver enhedstests. Men det er det værd.

At udvikle med TDD har værdifulde fordele:

1. Du har en bedre forståelse af en funktion, du implementerer.
2. Du har robuste indikatorer for en funktions fuldstændighed.
3. En kode er dækket af test og har færre chancer for at blive ødelagt af rettelser eller nye funktioner.

En omkostning ved disse fordele er ret høj - besværet forbundet med at skifte til en ny udviklingsmåde og tid, som du bruger på at udvikle hver ny funktion. Det er en pris på kvalitet.

Så det er sådan TDD fungerer - skriv røde enhedstests, start med at implementere en funktion, gør testene grønne, udfør refactor af koden.

Sted for enhedstest i TDD

Da enhedstest er de mindste elementer i testautomatiseringspyramiden, er TDD baseret på dem. Ved hjælp af enhedstests kan vi kontrollere forretningslogik af enhver klasse. Det er nemt at skrive enhedstests, hvis du ved, hvordan man gør dette. Så hvad skal du teste med enhedstests, og hvordan skal du gøre det? Kender du svarene på disse spørgsmål? Jeg vil forsøge at illustrere svarene i en kortfattet form.

En enhedstest skal være så lille som muligt. Nej-nej tænk ikke på dette, da én test er for én metode. Denne sag er selvfølgelig også mulig. Men som regel indebærer én enhedstest påkaldelse af flere metoder. Dette kaldes test af adfærd.

Lad os overveje kontoklassen:

public class Account {

    private String id = RandomStringUtils.randomAlphanumeric(6);
    private boolean status;
    private String zone;
    private BigDecimal amount;

    public Account() {
        status = true;
        zone = Zone.ZONE_1.name();
        amount = createBigDecimal(0.00);
    }

    public Account(boolean status, Zone zone, double amount) {
        this.status = status;
        this.zone = zone.name();
        this.amount = createBigDecimal(amount);
    }

    public enum Zone {
        ZONE_1, ZONE_2, ZONE_3
    }

    public static BigDecimal createBigDecimal(double total) {
        return new BigDecimal(total).setScale(2, BigDecimal.ROUND_HALF_UP);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("id: ").append(getId())
                .append("\nstatus: ")
                .append(getStatus())
                .append("\nzone: ")
                .append(getZone())
                .append("\namount: ")
                .append(getAmount());
        return sb.toString();
    }

    public String getId() {
        return id;
    }

    public boolean getStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getZone() {
        return zone;
    }

    public void setZone(String zone) {
        this.zone = zone;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        if (amount.signum() < 0)
            throw new IllegalArgumentException("The amount does not accept negative values");
        this.amount = amount;
    }
}

Der er 4 getter-metoder i klassen. Vær ekstra opmærksom på dem. Hvis vi opretter en separat enhedstest for hver gettermetode, får vi for mange overflødige kodelinjer. Denne situation kan håndteres ved hjælp af en adfærdstest . Forestil dig, at vi skal teste rigtigheden af ​​objektskabelsen ved hjælp af en af ​​dens konstruktører. Hvordan kontrollerer man, at objektet er oprettet som forventet? Vi skal kontrollere en værdi af hvert felt. Derfor kan gettere bruges i dette scenarie.

Opret små og hurtige enhedstests , fordi de skal udføres hver gang før commit til et git repository og ny build til en server. Du kan overveje et eksempel med reelle tal for at forstå vigtigheden af ​​enhedstesthastighed. Lad os antage, at et projekt har 1000 enhedstests. Hver af dem tager 100 ms. Som et resultat tager kørsel af alle test 1 minut og 40 sekunder.

Faktisk er 100ms for lang tid til en enhedstest, så du skal reducere en løbetid ved at anvende forskellige regler og teknikker, f.eks. udfør ikke databaseforbindelse i enhedstest (per definition er enhedstests isolerede) eller udfør initialiseringer af dyre objekter i @Before-blokken.

Vælg gode navne til enhedstests . Et navn på en test kan være så langt, som du vil, men det skal repræsentere, hvilken verifikation testen gør. Hvis jeg for eksempel skal teste en standardkonstruktør af Account-klassen, vil jeg navngive den defaultConstructorTest . Et mere nyttigt råd til at vælge en tests navn er at skrive en testlogik, før du navngiver testen. Mens du udvikler en test, forstår du, hvad der sker inde i den, som følge heraf bliver det lettere at sammensætte navn.

Enheds-test bør være forudsigelige . Dette er det mest åbenlyse krav. Jeg vil forklare det som eksempel. For at kontrollere driften af ​​pengeoverførsel (med 5% gebyr) skal du vide, hvilket beløb du sender, og hvor meget du får som output. Dette testscenarie kan implementeres som afsendelse af 100 $ og modtagelse af 95 $.

Og endelig bør enhedstests være velkornede . Når du sætter et logisk scenarie pr. test, kan du opnå en informativ feedback fra dine tests. Og i tilfælde af en enkelt fejl, vil du ikke miste information om resten af ​​funktionaliteten.

Alle disse anbefalinger har til formål at forbedre enhedstestdesignet. Men der er endnu en ting, du skal vide – det grundlæggende i testdesignteknik.

Grundlæggende om testdesignteknik

At skrive test er umuligt uden testdata. For eksempel, når du tester et pengeoverførselssystem, angiver du et beløb i feltet Send penge. Beløbet er et testdata i dette tilfælde. Så hvilke værdier skal du vælge til test? For at besvare dette spørgsmål skal vi gennemgå de mest populære testdesignteknikker. Det generelle formål med testdesignteknik er at forenkle sammensætningen af ​​testdata.

Lad os først foregive, at vi kun kan sende positive, heltalsbeløb. Vi kan heller ikke sende mere end 1000. Det kan præsenteres som:

0 < amount <= 1000; amount in integer

Alle vores testscenarier kan opdeles i to grupper:positive og negative scenarier. Den første er til testdata, som er tilladt af et system og fører til vellykkede resultater. Den anden er til såkaldte "fejlscenarier", når vi bruger upassende data til interaktion med systemet.

Ifølge klasser af ækvivalensteknik vi kan vælge et enkelt tilfældigt heltal fra området (0; 1000]. Lad det være 500. Da systemet fungerer for 500, burde det fungere fint for alle heltal fra området. Så 500 er en gyldig værdi. Vi kan også vælge ugyldigt input fra området. Det kan være et hvilket som helst tal med flydende komma, f.eks. 125,50

Så skal vi henvise til grænsetestteknikken . Ifølge den skal vi vælge 2 gyldige værdier fra venstre og højre side af området. I vores tilfælde tager vi 1 som det laveste tilladte positive heltal og 1000 fra højre side.
Næste trin er at vælge 2 ugyldige værdier på grænser. Så det er 0 og 1001.

Så i sidste ende har vi 6 værdier, som vi skal bruge i enhedstesten:

  • (1, 500, 1000) – for positive scenarier
  • (0, 125,50, 1001) – for negative scenarier

Oversigt

I dette indlæg forsøgte jeg at forklare alle aspekter af TDD og vise, hvor vigtige enhedstests er i TDD. Så jeg håber efter så detaljeret og lang bla-bla teori, at vi kan fortsætte med praksis. I min næste artikel vil jeg demonstrere, hvordan man udvikler tests før en funktionalitet. Vi vil gøre det trin for trin, begyndende fra en dokumentationsanalyse og afslutte med en koderefaktorering.

Vær sikker på, at alle test bliver grønne :)

Java tag