Suomi

Opi testivetoinen kehitys (TDD) JavaScriptissä. Tämä kattava opas käsittelee Red-Green-Refactor-syklin, käytännön toteutuksen Jestillä ja modernin kehityksen parhaat käytännöt.

Testivetoinen kehitys JavaScriptissä: Kattava opas globaaleille kehittäjille

Kuvittele tämä tilanne: olet saanut tehtäväksesi muokata kriittistä koodinpätkää suuressa, vanhassa järjestelmässä. Tunnet kauhun tunnetta. Rikkooko muutoksesi jotain muuta? Miten voit olla varma, että järjestelmä toimii edelleen kuten on tarkoitettu? Tämä muutoksen pelko on yleinen vaiva ohjelmistokehityksessä, ja se johtaa usein hitaaseen edistymiseen ja hauraisiin sovelluksiin. Mutta entä jos olisi olemassa tapa rakentaa ohjelmistoja luottavaisin mielin ja luoda turvaverkko, joka nappaa virheet ennen kuin ne koskaan pääsevät tuotantoon? Tämä on testivetoisen kehityksen (TDD) lupaus.

TDD ei ole pelkästään testaustekniikka; se on kurinalainen lähestymistapa ohjelmistosuunnitteluun ja -kehitykseen. Se kääntää perinteisen "kirjoita koodi, sitten testaa" -mallin päälaelleen. TDD:ssä kirjoitat testin, joka epäonnistuu ennen kuin kirjoitat tuotantokoodin, joka saa sen läpäisemään testin. Tällä yksinkertaisella käännöksellä on syvällisiä vaikutuksia koodin laatuun, suunnitteluun ja ylläpidettävyyteen. Tämä opas tarjoaa kattavan ja käytännöllisen katsauksen TDD:n toteuttamiseen JavaScriptissä, suunniteltuna ammattikehittäjien globaalille yleisölle.

Mitä on testivetoinen kehitys (TDD)?

Ytimessään testivetoinen kehitys on kehitysprosessi, joka perustuu hyvin lyhyen kehityssyklin toistamiseen. Sen sijaan, että ominaisuudet kirjoitettaisiin ja sitten testattaisiin, TDD vaatii, että testi kirjoitetaan ensin. Tämä testi epäonnistuu väistämättä, koska ominaisuutta ei ole vielä olemassa. Kehittäjän tehtävänä on sitten kirjoittaa yksinkertaisin mahdollinen koodi, jotta juuri kyseinen testi läpäistään. Kun testi on läpäisty, koodi siistitään ja parannetaan. Tämä perussilmukka tunnetaan nimellä "Red-Green-Refactor"-sykli.

TDD:n rytmi: Red-Green-Refactor

Tämä kolmivaiheinen sykli on TDD:n sydämenlyönti. Tämän rytmin ymmärtäminen ja harjoittelu on perustavanlaatuista tekniikan hallitsemiseksi.

Kun sykli on valmis yhdelle pienelle toiminnallisuudelle, aloitat alusta uudella epäonnistuvalla testillä seuraavaa osaa varten.

TDD:n kolme lakia

Robert C. Martin (tunnetaan usein nimellä "Uncle Bob"), keskeinen hahmo ketterässä ohjelmistokehitysliikkeessä, määritteli kolme yksinkertaista sääntöä, jotka kodifioivat TDD-kurinalaisuuden:

  1. Et saa kirjoittaa mitään tuotantokoodia, ellei se ole tarkoitettu saamaan epäonnistuvaa yksikkötestiä läpäistyksi.
  2. Et saa kirjoittaa enempää yksikkötestiä kuin mikä riittää sen epäonnistumiseen; ja käännösvirheet ovat epäonnistumisia.
  3. Et saa kirjoittaa enempää tuotantokoodia kuin mikä riittää yhden epäonnistuvan yksikkötestin läpäisemiseen.

Näiden lakien noudattaminen pakottaa sinut Red-Green-Refactor-sykliin ja varmistaa, että 100 % tuotantokoodistasi on kirjoitettu täyttämään tietty, testattu vaatimus.

Miksi TDD kannattaa ottaa käyttöön? Globaali liiketoimintaperuste

Vaikka TDD tarjoaa valtavia etuja yksittäisille kehittäjille, sen todellinen voima realisoituu tiimi- ja liiketoimintatasolla, erityisesti globaalisti hajautetuissa ympäristöissä.

JavaScript TDD -ympäristön pystyttäminen

Aloittaaksesi TDD:n käytön JavaScriptissä tarvitset muutamia työkaluja. Moderni JavaScript-ekosysteemi tarjoaa erinomaisia vaihtoehtoja.

Testauspinon ydinkomponentit

Sen yksinkertaisuuden ja kaiken kattavan luonteen vuoksi käytämme esimerkeissämme Jestiä. Se on erinomainen valinta tiimeille, jotka etsivät "nollakonfiguraation" kokemusta.

Vaiheittainen asennus Jestillä

Pystytetään uusi projekti TDD:tä varten.

1. Alusta projektisi: Avaa pääte ja luo uusi projektihakemisto.

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

2. Asenna Jest: Lisää Jest projektiisi kehityksen aikaiseksi riippuvuudeksi.

npm install --save-dev jest

3. Määritä testiskripti: Avaa `package.json`-tiedostosi. Etsi `"scripts"`-osio ja muokkaa `"test"`-skriptiä. On myös erittäin suositeltavaa lisätä `"test:watch"`-skripti, joka on korvaamaton TDD-työnkulussa.

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

`--watchAll`-lippu kertoo Jestille, että sen tulee automaattisesti suorittaa testit uudelleen aina, kun tiedosto tallennetaan. Tämä antaa välitöntä palautetta, mikä on täydellistä Red-Green-Refactor-sykliä varten.

Siinä kaikki! Ympäristösi on valmis. Jest löytää automaattisesti testitiedostot, joiden nimi on `*.test.js`, `*.spec.js` tai jotka sijaitsevat `__tests__`-hakemistossa.

TDD käytännössä: `CurrencyConverter`-moduulin rakentaminen

Sovelletaan TDD-sykliä käytännölliseen, maailmanlaajuisesti ymmärrettävään ongelmaan: rahan muuntamiseen valuuttojen välillä. Rakennamme `CurrencyConverter`-moduulin askel askeleelta.

Iteraatio 1: Yksinkertainen, kiinteän kurssin muunnos

🔴 PUNAINEN: Kirjoita ensimmäinen epäonnistuva testi

Ensimmäinen vaatimuksemme on muuntaa tietty summa yhdestä valuutasta toiseen kiinteällä kurssilla. Luo uusi tiedosto nimeltä `CurrencyConverter.test.js`.

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

describe('CurrencyConverter', () => {
  it('should convert an amount from USD to EUR correctly', () => {
    // Arrange
    const amount = 10; // 10 USD
    const expected = 9.2; // Assuming a fixed rate of 1 USD = 0.92 EUR

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

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

Suorita nyt testien tarkkailija päätteestäsi:

npm run test:watch

Testi epäonnistuu näyttävästi. Jest raportoi jotain kuten `TypeError: Cannot read properties of undefined (reading 'convert')`. Tämä on meidän PUNAINEN tilamme. Testi epäonnistuu, koska `CurrencyConverter` ei ole olemassa.

🟢 VIHREÄ: Kirjoita yksinkertaisin koodi, joka läpäisee testin

Nyt tehdään testistä läpäisevä. Luo `CurrencyConverter.js`.

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

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

module.exports = CurrencyConverter;

Heti kun tallennat tämän tiedoston, Jest suorittaa testin uudelleen, ja se muuttuu VIHREÄKSI. Olemme kirjoittaneet ehdottoman vähimmäismäärän koodia täyttääksemme testin vaatimuksen.

🔵 REFAKTOROI: Paranna koodia

Koodi on yksinkertainen, mutta voimme jo miettiä parannuksia. Sisäkkäinen `rates`-olio on hieman jäykkä. Toistaiseksi se on riittävän siisti. Tärkeintä on, että meillä on toimiva ominaisuus, jota suojaa testi. Siirrytään seuraavaan vaatimukseen.

Iteraatio 2: Tuntemattomien valuuttojen käsittely

🔴 PUNAINEN: Kirjoita testi virheelliselle valuutalle

Mitä pitäisi tapahtua, jos yritämme muuntaa valuuttaan, jota emme tunne? Sen pitäisi todennäköisesti heittää virhe. Määritellään tämä käyttäytyminen uudessa testissä `CurrencyConverter.test.js`-tiedostossa.

// In CurrencyConverter.test.js, inside the describe block

it('should throw an error for unknown currencies', () => {
  // Arrange
  const amount = 10;

  // Act & Assert
  // We wrap the function call in an arrow function for Jest's toThrow to work.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Unknown currency: XYZ');
});

Tallenna tiedosto. Testin suorittaja näyttää heti uuden epäonnistumisen. Se on PUNAINEN, koska koodimme ei heitä virhettä; se yrittää käyttää `rates['USD']['XYZ']`, mikä johtaa `TypeError`-virheeseen. Uusi testimme on oikein tunnistanut tämän puutteen.

🟢 VIHREÄ: Saa uusi testi läpäisemään

Muokataan `CurrencyConverter.js`-tiedostoa lisätäksemme validoinnin.

// 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]) {
      // Determine which currency is unknown for a better error message
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Unknown currency: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Tallenna tiedosto. Molemmat testit läpäisevät nyt. Olemme takaisin VIHREÄSSÄ.

🔵 REFAKTOROI: Siisti koodi

`convert`-funktiomme kasvaa. Validointilogiikka on sekoittunut laskennan kanssa. Voisimme purkaa validoinnin erilliseen yksityiseen funktioon parantaaksemme luettavuutta, mutta toistaiseksi se on vielä hallittavissa. Tärkeintä on, että meillä on vapaus tehdä näitä muutoksia, koska testimme kertovat meille, jos rikomme jotain.

Iteraatio 3: Asynkroninen kurssien haku

Kurssien kovakoodaaminen ei ole realistista. Refaktoroidaan moduulimme hakemaan kurssit (mokatusta) ulkoisesta API:sta.

🔴 PUNAINEN: Kirjoita asynkroninen testi, joka mokkaa API-kutsun

Ensin meidän on uudelleenjärjesteltävä muuntimemme. Siitä tulee nyt luokka, jonka voimme instantioida, ehkä API-asiakasohjelman kanssa. Meidän on myös mokattava `fetch`-API. Jest tekee tästä helppoa.

Kirjoitetaan testitiedostomme uudelleen ottamaan huomioon tämä uusi, asynkroninen todellisuus. Aloitamme testaamalla jälleen onnistuneen polun.

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

// Mock the external dependency
global.fetch = jest.fn();

beforeEach(() => {
  // Clear mock history before each test
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('should fetch rates and convert correctly', async () => {
    // Arrange
    // Mock the successful API response
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

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

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

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

  // We'd also add tests for API failures, etc.
});

Tämän suorittaminen johtaa PUNAISEEN mereen. Vanha `CurrencyConverter` ei ole luokka, sillä ei ole `async`-metodia, eikä se käytä `fetch`-funktiota.

🟢 VIHREÄ: Toteuta asynkroninen logiikka

Nyt kirjoitetaan `CurrencyConverter.js` uudelleen vastaamaan testin vaatimuksia.

// 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}`);
    }

    // Simple rounding to avoid floating point issues in tests
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Kun tallennat, testin pitäisi muuttua VIHREÄKSI. Huomaa, että lisäsimme myös pyöristyslogiikan käsittelemään liukulukujen epätarkkuuksia, mikä on yleinen ongelma talouslaskelmissa.

🔵 REFAKTOROI: Paranna asynkronista koodia

`convert`-metodi tekee paljon: hakee, käsittelee virheitä, jäsentää ja laskee. Voisimme refaktoroida tämän luomalla erillisen `RateFetcher`-luokan, joka vastaa vain API-kommunikaatiosta. `CurrencyConverter` käyttäisi sitten tätä hakijaa. Tämä noudattaa yhden vastuun periaatetta (Single Responsibility Principle) ja tekee molemmista luokista helpompia testata ja ylläpitää. TDD ohjaa meitä kohti tätä siistimpää suunnittelua.

Yleiset TDD-mallit ja antimallit

Kun harjoittelet TDD:tä, huomaat malleja, jotka toimivat hyvin, ja antimallleja, jotka aiheuttavat kitkaa.

Hyvät mallit, joita noudattaa

Vältettävät antimallit

TDD osana laajempaa kehityksen elinkaarta

TDD ei ole olemassa tyhjiössä. Se integroituu kauniisti nykyaikaisiin Agile- ja DevOps-käytäntöihin, erityisesti globaaleille tiimeille.

Yhteenveto: Matkasi TDD:n parissa

Testivetoinen kehitys on enemmän kuin testaustrategia—se on paradigman muutos siinä, miten lähestymme ohjelmistokehitystä. Se edistää laadun, luottamuksen ja yhteistyön kulttuuria. Red-Green-Refactor-sykli tarjoaa tasaisen rytmin, joka ohjaa sinut kohti puhdasta, vankkaa ja ylläpidettävää koodia. Tuloksena oleva testisarja muuttuu turvaverkoksi, joka suojaa tiimiäsi regressioilta, ja eläväksi dokumentaatioksi, joka perehdyttää uusia jäseniä.

Oppimiskäyrä voi tuntua jyrkältä, ja alkuvauhti voi tuntua hitaammalta. Mutta pitkän aikavälin hyödyt vähentyneessä virheenkorjausajassa, parantuneessa ohjelmistosuunnittelussa ja lisääntyneessä kehittäjien luottamuksessa ovat mittaamattomia. Matka TDD:n hallitsemiseen on kurinalaisuuden ja harjoittelun matka.

Aloita tänään. Valitse yksi pieni, ei-kriittinen ominaisuus seuraavassa projektissasi ja sitoudu prosessiin. Kirjoita testi ensin. Katso sen epäonnistuvan. Saa se läpäisemään. Ja sitten, mikä tärkeintä, refaktoroi. Koe vihreän testisarjan tuoma luottamus, ja pian ihmettelet, miten olet koskaan rakentanut ohjelmistoja millään muulla tavalla.