Hrvatski

Savladajte razvoj vođen testovima (TDD) u JavaScriptu. Ovaj sveobuhvatni vodič pokriva Red-Green-Refactor ciklus, praktičnu implementaciju s Jestom i najbolje prakse za moderni razvoj.

Razvoj vođen testovima (TDD) u JavaScriptu: Sveobuhvatni vodič za globalne programere

Zamislite ovaj scenarij: zaduženi ste za izmjenu kritičnog dijela koda u velikom, naslijeđenom sustavu. Osjećate strepnju. Hoće li vaša promjena pokvariti nešto drugo? Kako možete biti sigurni da sustav i dalje radi kako je predviđeno? Taj strah od promjene česta je boljka u razvoju softvera, koja često dovodi do sporog napretka i krhkih aplikacija. Ali što ako postoji način za izgradnju softvera s povjerenjem, stvarajući sigurnosnu mrežu koja hvata greške prije nego što uopće stignu u produkciju? To je obećanje razvoja vođenog testovima (Test-Driven Development - TDD).

TDD nije samo tehnika testiranja; to je discipliniran pristup dizajnu i razvoju softvera. On preokreće tradicionalni model "napiši kod, pa testiraj". S TDD-om, pišete test koji ne uspijeva prije nego što napišete produkcijski kod koji će ga zadovoljiti. Ova jednostavna inverzija ima duboke implikacije na kvalitetu koda, dizajn i održivost. Ovaj vodič pružit će sveobuhvatan, praktičan pogled na implementaciju TDD-a u JavaScriptu, namijenjen globalnoj publici profesionalnih programera.

Što je razvoj vođen testovima (TDD)?

U svojoj srži, razvoj vođen testovima je razvojni proces koji se oslanja na ponavljanje vrlo kratkog razvojnog ciklusa. Umjesto pisanja funkcionalnosti pa njihovog testiranja, TDD inzistira na tome da se test napiše prvi. Taj će test neizbježno pasti jer funkcionalnost još ne postoji. Zadatak programera je tada napisati najjednostavniji mogući kod kako bi taj specifični test prošao. Jednom kada prođe, kod se čisti i poboljšava. Ova temeljna petlja poznata je kao "Red-Green-Refactor" ciklus.

Ritam TDD-a: Red-Green-Refactor

Ovaj ciklus od tri koraka srce je TDD-a. Razumijevanje i prakticiranje ovog ritma temeljno je za ovladavanje tehnikom.

Nakon što je ciklus završen za jedan mali dio funkcionalnosti, započinjete ponovno s novim neuspješnim testom za sljedeći dio.

Tri zakona TDD-a

Robert C. Martin (često poznat kao "Ujak Bob"), ključna figura u agilnom pokretu razvoja softvera, definirao je tri jednostavna pravila koja kodificiraju TDD disciplinu:

  1. Ne smijete pisati produkcijski kod osim ako to nije potrebno da bi prošao neuspješni jedinični test.
  2. Ne smijete napisati više koda u jediničnom testu nego što je dovoljno da on ne prođe; a greške pri kompajliranju se također smatraju neuspjehom.
  3. Ne smijete napisati više produkcijskog koda nego što je dovoljno da prođe taj jedan neuspješni jedinični test.

Slijeđenje ovih zakona prisiljava vas na Red-Green-Refactor ciklus i osigurava da je 100% vašeg produkcijskog koda napisano kako bi zadovoljilo specifičan, testiran zahtjev.

Zašto biste trebali usvojiti TDD? Globalni poslovni argumenti

Iako TDD nudi ogromne koristi pojedinim programerima, njegova prava snaga ostvaruje se na razini tima i poslovanja, posebno u globalno distribuiranim okruženjima.

Postavljanje vašeg JavaScript TDD okruženja

Da biste započeli s TDD-om u JavaScriptu, potrebno vam je nekoliko alata. Moderni JavaScript ekosustav nudi izvrsne izbore.

Osnovne komponente okruženja za testiranje

Zbog svoje jednostavnosti i sve-u-jedan prirode, za naše primjere koristit ćemo Jest. To je izvrstan izbor za timove koji traže iskustvo "nulte konfiguracije".

Korak-po-korak postavljanje s Jestom

Postavimo novi projekt za TDD.

1. Inicijalizirajte svoj projekt: Otvorite terminal i stvorite novi direktorij projekta.

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

2. Instalirajte Jest: Dodajte Jest u svoj projekt kao razvojnu ovisnost (development dependency).

npm install --save-dev jest

3. Konfigurirajte skriptu za testiranje: Otvorite datoteku `package.json`. Pronađite odjeljak `"scripts"` i izmijenite skriptu `"test"`. Također se preporučuje dodavanje skripte `"test:watch"`, koja je neprocjenjiva za TDD tijek rada.

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

Zastavica `--watchAll` govori Jestu da automatski ponovno pokrene testove svaki put kad se datoteka spremi. To pruža trenutne povratne informacije, što je savršeno za Red-Green-Refactor ciklus.

To je to! Vaše okruženje je spremno. Jest će automatski pronaći testne datoteke koje se zovu `*.test.js`, `*.spec.js` ili se nalaze u direktoriju `__tests__`.

TDD u praksi: Izrada modula `CurrencyConverter`

Primijenimo TDD ciklus na praktičan, globalno razumljiv problem: konverziju novca između valuta. Izgradit ćemo modul `CurrencyConverter` korak po korak.

Iteracija 1: Jednostavna konverzija s fiksnim tečajem

🔴 CRVENO: Napišite prvi neuspješni test

Naš prvi zahtjev je pretvoriti određeni iznos iz jedne valute u drugu koristeći fiksni tečaj. Stvorite novu datoteku pod nazivom `CurrencyConverter.test.js`.

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

describe('CurrencyConverter', () => {
  it('trebao bi ispravno pretvoriti iznos iz USD u EUR', () => {
    // Priprema
    const amount = 10; // 10 USD
    const expected = 9.2; // Pretpostavljajući fiksni tečaj od 1 USD = 0.92 EUR

    // Izvršavanje
    const result = CurrencyConverter.convert(amount, 'USD', 'EUR');

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

Sada pokrenite praćenje testova iz svog terminala:

npm run test:watch

Test će spektakularno pasti. Jest će izvijestiti nešto poput `TypeError: Cannot read properties of undefined (reading 'convert')`. Ovo je naše CRVENO stanje. Test ne uspijeva jer `CurrencyConverter` ne postoji.

🟢 ZELENO: Napišite najjednostavniji kod da test prođe

Sada, učinimo da test prođe. Stvorite `CurrencyConverter.js`.

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

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

module.exports = CurrencyConverter;

Čim spremite ovu datoteku, Jest će ponovno pokrenuti test i on će postati ZELEN. Napisali smo apsolutni minimum koda da zadovoljimo zahtjev testa.

🔵 PLAVO: Refaktorirajte kod

Kod je jednostavan, ali već možemo razmišljati o poboljšanjima. Ugniježđeni objekt `rates` je pomalo krut. Za sada je dovoljno čist. Najvažnije je da imamo radnu funkcionalnost zaštićenu testom. Prijeđimo na sljedeći zahtjev.

Iteracija 2: Rukovanje nepoznatim valutama

🔴 CRVENO: Napišite test za nevažeću valutu

Što bi se trebalo dogoditi ako pokušamo izvršiti konverziju u valutu koju ne poznajemo? Vjerojatno bi trebalo baciti grešku. Definirajmo to ponašanje u novom testu u `CurrencyConverter.test.js`.

// U CurrencyConverter.test.js, unutar describe bloka

it('trebao bi baciti grešku za nepoznate valute', () => {
  // Priprema
  const amount = 10;

  // Izvršavanje i Provjera
  // Poziv funkcije omotavamo u 'arrow' funkciju kako bi Jestova toThrow metoda radila.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Unknown currency: XYZ');
});

Spremite datoteku. Pokretač testova odmah prikazuje novi neuspjeh. CRVENO je jer naš kod ne baca grešku; pokušava pristupiti `rates['USD']['XYZ']`, što rezultira `TypeError`. Naš novi test je ispravno identificirao ovu manu.

🟢 ZELENO: Učinite da novi test prođe

Izmijenimo `CurrencyConverter.js` kako bismo dodali provjeru.

// 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]) {
      // Odredite koja je valuta nepoznata za bolju poruku o grešci
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Unknown currency: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Spremite datoteku. Oba testa sada prolaze. Vratili smo se na ZELENO.

🔵 PLAVO: Očistite kod

Naša funkcija `convert` raste. Logika provjere pomiješana je s izračunom. Mogli bismo izdvojiti provjeru u zasebnu privatnu funkciju kako bismo poboljšali čitljivost, ali za sada je još uvijek upravljiva. Ključno je da imamo slobodu unositi te promjene jer će nam naši testovi reći ako nešto pokvarimo.

Iteracija 3: Asinkrono dohvaćanje tečajeva

Fiksno kodiranje tečajeva nije realno. Refaktorirajmo naš modul da dohvaća tečajeve s (lažiranog) vanjskog API-ja.

🔴 CRVENO: Napišite asinkroni test koji lažira (mocka) API poziv

Prvo, moramo restrukturirati naš konverter. Sada će morati biti klasa koju možemo instancirati, možda s API klijentom. Također ćemo morati lažirati `fetch` API. Jest to olakšava.

Prepišimo našu testnu datoteku kako bismo se prilagodili ovoj novoj, asinkronoj stvarnosti. Počet ćemo ponovnim testiranjem sretnog puta.

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

// Lažiramo (mockamo) vanjsku ovisnost
global.fetch = jest.fn();

beforeEach(() => {
  // Očisti povijest lažiranja (mock) prije svakog testa
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('trebao bi dohvatiti tečajeve i ispravno izvršiti konverziju', async () => {
    // Priprema
    // Lažiramo (mockamo) uspješan API odgovor
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

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

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

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

  // Također bismo dodali testove za neuspjehe API-ja itd.
});

Pokretanje ovoga rezultirat će morem CRVENE boje. Naš stari `CurrencyConverter` nije klasa, nema `async` metodu i ne koristi `fetch`.

🟢 ZELENO: Implementirajte asinkronu logiku

Sada prepišimo `CurrencyConverter.js` kako bi zadovoljio zahtjeve testa.

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

    // Jednostavno zaokruživanje kako bi se izbjegli problemi s pomičnim zarezom u testovima
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Kada spremite, test bi trebao postati ZELEN. Imajte na umu da smo također dodali logiku zaokruživanja kako bismo se nosili s netočnostima s pomičnim zarezom, što je čest problem u financijskim izračunima.

🔵 PLAVO: Poboljšajte asinkroni kod

Metoda `convert` radi mnogo toga: dohvaća, obrađuje greške, parsira i izračunava. Mogli bismo to refaktorirati stvaranjem zasebne klase `RateFetcher` odgovorne samo za API komunikaciju. Naš `CurrencyConverter` bi tada koristio taj dohvaćač. To slijedi Načelo jedinstvene odgovornosti (Single Responsibility Principle) i čini obje klase lakšima za testiranje i održavanje. TDD nas vodi prema ovom čišćem dizajnu.

Uobičajeni TDD obrasci i anti-obrasci

Kako budete prakticirali TDD, otkrit ćete obrasce koji dobro funkcioniraju i anti-obrasce koji uzrokuju probleme.

Dobri obrasci koje treba slijediti

Anti-obrasci koje treba izbjegavati

TDD u širem razvojnom ciklusu

TDD ne postoji u vakuumu. Izvrsno se integrira s modernim agilnim i DevOps praksama, posebno za globalne timove.

Zaključak: Vaše putovanje s TDD-om

Razvoj vođen testovima više je od strategije testiranja—to je promjena paradigme u načinu na koji pristupamo razvoju softvera. Potiče kulturu kvalitete, samopouzdanja i suradnje. Ciklus Red-Green-Refactor pruža stalan ritam koji vas vodi prema čistom, robusnom i održivom kodu. Rezultirajući skup testova postaje sigurnosna mreža koja štiti vaš tim od regresija i živuća dokumentacija koja uvodi nove članove u posao.

Krivulja učenja može se činiti strmom, a početni tempo sporijim. Ali dugoročne dividende u smanjenom vremenu otklanjanja pogrešaka, poboljšanom dizajnu softvera i povećanom samopouzdanju programera su nemjerljive. Put do ovladavanja TDD-om je put discipline i prakse.

Počnite danas. Odaberite jednu malu, nekritičnu funkcionalnost u svom sljedećem projektu i posvetite se procesu. Prvo napišite test. Gledajte kako ne uspijeva. Učinite da prođe. I onda, najvažnije, refaktorirajte. Iskusite samopouzdanje koje dolazi iz zelenog skupa testova i uskoro ćete se pitati kako ste ikada gradili softver na bilo koji drugi način.