Dansk

Mestr test-drevet udvikling (TDD) i JavaScript. Denne omfattende guide dækker Red-Green-Refactor-cyklussen, praktisk implementering med Jest og bedste praksis for moderne udvikling.

Test-drevet udvikling i JavaScript: En omfattende guide for globale udviklere

Forestil dig dette scenarie: Du får til opgave at ændre en kritisk del af koden i et stort legacy-system. Du mærker en følelse af frygt. Vil din ændring ødelægge noget andet? Hvordan kan du være sikker på, at systemet stadig fungerer som forventet? Denne frygt for forandring er en almindelig lidelse inden for softwareudvikling, som ofte fører til langsom fremgang og skrøbelige applikationer. Men hvad nu hvis der var en måde at bygge software med selvtillid, som skaber et sikkerhedsnet, der fanger fejl, før de nogensinde når produktionen? Dette er løftet fra test-drevet udvikling (TDD).

TDD er ikke blot en testteknik; det er en disciplineret tilgang til softwaredesign og -udvikling. Den vender den traditionelle "skriv kode, test derefter"-model på hovedet. Med TDD skriver du en test, der fejler før du skriver produktionskoden, der får den til at bestå. Denne simple omvending har dybtgående konsekvenser for kodekvalitet, design og vedligeholdelighed. Denne guide vil give et omfattende, praktisk indblik i implementering af TDD i JavaScript, designet til et globalt publikum af professionelle udviklere.

Hvad er test-drevet udvikling (TDD)?

I sin kerne er test-drevet udvikling en udviklingsproces, der bygger på gentagelsen af en meget kort udviklingscyklus. I stedet for at skrive funktioner og derefter teste dem, insisterer TDD på, at testen skrives først. Denne test vil uundgåeligt fejle, fordi funktionen endnu ikke eksisterer. Udviklerens opgave er derefter at skrive den simplest mulige kode for at få netop den test til at bestå. Når den består, bliver koden ryddet op og forbedret. Denne fundamentale løkke er kendt som "Red-Green-Refactor"-cyklussen.

TDD's rytme: Red-Green-Refactor

Denne tretrinscyklus er hjerteslaget i TDD. At forstå og praktisere denne rytme er fundamentalt for at mestre teknikken.

Når cyklussen er fuldført for én lille del af funktionaliteten, begynder du forfra med en ny fejlende test for den næste del.

De tre love for TDD

Robert C. Martin (ofte kendt som "Uncle Bob"), en nøglefigur i den agile softwarebevægelse, definerede tre simple regler, der kodificerer TDD-disciplinen:

  1. Du må ikke skrive produktionskode, medmindre det er for at få en fejlende enhedstest til at bestå.
  2. Du må ikke skrive mere af en enhedstest, end hvad der er tilstrækkeligt til at den fejler; og kompileringsfejl er fejl.
  3. Du må ikke skrive mere produktionskode, end hvad der er tilstrækkeligt til at få den ene fejlende enhedstest til at bestå.

At følge disse love tvinger dig ind i Red-Green-Refactor-cyklussen og sikrer, at 100% af din produktionskode er skrevet for at opfylde et specifikt, testet krav.

Hvorfor bør du anvende TDD? Den globale business case

Selvom TDD tilbyder enorme fordele for individuelle udviklere, realiseres dens sande styrke på team- og forretningsniveau, især i globalt distribuerede miljøer.

Opsætning af dit JavaScript TDD-miljø

For at komme i gang med TDD i JavaScript har du brug for et par værktøjer. Det moderne JavaScript-økosystem tilbyder fremragende valgmuligheder.

Kernekomponenter i en test-stack

På grund af sin enkelhed og alt-i-en-natur vil vi bruge Jest i vores eksempler. Det er et fremragende valg for teams, der søger en "nul-konfigurations"-oplevelse.

Trin-for-trin opsætning med Jest

Lad os oprette et nyt projekt til TDD.

1. Initialiser dit projekt: Åbn din terminal og opret en ny projektmappe.

mkdir js-tdd-project
cd js-tdd-project
npm init -y

2. Installer Jest: Tilføj Jest til dit projekt som en udviklingsafhængighed.

npm install --save-dev jest

3. Konfigurer test-scriptet: Åbn din `package.json`-fil. Find `"scripts"`-sektionen og rediger `"test"`-scriptet. Det kan også stærkt anbefales at tilføje et `"test:watch"`-script, som er uvurderligt for TDD-workflowet.

"scripts": {
  "test": "jest",
  "test:watch": "jest --watchAll"
}

`--watchAll`-flaget beder Jest om automatisk at køre tests igen, hver gang en fil gemmes. Dette giver øjeblikkelig feedback, hvilket er perfekt til Red-Green-Refactor-cyklussen.

Det var det! Dit miljø er klar. Jest vil automatisk finde testfiler, der hedder `*.test.js`, `*.spec.js`, eller som er placeret i en `__tests__`-mappe.

TDD i praksis: Opbygning af et `CurrencyConverter`-modul

Lad os anvende TDD-cyklussen på et praktisk, globalt forståeligt problem: konvertering af penge mellem valutaer. Vi bygger et `CurrencyConverter`-modul trin for trin.

Iteration 1: Simpel, fastkurs-konvertering

🔴 RØD: Skriv den første fejlende test

Vores første krav er at konvertere et specifikt beløb fra en valuta til en anden ved hjælp af en fast kurs. Opret en ny fil ved navn `CurrencyConverter.test.js`.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

describe('CurrencyConverter', () => {
  it('bør konvertere et beløb fra USD til EUR korrekt', () => {
    // Forbered
    const amount = 10; // 10 USD
    const expected = 9.2; // Antager en fast kurs på 1 USD = 0,92 EUR

    // Handling
    const result = CurrencyConverter.convert(amount, 'USD', 'EUR');

    // Validering
    expect(result).toBe(expected);
  });
});

Kør nu test-watcheren fra din terminal:

npm run test:watch

Testen vil fejle spektakulært. Jest vil rapportere noget i retning af `TypeError: Cannot read properties of undefined (reading 'convert')`. Dette er vores RØDE tilstand. Testen fejler, fordi `CurrencyConverter` ikke eksisterer.

🟢 GRØN: Skriv den simpleste kode for at bestå

Lad os nu få testen til at bestå. Opret `CurrencyConverter.js`.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Så snart du gemmer denne fil, vil Jest køre testen igen, og den vil blive GRØN. Vi har skrevet den absolut minimale kode for at opfylde testens krav.

🔵 REFACTOR: Forbedr koden

Koden er simpel, men vi kan allerede tænke på forbedringer. Det indlejrede `rates`-objekt er lidt stift. For nu er det rent nok. Det vigtigste er, at vi har en fungerende funktion beskyttet af en test. Lad os gå videre til det næste krav.

Iteration 2: Håndtering af ukendte valutaer

🔴 RØD: Skriv en test for en ugyldig valuta

Hvad skal der ske, hvis vi prøver at konvertere til en valuta, vi ikke kender? Det bør sandsynligvis kaste en fejl. Lad os definere denne adfærd i en ny test i `CurrencyConverter.test.js`.

// I CurrencyConverter.test.js, inde i describe-blokken

it('bør kaste en fejl for ukendte valutaer', () => {
  // Forbered
  const amount = 10;

  // Handling & Validering
  // Vi indkapsler funktionskaldet i en arrow-funktion, for at Jests toThrow kan fungere.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Unknown currency: XYZ');
});

Gem filen. Test-køreren viser straks en ny fejl. Den er RØD, fordi vores kode ikke kaster en fejl; den forsøger at tilgå `rates['USD']['XYZ']`, hvilket resulterer i en `TypeError`. Vores nye test har korrekt identificeret denne fejl.

🟢 GRØN: Få den nye test til at bestå

Lad os ændre `CurrencyConverter.js` for at tilføje valideringen.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92,
    GBP: 0.80
  },
  EUR: {
    USD: 1.08
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    if (!rates[from] || !rates[from][to]) {
      // Bestem hvilken valuta der er ukendt for en bedre fejlmeddelelse
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Unknown currency: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Gem filen. Begge tests består nu. Vi er tilbage til GRØN.

🔵 REFACTOR: Ryd op

Vores `convert`-funktion vokser. Valideringslogikken er blandet med beregningen. Vi kunne udtrække valideringen til en separat privat funktion for at forbedre læsbarheden, men for nu er den stadig håndterbar. Nøglen er, at vi har friheden til at foretage disse ændringer, fordi vores tests vil fortælle os, hvis vi ødelægger noget.

Iteration 3: Asynkron hentning af kurser

At hardcode kurser er ikke realistisk. Lad os refaktorere vores modul til at hente kurser fra et (mocket) eksternt API.

🔴 RØD: Skriv en asynkron test, der mocker et API-kald

Først skal vi omstrukturere vores konverter. Den skal nu være en klasse, som vi kan instantiere, måske med en API-klient. Vi skal også mocke `fetch`-API'et. Jest gør dette nemt.

Lad os omskrive vores testfil for at imødekomme denne nye, asynkrone virkelighed. Vi starter med at teste den vellykkede sti igen.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

// Mock den eksterne afhængighed
global.fetch = jest.fn();

beforeEach(() => {
  // Ryd mock-historik før hver test
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('bør hente kurser og konvertere korrekt', async () => {
    // Forbered
    // Mock det succesfulde API-svar
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

    const converter = new CurrencyConverter('https://api.exchangerates.com');
    const amount = 10; // 10 USD

    // Handling
    const result = await converter.convert(amount, 'USD', 'EUR');

    // Validering
    expect(result).toBe(9.2);
    expect(fetch).toHaveBeenCalledTimes(1);
    expect(fetch).toHaveBeenCalledWith('https://api.exchangerates.com/latest?base=USD');
  });

  // Vi ville også tilføje tests for API-fejl osv.
});

At køre dette vil resultere i et hav af RØDT. Vores gamle `CurrencyConverter` er ikke en klasse, har ikke en `async`-metode og bruger ikke `fetch`.

🟢 GRØN: Implementer den asynkrone logik

Lad os nu omskrive `CurrencyConverter.js` for at opfylde testens krav.

// CurrencyConverter.js
class CurrencyConverter {
  constructor(apiUrl) {
    this.apiUrl = apiUrl;
  }

  async convert(amount, from, to) {
    const response = await fetch(`${this.apiUrl}/latest?base=${from}`);
    if (!response.ok) {
      throw new Error('Failed to fetch exchange rates.');
    }

    const data = await response.json();
    const rate = data.rates[to];

    if (!rate) {
      throw new Error(`Unknown currency: ${to}`);
    }

    // Simpel afrunding for at undgå floating point-problemer i tests
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Når du gemmer, bør testen blive GRØN. Bemærk, at vi også tilføjede afrundingslogik for at håndtere unøjagtigheder med floating-point, et almindeligt problem i finansielle beregninger.

🔵 REFACTOR: Forbedr den asynkrone kode

`convert`-metoden gør meget: henter, håndterer fejl, parser og beregner. Vi kunne refaktorere dette ved at oprette en separat `RateFetcher`-klasse, der kun er ansvarlig for API-kommunikationen. Vores `CurrencyConverter` ville så bruge denne fetcher. Dette følger Single Responsibility Principle og gør begge klasser lettere at teste og vedligeholde. TDD guider os mod dette renere design.

Almindelige TDD-mønstre og anti-mønstre

Når du praktiserer TDD, vil du opdage mønstre, der fungerer godt, og anti-mønstre, der skaber friktion.

Gode mønstre at følge

Anti-mønstre at undgå

TDD i den bredere udviklingslivscyklus

TDD eksisterer ikke i et vakuum. Det integreres smukt med moderne Agile- og DevOps-praksisser, især for globale teams.

Konklusion: Din rejse med TDD

Test-drevet udvikling er mere end en teststrategi—det er et paradigmeskift i, hvordan vi griber softwareudvikling an. Det fremmer en kultur af kvalitet, selvtillid og samarbejde. Red-Green-Refactor-cyklussen giver en stabil rytme, der guider dig mod ren, robust og vedligeholdelig kode. Den resulterende test-suite bliver et sikkerhedsnet, der beskytter dit team mod regressioner, og en levende dokumentation, der onboarder nye medlemmer.

Læringskurven kan føles stejl, og det indledende tempo kan virke langsommere. Men det langsigtede udbytte i form af reduceret fejlfindingstid, forbedret softwaredesign og øget udviklertillid er umåleligt. Rejsen til at mestre TDD er en rejse præget af disciplin og praksis.

Start i dag. Vælg én lille, ikke-kritisk funktion i dit næste projekt og forpligt dig til processen. Skriv testen først. Se den fejle. Få den til at bestå. Og så, vigtigst af alt, refactorer. Oplev den selvtillid, der kommer fra en grøn test-suite, og du vil snart undre dig over, hvordan du nogensinde har bygget software på nogen anden måde.

Test-drevet udvikling i JavaScript: En omfattende guide for globale udviklere | MLOG