Lietuvių

Įsisavinkite testais grįstą programavimą (TDD) su „JavaScript“. Šis išsamus vadovas apima „Red-Green-Refactor“ ciklą, praktinį diegimą su „Jest“ ir geriausias šiuolaikinio programavimo praktikas.

Testais grįstas programavimas (TDD) naudojant „JavaScript“: išsamus vadovas pasaulio programuotojams

Įsivaizduokite tokį scenarijų: jums pavesta modifikuoti kritinę kodo dalį didelėje, pasenusioje sistemoje. Jaučiate baimę. Ar jūsų pakeitimas ko nors nesugadins? Kaip galite būti tikri, kad sistema vis dar veikia taip, kaip numatyta? Ši pokyčių baimė yra dažna programinės įrangos kūrimo problema, dažnai lemianti lėtą pažangą ir trapias programas. Bet kas, jei būtų būdas kurti programinę įrangą su pasitikėjimu, sukuriant saugumo tinklą, kuris pagauna klaidas dar prieš joms pasiekiant produkcinę aplinką? Tai yra testais grįsto programavimo (TDD) pažadas.

TDD nėra tik testavimo technika; tai disciplinuotas požiūris į programinės įrangos projektavimą ir kūrimą. Jis apverčia tradicinį modelį „parašyk kodą, tada testuok“. Naudojant TDD, testą, kuris bus nesėkmingas, parašote prieš rašydami produkcinį kodą, kad jis taptų sėkmingas. Šis paprastas apvertimas turi didelės įtakos kodo kokybei, dizainui ir palaikomumui. Šiame vadove pateikiama išsami, praktinė TDD diegimo „JavaScript“ apžvalga, skirta profesionalių programuotojų auditorijai visame pasaulyje.

Kas yra testais grįstas programavimas (TDD)?

Iš esmės testais grįstas programavimas yra kūrimo procesas, pagrįstas labai trumpo kūrimo ciklo kartojimu. Užuot kūrus funkcijas ir jas testavus, TDD reikalauja, kad testas būtų parašytas pirmiausia. Šis testas neišvengiamai bus nesėkmingas, nes funkcija dar neegzistuoja. Tada programuotojo darbas yra parašyti kuo paprastesnį kodą, kad tas konkretus testas taptų sėkmingas. Kai jis tampa sėkmingas, kodas yra išvalomas ir tobulinamas. Šis fundamentalus ciklas žinomas kaip „Red-Green-Refactor“ (liet. Raudona-Žalia-Refaktorinimas) ciklas.

TDD ritmas: „Red-Green-Refactor“

Šis trijų žingsnių ciklas yra TDD širdies plakimas. Šio ritmo supratimas ir praktikavimas yra esminis norint įvaldyti šią techniką.

Kai ciklas baigiamas vienai mažai funkcijos daliai, pradedate iš naujo su nauju nesėkmingu testu kitai daliai.

Trys TDD dėsniai

Robertas C. Martinas (dažnai žinomas kaip „Dėdė Bobas“), svarbi figūra „Agile“ programinės įrangos judėjime, apibrėžė tris paprastas taisykles, kurios kodifikuoja TDD discipliną:

  1. Negalima rašyti jokio produkcinio kodo, nebent tam, kad nesėkmingas modulio testas taptų sėkmingas.
  2. Negalima rašyti daugiau modulio testo, nei pakanka, kad jis taptų nesėkmingas; o kompiliavimo klaidos taip pat yra nesėkmės.
  3. Negalima rašyti daugiau produkcinio kodo, nei pakanka, kad vienas nesėkmingas modulio testas taptų sėkmingas.

Šių dėsnių laikymasis priverčia jus laikytis „Red-Green-Refactor“ ciklo ir užtikrina, kad 100% jūsų produkcinio kodo yra parašyta siekiant patenkinti konkretų, patikrintą reikalavimą.

Kodėl turėtumėte taikyti TDD? Pasaulinis verslo argumentas

Nors TDD teikia didžiulę naudą individualiems programuotojams, tikroji jo galia atsiskleidžia komandos ir verslo lygmeniu, ypač pasauliniu mastu paskirstytose aplinkose.

„JavaScript“ TDD aplinkos paruošimas

Norint pradėti dirbti su TDD „JavaScript“, jums reikės kelių įrankių. Šiuolaikinė „JavaScript“ ekosistema siūlo puikių pasirinkimų.

Pagrindiniai testavimo rinkinio komponentai

Dėl savo paprastumo ir „viskas viename“ prigimties, savo pavyzdžiams naudosime Jest. Tai puikus pasirinkimas komandoms, ieškančioms „nulio konfigūracijos“ patirties.

Žingsnis po žingsnio paruošimas su „Jest“

Paruoškime naują projektą TDD.

1. Inicijuokite savo projektą: atidarykite terminalą ir sukurkite naują projekto aplanką.

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

2. Įdiekite „Jest“: pridėkite „Jest“ į savo projektą kaip kūrimo priklausomybę.

npm install --save-dev jest

3. Konfigūruokite testavimo scenarijų: atidarykite savo `package.json` failą. Raskite `"scripts"` sekciją ir modifikuokite `"test"` scenarijų. Taip pat labai rekomenduojama pridėti `"test:watch"` scenarijų, kuris yra neįkainojamas TDD darbo eigai.

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

The `--watchAll` flag tells Jest to automatically re-run tests whenever a file is saved. This provides instant feedback, which is perfect for the Red-Green-Refactor cycle.

Viskas! Jūsų aplinka paruošta. „Jest“ automatiškai ras testų failus, kurių pavadinimai yra `*.test.js`, `*.spec.js`, arba esančius `__tests__` aplanke.

TDD praktikoje: `CurrencyConverter` modulio kūrimas

Pritaikykime TDD ciklą praktinei, visame pasaulyje suprantamai problemai: pinigų konvertavimui tarp valiutų. Žingsnis po žingsnio sukursime `CurrencyConverter` modulį.

1 iteracija: paprastas konvertavimas pagal fiksuotą kursą

🔴 RAUDONA: parašykite pirmąjį nesėkmingą testą

Mūsų pirmasis reikalavimas yra konvertuoti konkrečią sumą iš vienos valiutos į kitą naudojant fiksuotą kursą. Sukurkite naują failą pavadinimu `CurrencyConverter.test.js`.

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

describe('CurrencyConverter', () => {
  it('should convert an amount from USD to EUR correctly', () => {
    // Paruošimas
    const amount = 10; // 10 USD
    const expected = 9.2; // Tarkime, fiksuotas kursas yra 1 USD = 0,92 EUR

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

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

Dabar paleiskite testų stebėtoją savo terminale:

npm run test:watch

Testas įspūdingai nepavyks. „Jest“ praneš kažką panašaus į `TypeError: Cannot read properties of undefined (reading 'convert')`. Tai yra mūsų RAUDONA būsena. Testas nepavyksta, nes `CurrencyConverter` neegzistuoja.

🟢 ŽALIA: parašykite paprasčiausią kodą, kad testas pavyktų

Dabar padarykime, kad testas pavyktų. Sukurkite `CurrencyConverter.js`.

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

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

module.exports = CurrencyConverter;

Kai tik išsaugosite šį failą, „Jest“ iš naujo paleis testą ir jis taps ŽALIAS. Mes parašėme absoliučiai minimalų kodą, kad patenkintume testo reikalavimą.

🔵 REFAKTORINIMAS: patobulinkite kodą

Kodas yra paprastas, bet jau galime galvoti apie patobulinimus. Įdėtasis `rates` objektas yra šiek tiek nelankstus. Kol kas jis yra pakankamai švarus. Svarbiausia, kad turime veikiančią funkciją, apsaugotą testu. Pereikime prie kito reikalavimo.

2 iteracija: nežinomų valiutų tvarkymas

🔴 RAUDONA: parašykite testą nežinomai valiutai

Kas turėtų nutikti, jei bandytume konvertuoti į valiutą, kurios nežinome? Tikriausiai turėtų būti išmesta klaida. Apibrėžkime šį elgesį naujame teste `CurrencyConverter.test.js` faile.

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

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

  // Vykdymas ir tvirtinimas
  // Apgaubiame funkcijos iškvietimą rodykline funkcija, kad „Jest“ toThrow veiktų.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Unknown currency: XYZ');
});

Išsaugokite failą. Testų paleidiklis iškart parodys naują nesėkmę. Jis RAUDONAS, nes mūsų kodas neišmeta klaidos; jis bando pasiekti `rates['USD']['XYZ']`, o tai sukelia `TypeError`. Mūsų naujas testas teisingai nustatė šį trūkumą.

🟢 ŽALIA: padarykite, kad naujas testas pavyktų

Modifikuokime `CurrencyConverter.js`, kad pridėtume patvirtinimą.

// 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]) {
      // Nustatykite, kuri valiuta yra nežinoma, kad klaidos pranešimas būtų geresnis
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Unknown currency: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Išsaugokite failą. Dabar abu testai sėkmingi. Mes grįžome į ŽALIĄ būseną.

🔵 REFAKTORINIMAS: išvalykite

Mūsų `convert` funkcija auga. Patvirtinimo logika yra sumaišyta su skaičiavimu. Galėtume iškelti patvirtinimą į atskirą privačią funkciją, kad pagerintume skaitomumą, bet kol kas ji vis dar valdoma. Svarbiausia, kad turime laisvę daryti šiuos pakeitimus, nes mūsų testai mums pasakys, jei ką nors sugadinsime.

3 iteracija: asinchroninis kursų gavimas

Fiksuotų kursų kodavimas nėra realistiškas. Refaktorinkime savo modulį, kad jis gautų kursus iš (imituotos) išorinės API.

🔴 RAUDONA: parašykite asinchroninį testą, kuris imituoja API iškvietimą

Pirma, turime pertvarkyti savo konverterį. Dabar jis turės būti klasė, kurią galime inicializuoti, galbūt su API klientu. Taip pat reikės imituoti `fetch` API. „Jest“ tai padaro lengvai.

Perrašykime savo testo failą, kad jis atitiktų šią naują, asinchroninę realybę. Pradėsime vėl testuodami sėkmingą scenarijų.

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

// Imituojame išorinę priklausomybę
global.fetch = jest.fn();

beforeEach(() => {
  // Išvalome imitacijos istoriją prieš kiekvieną testą
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('should fetch rates and convert correctly', async () => {
    // Paruošimas
    // Imituojame sėkmingą API atsakymą
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

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

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

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

  // Taip pat pridėtume testus API nesėkmėms ir pan.
});

Tai paleidus, rezultatas bus RAUDONOS spalvos jūra. Mūsų senasis `CurrencyConverter` nėra klasė, neturi `async` metodo ir nenaudoja `fetch`.

🟢 ŽALIA: įgyvendinkite asinchroninę logiką

Dabar perrašykime `CurrencyConverter.js`, kad atitiktų testo reikalavimus.

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

    // Paprastas apvalinimas, siekiant išvengti slankiojo kablelio problemų testuose
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Kai išsaugosite, testas turėtų tapti ŽALIAS. Atkreipkite dėmesį, kad taip pat pridėjome apvalinimo logiką, kad išspręstume slankiojo kablelio netikslumus, kurie yra dažna problema finansiniuose skaičiavimuose.

🔵 REFAKTORINIMAS: patobulinkite asinchroninį kodą

`convert` metodas daro daug: gauna duomenis, tvarko klaidas, apdoroja ir skaičiuoja. Galėtume tai refaktorinti sukurdami atskirą `RateFetcher` klasę, atsakingą tik už API komunikaciją. Mūsų `CurrencyConverter` tada naudotų šį gavėją. Tai atitinka Vieno atsakomybės principą (Single Responsibility Principle) ir daro abi klases lengviau testuojamas ir palaikomas. TDD mus veda link šio švaresnio dizaino.

Dažniausi TDD modeliai ir antimodeliai

Praktikuodami TDD, atrasite modelius, kurie gerai veikia, ir antimodelius, kurie kelia trintį.

Geri modeliai, kuriais verta sekti

Antimodeliai, kurių reikia vengti

TDD platesniame kūrimo gyvavimo cikle

TDD neegzistuoja vakuume. Jis puikiai integruojasi su šiuolaikinėmis „Agile“ ir „DevOps“ praktikomis, ypač pasaulinėms komandoms.

Išvada: jūsų kelionė su TDD

Testais grįstas programavimas yra daugiau nei testavimo strategija – tai paradigmos pokytis, kaip mes žiūrime į programinės įrangos kūrimą. Jis skatina kokybės, pasitikėjimo ir bendradarbiavimo kultūrą. „Red-Green-Refactor“ ciklas suteikia pastovų ritmą, kuris veda jus link švaraus, tvirto ir palaikomo kodo. Gautas testų rinkinys tampa saugumo tinklu, kuris apsaugo jūsų komandą nuo regresijų, ir gyva dokumentacija, kuri padeda įsilieti naujiems nariams.

Mokymosi kreivė gali atrodyti stati, o pradinis tempas gali pasirodyti lėtesnis. Tačiau ilgalaikiai dividendai, susiję su sumažėjusiu derinimo laiku, pagerėjusiu programinės įrangos dizainu ir padidėjusiu programuotojų pasitikėjimu, yra neišmatuojami. Kelionė į TDD įvaldymą yra disciplinos ir praktikos kelionė.

Pradėkite šiandien. Pasirinkite vieną mažą, nekritinę funkciją kitame savo projekte ir įsipareigokite procesui. Parašykite testą pirmiausia. Stebėkite, kaip jis nepavyksta. Padarykite, kad jis pavyktų. Ir tada, svarbiausia, atlikite refaktorinimą. Patirkite pasitikėjimą, kurį suteikia žalias testų rinkinys, ir netrukus stebėsitės, kaip kada nors kūrėte programinę įrangą kitaip.