Į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ą.
- 🔴 Raudona — parašykite nesėkmingą testą: pradedate rašydami automatinį testą naujai funkcijai. Šis testas turėtų apibrėžti, ką norite, kad kodas darytų. Kadangi dar neparašėte jokio įgyvendinimo kodo, šis testas garantuotai bus nesėkmingas. Nesėkmingas testas nėra problema; tai pažanga. Tai įrodo, kad testas veikia teisingai (jis gali būti nesėkmingas) ir nustato aiškų, konkretų tikslą kitam žingsniui.
- 🟢 Žalia — parašykite paprasčiausią kodą, kad testas taptų sėkmingas: dabar jūsų tikslas yra vienintelis: padaryti testą sėkmingą. Turėtumėte parašyti absoliučiai minimalų produkcinio kodo kiekį, reikalingą, kad testas iš raudono taptų žalias. Tai gali atrodyti neintuityvu; kodas gali būti ne elegantiškas ar efektyvus. Tai yra gerai. Čia dėmesys skiriamas tik testo apibrėžto reikalavimo įvykdymui.
- 🔵 Refaktorinimas — patobulinkite kodą: dabar, kai turite sėkmingą testą, turite saugumo tinklą. Galite užtikrintai valyti ir tobulinti savo kodą, nebijodami sugadinti funkcionalumo. Čia jūs sprendžiate kodo „kvapus“, šalinante dubliavimąsi, gerinate aiškumą ir optimizuojate našumą. Galite bet kuriuo metu refaktorinimo metu paleisti savo testų rinkinį, kad įsitikintumėte, jog neįvedėte jokių regresijų. Po refaktorinimo visi testai vis dar turėtų būti žali.
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ą:
- Negalima rašyti jokio produkcinio kodo, nebent tam, kad nesėkmingas modulio testas taptų sėkmingas.
- Negalima rašyti daugiau modulio testo, nei pakanka, kad jis taptų nesėkmingas; o kompiliavimo klaidos taip pat yra nesėkmės.
- 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.
- Didesnis pasitikėjimas ir greitis: išsamus testų rinkinys veikia kaip saugumo tinklas. Tai leidžia komandoms užtikrintai pridėti naujų funkcijų ar refaktorinti esamas, o tai lemia didesnį tvarų kūrimo greitį. Jūs praleidžiate mažiau laiko rankiniam regresijos testavimui ir derinimui, o daugiau laiko skiriate vertės kūrimui.
- Pagerintas kodo dizainas: testų rašymas pirmiausia verčia jus galvoti apie tai, kaip jūsų kodas bus naudojamas. Jūs esate pirmasis savo paties API vartotojas. Tai natūraliai veda prie geriau suprojektuotos programinės įrangos su mažesniais, labiau sufokusuotais moduliais ir aiškesniu atsakomybių atskyrimu.
- Gyva dokumentacija: pasaulinei komandai, dirbančiai skirtingose laiko juostose ir kultūrose, aiški dokumentacija yra kritiškai svarbi. Gerai parašytas testų rinkinys yra gyvos, vykdomosios dokumentacijos forma. Naujas programuotojas gali perskaityti testus, kad suprastų, ką tiksliai turi daryti kodo dalis ir kaip ji elgiasi įvairiuose scenarijuose. Skirtingai nuo tradicinės dokumentacijos, ji niekada negali pasenti.
- Sumažintos bendrosios nuosavybės išlaidos (TCO): klaidos, pagautos ankstyvoje kūrimo ciklo stadijoje, yra eksponentiškai pigiau ištaisomos nei tos, kurios randamos produkcinėje aplinkoje. TDD sukuria tvirtą sistemą, kurią lengviau palaikyti ir plėsti laikui bėgant, taip sumažinant ilgalaikes programinės įrangos TCO.
„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
- Testų paleidiklis (Test Runner): programa, kuri suranda ir paleidžia jūsų testus. Ji suteikia struktūrą (pvz., `describe` ir `it` blokus) ir pateikia rezultatus. Jest ir Mocha yra du populiariausi pasirinkimai.
- Tvirtinimo biblioteka (Assertion Library): įrankis, teikiantis funkcijas, skirtas patikrinti, ar jūsų kodas veikia taip, kaip tikėtasi. Jis leidžia rašyti teiginius, tokius kaip `expect(result).toBe(true)`. Chai yra populiari atskira biblioteka, o Jest turi savo galingą tvirtinimo biblioteką.
- Imitavimo biblioteka (Mocking Library): įrankis, skirtas kurti priklausomybių „netikras“ versijas, pavyzdžiui, API iškvietimų ar duomenų bazės prisijungimų. Tai leidžia testuoti kodą izoliuotai. Jest turi puikias integruotas imitavimo galimybes.
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
- Paruošimas, Vykdymas, Tvirtinimas (Arrange, Act, Assert - AAA): struktūrizuokite savo testus į tris aiškias dalis. Paruoškite savo aplinką, įvykdykite testuojamą kodą ir patvirtinkite, kad rezultatas yra teisingas. Tai daro testus lengvai skaitomus ir suprantamus.
- Testuokite vieną elgesio aspektą vienu metu: kiekvienas testas turėtų patikrinti vieną, konkretų elgesio aspektą. Tai leidžia aiškiai matyti, kas sugedo, kai testas nepavyksta.
- Naudokite aprašomuosius testų pavadinimus: testo pavadinimas, pvz., `it('turėtų išmesti klaidą, jei suma yra neigiama')`, yra daug vertingesnis nei `it('testas 1')`.
Antimodeliai, kurių reikia vengti
- Įgyvendinimo detalių testavimas: testai turėtų būti sutelkti į viešąją API (į „ką“), o ne į privatų įgyvendinimą (į „kaip“). Privačių metodų testavimas daro jūsų testus trapius ir apsunkina refaktorinimą.
- Refaktorinimo žingsnio ignoravimas: tai yra dažniausia klaida. Praleidžiant refaktorinimą, kaupiasi techninė skola tiek jūsų produkciniame kode, tiek testų rinkinyje.
- Didelių, lėtų testų rašymas: modulių testai turėtų būti greiti. Jei jie priklauso nuo realių duomenų bazių, tinklo iškvietimų ar failų sistemų, jie tampa lėti ir nepatikimi. Naudokite imitacijas (mocks) ir pakaitalus (stubs), kad izoliuotumėte savo modulius.
TDD platesniame kūrimo gyvavimo cikle
TDD neegzistuoja vakuume. Jis puikiai integruojasi su šiuolaikinėmis „Agile“ ir „DevOps“ praktikomis, ypač pasaulinėms komandoms.
- TDD ir „Agile“: vartotojo istorija arba priėmimo kriterijus iš jūsų projektų valdymo įrankio gali būti tiesiogiai paverstas nesėkmingų testų serija. Tai užtikrina, kad kuriate būtent tai, ko reikalauja verslas.
- TDD ir nuolatinė integracija / nuolatinis diegimas (CI/CD): TDD yra patikimo CI/CD konvejerio pagrindas. Kiekvieną kartą, kai programuotojas įkelia kodą, automatizuota sistema (pvz., „GitHub Actions“, „GitLab CI“ ar „Jenkins“) gali paleisti visą testų rinkinį. Jei bent vienas testas nepavyksta, kūrimas sustabdomas, taip užkertant kelią klaidoms pasiekti produkcinę aplinką. Tai suteikia greitą, automatizuotą grįžtamąjį ryšį visai komandai, nepriklausomai nuo laiko juostų.
- TDD vs. BDD (Behavior-Driven Development): BDD yra TDD plėtinys, kuris sutelktas į bendradarbiavimą tarp programuotojų, kokybės užtikrinimo specialistų ir verslo suinteresuotųjų šalių. Jis naudoja natūralios kalbos formatą (Given-When-Then), kad aprašytų elgesį. Dažnai BDD funkcijos failas skatina kelių TDD stiliaus modulių testų sukūrimą.
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.