Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. assert — birinchi tekshiruv
- 2.1 Tekshiruv — bitta qator
- 2.2 Tekshiruv yiqilganda
- 2.3 equal va deepEqual
- 2.4 throws — xato kutilganda
- 3. test — nomli tekshiruv
- 3.1 Birinchi test
- 3.2 describe — guruh
- 4. Qizil → yashil
- 4.1 Chegaraviy holatlar
- 4.2 Sikl
- 5. Loyihada: test fayllari va node --test
- 5.1 Kod — bir faylda, test — boshqasida
- 5.2 npm test
- 5.3 Test fayllarini qanday topadi
- 5.4 Foydali imkoniyatlar
- 5.5 Har test — mustaqil
- 6. Nimani test qilish oson va nimani qiyin
- 6.1 Toza funksiyalar — eng oson
- 6.2 Tashqi narsalarga bog'liq kod
- 7. Ko'p uchraydigan xatolar
- 7.1 Obyektni equal bilan solishtirish
- 7.2 Funksiyani throws ga chaqirib berish
- 7.3 Asinxron testda await yo'q
- 7.4 Testlar bir-biriga bog'liq
- 7.5 Hech qachon yiqilmagan test
- 8. Mashqlar
- 1-mashq (oson): Tekshiruvni to'ldiring
- 2-mashq (o'rta): isOpen uchun testlar
- 3-mashq (qiyin): Avval qizil
- 4-mashq: Vazifalar qadami — tekshiruv skriptlari testlarga aylanadi
- 9. Real ishda
- Xulosa
- Manbalar
Birinchi avtomatik test: node:test va assert — kodni o'zi tekshiradigan kod
Qisqacha: Avtomatik test — kodingizni tekshiradigan kichik kod.
assert.equal(haqiqiy, kutilgan)ikki qiymat teng bo'lmasa xato tashlaydi.test("nomi", () => { … })tekshiruvlarga nom beradi,describeularni guruhlaydi. Terminaldanode --testloyihadagi barcha*.test.jsfayllarni topib ishga tushiradi va natijani✔/✖bilan chiqaradi. Hammasi Node.js ichida bor — hech narsa o'rnatish shart emas.
Bu darsda
- Avtomatik test nima ekanini va qo'lda tekshirishdan nimasi bilan yaxshiligini tushuntira olasiz.
node:assert/strictbilanequal,deepEqual,throwstekshiruvlarini yozasiz va yiqilgan tekshiruv xabarini o'qiysiz.node:testdatestvadescribebilan test fayl yozib,node --testbilan ishga tushirasiz.- Chegaraviy holatlarni topib, ularni "qizil → yashil" sikli bilan testga aylantirasiz.
vazifalarning tekshiruv skriptlarini haqiqiy testlarga aylantirasiz.
Oldin bilishingiz kerak: Toza kod: funksiyalar, izohlar va tuzilma, Xato strategiyasi, CommonJS va ESM bilan o'zaro ishlash, npm'ga birinchi qadam.
1. Nega bu kerak?
O'tgan ikki darsda vazifalar ning nomlari va funksiyalarini tozaladik. Har safar xulq o'zgarmaganini qanday isbotladik? Skriptlarni ishga tushirib, chiqishni faylga yozdik, keyin git diff bilan eski fayl bilan solishtirdik. Har o'zgarishdan keyin — to'rtta buyruq, ikki fayl, ko'z bilan qarash.
Bu ishladi, lekin uchta muammosi bor. Birinchisi — sekin: kichik o'zgarish uchun ham hammasini qo'lda qilasiz. Ikkinchisi — ishonchsiz: ✅ qatorlarini ko'z bilan o'qiysiz, bittasi ❌ bo'lsa, o'tkazib yuborishingiz mumkin. Uchinchisi — skriptdagi tekshiruvlar bir-biriga bog'liq: bitta umumiy ro'yxat ketma-ket o'zgartiriladi, beshinchi qator yiqilsa, oltinchisi ham noto'g'ri bo'ladi.
Debugging metodologiyasi darsida bir gap aytgan edik: avtomatik test — kodni siz o'rningizga tekshiradigan kichik dastur. Bugun uni yozamiz. Siz bir marta "bu funksiya shunday ishlashi kerak" deb yozasiz, keyin bitta buyruq har safar hamma holatni bir soniyadan kamroq vaqtda qayta tekshiradi. Bu kran ostidagi o'lchov idishiga o'xshaydi: suv har safar bir xil chiziqqa yetishi kerak — yetmasa, darhol ko'rinadi.
2. assert — birinchi tekshiruv
2.1 Tekshiruv — bitta qator
O'tgan darsda «Bahor» chekidan calculateTotal ("jamini hisobla") funksiyasini ajratgan edik. U toza funksiya: buyurtma beriladi, summa qaytadi. Uni tekshirish uchun Node.js'ning ichki moduli bor — node:assert/strict:
import assert from "node:assert/strict";
function calculateTotal(order) {
return order.reduce(
(total, item) => total + item.price * item.qty,
0,
);
}
assert.equal(calculateTotal([{ price: 5000, qty: 3 }]), 15000);
assert.equal(calculateTotal([]), 0);
assert.equal(calculateTotal([{ price: 35000, qty: 2 }]), 70000);
console.log("Hammasi joyida");Konsolda:
Hammasi joyidaassert — inglizcha "qat'iy ta'kidlamoq". assert.equal(haqiqiy, kutilgan) aytadi: "men ishonamanki, bu ikki qiymat teng". Teng bo'lsa — hech narsa bo'lmaydi, kod davom etadi. Teng bo'lmasa — xato tashlaydi va dastur to'xtaydi. Uchta tekshiruv o'tdi, shuning uchun oxirgi qatorga yetib keldik.
Tartibga e'tibor bering: birinchi — haqiqiy qiymat (funksiya nima qaytardi), ikkinchi — kutilgan (nima bo'lishi kerak). Xato xabari shu tartibga tayanadi.
Bu blok faqat Node.js'da ishlaydi — node: bilan boshlangan modullar brauzerda yo'q. Shuning uchun uni o'zingizning kompyuteringizda, .mjs faylga saqlab, node fayl.mjs bilan ishga tushiring.
2.2 Tekshiruv yiqilganda
Endi oxirgi tekshiruvda ataylab adashamiz — 70 000 o'rniga 75 000 yozamiz:
import assert from "node:assert/strict";
function calculateTotal(order) {
return order.reduce(
(total, item) => total + item.price * item.qty,
0,
);
}
assert.equal(calculateTotal([{ price: 35000, qty: 2 }]), 75000);
console.log("Hammasi joyida");Terminalda (Node 24; fayl yo'li va pastki qatorlar qisqartirildi):
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
70000 !== 75000
at file:///…/jami.mjs:10:8
...
code: 'ERR_ASSERTION',
actual: 70000,
expected: 75000,
operator: 'strictEqual',O'qiymiz. AssertionError — "ta'kid xatosi": ta'kidlagan narsangiz rost chiqmadi. "Expected values to be strictly equal" — "qiymatlar qat'iy teng bo'lishi kutilgan edi". Keyin aniq farq: 70000 !== 75000 — haqiqiy 70 000, kutilgan 75 000. at file:///…/jami.mjs:10:8 — qaysi faylning qaysi qatori. "Hammasi joyida" chiqmadi: dastur birinchi yiqilgan tekshiruvda to'xtadi.
Bu yerda funksiya to'g'ri, test xato edi: 35 000 × 2 — 70 000. Test ham kod, u ham adashishi mumkin. Test yiqilganda birinchi savol: "Kod xatomi yoki kutganim?"
2.3 equal va deepEqual
assert.equal qiymatlarni === ga o'xshab solishtiradi (aniqrog'i — Object.is bilan). Sonlar va satrlar uchun bu to'g'ri. Massiv va obyektlarda esa tuzoq bor:
import assert from "node:assert/strict";
assert.equal([1, 2], [1, 2]);Terminalda:
AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal:
[
1,
2
]"Tuzilishi bir xil, lekin havolasi boshqa" — sayoz va chuqur nusxalash darsidagi gap: ikkita alohida massiv === bilan teng emas. Ichidagi qiymatlarni solishtirish uchun assert.deepEqual:
import assert from "node:assert/strict";
assert.deepEqual([1, 2], [1, 2]);
assert.deepEqual(
{ name: "Osh", price: 35000 },
{ price: 35000, name: "Osh" },
);
console.log("Ichi bir xil");Konsolda:
Ichi bir xilObyekt kalitlarining tartibi muhim emas — qiymatlari teng bo'lsa yetarli. Turi esa muhim:
import assert from "node:assert/strict";
assert.deepEqual(
{ name: "Ali", age: 20 },
{ name: "Ali", age: "20" },
);Terminalda:
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
+ age: 20,
- age: '20',
name: 'Ali'
}+ actual — haqiqiy qiymat (+ belgili qator), - expected — kutilgan (- belgili qator). Farq aynan qaysi kalitda ekanini ko'rsatadi: son 20 va satr "20". Kalitlar alifbo tartibida chiqadi: age — name dan oldin. Modul nomidagi strict so'zi shuni bildiradi: 20 va "20" teng emas. Shuning uchun biz doim node:assert/strict ni import qilamiz — oddiy node:assert ning eski, "yumshoq" variantlari turni e'tiborsiz qoldiradi.
2.4 throws — xato kutilganda
Ba'zan to'g'ri xulq — xato tashlash. Xato strategiyasi darsida noto'g'ri kirishga RangeError tashlashni o'rgangan edik. Buni ham tekshirsa bo'ladi:
import assert from "node:assert/strict";
function applyDiscount(amount) {
if (!Number.isFinite(amount) || amount < 0) {
throw new RangeError(`Noto'g'ri summa: ${amount}`);
}
return amount; // chegirma hisobi bu misolda muhim emas
}
assert.throws(() => applyDiscount(-5000), RangeError);
assert.throws(() => applyDiscount(NaN), {
name: "RangeError",
message: "Noto'g'ri summa: NaN",
});
console.log("Xatolar to'g'ri tashlandi");Konsolda:
Xatolar to'g'ri tashlandiassert.throws ga funksiyaning o'zini emas, uni chaqiradigan arrow'ni beramiz: () => applyDiscount(-5000). Agar applyDiscount(-5000) ni to'g'ridan-to'g'ri yozsak, xato throws ishga tushmasidan oldin chiqib ketardi. Ikkinchi argument — kutilgan xato: klass (RangeError) yoki xususiyatlari yozilgan obyekt. Funksiya xato tashlamasa yoki boshqa xato tashlasa — throws o'zi yiqiladi.
Yana ikki foydali tekshiruv: assert.ok(qiymat) — qiymat truthy bo'lsin; assert.match(satr, /regex/) — satr naqshga mos kelsin (RegExp asoslari).
Tekshirib ko'ring:
assert.equal({ price: 5000 }, { price: 5000 })o'tadimi?assert.equal("5000", 5000)ni-chi?
Javob
Ikkalasi ham yiqiladi. Birinchisida — ikki alohida obyekt, havolasi boshqa; kerakli tekshiruv — deepEqual. Ikkinchisida — satr va son; strict rejimda turi ham solishtiriladi, "5000" !== 5000.
3. test — nomli tekshiruv
3.1 Birinchi test
Faqat assert bilan ishlashning kamchiligi: birinchi yiqilgan tekshiruvda hammasi to'xtaydi, qolganlari haqida hech narsa bilmaymiz. Ularning nomi ham yo'q. Node.js'ning ikkinchi ichki moduli — node:test — buni hal qiladi:
import { test } from "node:test";
import assert from "node:assert/strict";
function calculateTotal(order) {
return order.reduce(
(total, item) => total + item.price * item.qty,
0,
);
}
test("bitta taom", () => {
assert.equal(calculateTotal([{ price: 35000, qty: 2 }]), 70000);
});
test("bo'sh buyurtma — 0", () => {
assert.equal(calculateTotal([]), 0);
});Faylni node fayl.mjs bilan ishga tushirsangiz (vaqtlar har safar biroz boshqacha bo'ladi):
✔ bitta taom (0.8813ms)
✔ bo'sh buyurtma — 0 (0.1963ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 8.8223test(nomi, funksiya) — bitta test: nom va uning ichidagi tekshiruvlar. ✔ — test o'tdi, qavs ichida — qancha vaqt oldi. Pastda hisobot: tests 2 — jami, pass 2 — o'tgani, fail 0 — yiqilgani. Qolgan qatorlarni keyinroq ko'ramiz.
Test nomi — bu hujjat. "bo'sh buyurtma — 0" o'qigan odam funksiya qanday ishlashi kerakligini bilib oladi. Nomni xulq haqidagi gap kabi yozing: "nima berilsa — nima bo'ladi".
3.2 describe — guruh
Bitta funksiyaning testlari ko'payganda, ularni describe bilan guruhlaymiz. Guruhni suite (to'plam) ham deyishadi — hisobotdagi suites qatori shuni sanaydi. Quyidagi misolda applyDiscount ataylab xato yozilgan — Sardor >= o'rniga > qo'ygan:
import { describe, test } from "node:test";
import assert from "node:assert/strict";
// 200 000 so'mdan boshlab — 10% chegirma
function applyDiscount(amount) {
if (amount > 200000) {
return amount * 0.9;
}
return amount;
}
describe("applyDiscount", () => {
test("kichik buyurtma — chegirmasiz", () => {
assert.equal(applyDiscount(70000), 70000);
});
test("chegara: aynan 200 000 — chegirma bor", () => {
assert.equal(applyDiscount(200000), 180000);
});
test("katta buyurtma — 10% kam", () => {
assert.equal(applyDiscount(300000), 270000);
});
});Terminalda (stack qatorlari qisqartirildi):
▶ applyDiscount
✔ kichik buyurtma — chegirmasiz (0.8507ms)
✖ chegara: aynan 200 000 — chegirma bor (1.3873ms)
✔ katta buyurtma — 10% kam (0.1963ms)
✖ applyDiscount (3.8968ms)
ℹ tests 3
ℹ suites 1
ℹ pass 2
ℹ fail 1
...
✖ failing tests:
test at b2.mjs:17:3
✖ chegara: aynan 200 000 — chegirma bor (1.3873ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
200000 !== 180000Uchta muhim narsa. Birinchisi — test yiqilsa ham, keyingi test ishladi: uchinchi test o'tganini ko'ryapmiz. Har test o'z "qutisi"da. Ikkinchisi — pastda yiqilgan testlar alohida ro'yxatda, nomi va qatori bilan. Uchinchisi — nom xatoni o'zi aytib turibdi: "aynan 200 000 da chegirma bor" — demak, chegara noto'g'ri.
describe ichidagi testlar uchun test o'rniga it ham yozish mumkin — bu bir xil funksiya, boshqa nomi. Ko'p loyihalarda describe("…", () => { it("…") }) ko'rasiz: inglizchada "describe … it should …" gap kabi o'qiladi.
4. Qizil → yashil
4.1 Chegaraviy holatlar
Yuqoridagi xato chegarada yashiringan edi: 199 999 va 200 001 da > ham, >= ham bir xil ishlaydi — faqat aynan 200 000 da farq qiladi. Xatolar aynan shunday joylarni yaxshi ko'radi. Ularni chegaraviy holatlar (edge cases) deyishadi.
Test yozishdan oldin o'zingizga savol bering:
- Chegara qayerda? Undan bitta kam, aynan o'zi, bitta ko'p.
- Bo'sh kirish:
[],"",0. - Noto'g'ri kirish: manfiy son,
NaN,undefined. - Maxsus belgilar: o'zbekcha apostrof, emoji, kirill harflari (Unicode regex darsidagi teglar).
4.2 Sikl
Xatoni tuzatishning ishonchli tartibi uch qadamdan iborat:
- Qizil — xatoni ko'rsatadigan testni yozing va uning yiqilishini ko'ring.
- Yashil — kodni tuzating, test o'tsin.
- Toza — kerak bo'lsa kodni tartibga keltiring; testlar yashil qolsin.
flowchart LR
Q["Qizil: test yozildi, yiqildi ✖"] --> Y["Yashil: kod tuzatildi, o'tdi ✔"]
Y --> T["Toza: refaktor, testlar yashil"]
T --> QNega avval yiqilishini ko'rish kerak? Chunki hech qachon yiqilmaydigan test ham bo'ladi — masalan, assert yozish esdan chiqqan bo'lsa yoki noto'g'ri funksiya chaqirilgan bo'lsa. Qizilni ko'rgan test haqiqatan nimanidir tekshiradi.
Testni koddan oldin yozish va shu sikl bo'yicha ishlash uslubi TDD (test-driven development — testga asoslangan ishlab chiqish) deyiladi. Hozir uni to'liq qo'llash shart emas, lekin xatoni tuzatishda qoida oddiy: avval xatoni testga aylantiring, keyin tuzating. Shunda bu xato hech qachon jimgina qaytib kelmaydi.
Tekshirib ko'ring: «Bahor» 7:00 dan 23:00 gacha ishlaydi.
isOpen(hour)("ochiqmi") funksiyasi uchun qaysi soatlarni testga yozardingiz?
Javob
Chegaralar atrofi: 6 (yopiq), 7 (ochiq — ochilish), 22 (ochiq), 23 (yopiq — yopilish payti). Shuningdek, 0 yoki 12 kabi "oddiy" holatlar va noto'g'ri qiymatlar (−1, 25), agar funksiya ularni rad etishi kerak bo'lsa. Eng muhimlari — 7 va 23: < va <= xatosi aynan shu yerda chiqadi.
5. Loyihada: test fayllari va node --test
5.1 Kod — bir faylda, test — boshqasida
Haqiqiy loyihada funksiyalar o'z modulida yashaydi, testlar esa alohida faylda. «Bahor» hisob-kitobi uchun kichik loyiha:
bahor-hisob/
package.json
hisob.js
hisob.test.jshisob.js — tuzatilgan kod, endi eksport qilinadi (ESM export):
// hisob.js — «Bahor» buyurtma hisobi
export const DISCOUNT_THRESHOLD = 200000; // so'm
export const DISCOUNT_PERCENT = 10;
export function calculateTotal(order) {
return order.reduce(
(total, { price, qty }) => total + price * qty,
0,
);
}
export function applyDiscount(amount) {
if (!Number.isFinite(amount) || amount < 0) {
throw new RangeError(`Noto'g'ri summa: ${amount}`);
}
if (amount < DISCOUNT_THRESHOLD) {
return amount;
}
return (amount * (100 - DISCOUNT_PERCENT)) / 100;
}O'tgan darsdagi applyDiscount ga bitta himoya sharti qo'shildi: noto'g'ri summa — RangeError.
hisob.test.js — uning testlari. Nomi kod fayli bilan bir xil, faqat .test qo'shilgan:
// hisob.test.js — hisob.js testlari
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { calculateTotal, applyDiscount } from "./hisob.js";
describe("calculateTotal", () => {
test("bir nechta taom", () => {
const order = [
{ price: 35000, qty: 2 },
{ price: 5000, qty: 3 },
];
assert.equal(calculateTotal(order), 85000);
});
test("bo'sh buyurtma — 0", () => {
assert.equal(calculateTotal([]), 0);
});
});
describe("applyDiscount", () => {
test("chegaradan bitta kam — chegirmasiz", () => {
assert.equal(applyDiscount(199999), 199999);
});
test("aynan chegara — 10% chegirma", () => {
assert.equal(applyDiscount(200000), 180000);
});
test("manfiy summa — RangeError", () => {
assert.throws(() => applyDiscount(-5000), RangeError);
});
test("son emas — xato matni bilan", () => {
assert.throws(() => applyDiscount(NaN), {
name: "RangeError",
message: "Noto'g'ri summa: NaN",
});
});
});package.json da — "type": "module" (ESM) va npm skripti:
{
"name": "bahor-hisob",
"private": true,
"type": "module",
"scripts": {
"test": "node --test"
}
}5.2 npm test
Terminalda loyiha papkasida npm test (bu npm run test ning qisqa shakli). Bizda (Node 24.21.0, npm 11.19.0) shunday chiqdi:
> test
> node --test
▶ calculateTotal
✔ bir nechta taom (1.709ms)
✔ bo'sh buyurtma — 0 (0.1953ms)
✔ calculateTotal (3.2373ms)
▶ applyDiscount
✔ chegaradan bitta kam — chegirmasiz (0.3375ms)
✔ aynan chegara — 10% chegirma (0.1543ms)
✔ manfiy summa — RangeError (0.5595ms)
✔ son emas — xato matni bilan (0.4966ms)
✔ applyDiscount (1.9386ms)
ℹ tests 6
ℹ suites 2
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 157.9356node --test — Node.js'ning ichki test yurgizuvchisi (test runner). U papkani o'zi aylanib, test fayllarini topadi va har birini alohida ishga tushiradi. Test topilmasa yoki bittasi yiqilsa, buyruq xato kodi bilan tugaydi — keyinchalik avtomatik tekshiruv tizimlari (CI) aynan shunga qaraydi.
5.3 Test fayllarini qanday topadi
node --test faqat nomi ma'lum shaklda bo'lgan fayllarni ishga tushiradi. Eng ko'p ishlatiladigani — *.test.js (yana *-test.js, *_test.js, test-*.js, test papkasi ichidagi fayllar va .mjs/.cjs variantlari). node_modules papkasi o'tkazib yuboriladi.
Bundan foydali xulosa: test bo'lmagan faylni ataylab boshqacha nomlash mumkin. Masalan, haqiqiy serverga so'rov yuboradigan sekin test api.tarmoq.js deb nomlansa, oddiy npm test uni ishga tushirmaydi. Uni alohida, internet bor paytda ishga tushirasiz. Vazifalar qadamida aynan shunday qilamiz.
5.4 Foydali imkoniyatlar
node --test --watch— fayl saqlanganda testlar o'zi qayta ishga tushadi. Kod yozayotganda terminalni ochiq qoldirasiz.test.todo("…")— hali yozilmagan testning nomi, eslatma sifatida.test.skip("…", …)— vaqtincha o'tkazib yuboriladigan test.test.only("…", …)— faqat shu test (node --test --test-onlybilan).
Hisobotdagi skipped va todo qatorlari shularni sanaydi:
import { test } from "node:test";
test.todo("katta buyurtmaga bepul choy");
test.skip("Click orqali to'lov", () => {});
test("oddiy", () => {});Terminalda (vaqtlar har safar boshqacha; # TODO va # SKIP belgilari bilan):
✔ katta buyurtmaga bepul choy (1.0257ms) # TODO
﹣ Click orqali to'lov (0.2319ms) # SKIP
✔ oddiy (0.2218ms)
ℹ tests 3
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 1
ℹ duration_ms 10.5168skip ni unutib qoldirmang: o'tkazilgan test hech narsani himoya qilmaydi.
5.5 Har test — mustaqil
Testlar istalgan tartibda va alohida ishlashi kerak. Agar ikkinchi test birinchisi o'zgartirgan ro'yxatga tayansa, birinchisini o'chirganingizda ikkinchisi ham buziladi — sababi esa ko'rinmaydi. Qoida: har test o'z ma'lumotini o'zi yaratadi. Ko'p testga bir xil tayyorgarlik kerak bo'lsa — beforeEach(() => { … }): u har testdan oldin ishlaydi (afterEach — keyin).
Tekshirib ko'ring:
bahor-hisobpapkasigatekshiruv.jsnomli fayl qo'shdingiz, ichidatest(...)bor.npm testuni ishga tushiradimi?
Javob
Yo'q. tekshiruv.js nomi test naqshlariga (*.test.js, test-*.js va boshqalar) mos kelmaydi. Uni tekshiruv.test.js deb nomlang yoki node --test tekshiruv.js deb faylni aniq ko'rsating.
6. Nimani test qilish oson va nimani qiyin
6.1 Toza funksiyalar — eng oson
calculateTotal — toza funksiya: kirish berasiz, chiqishni solishtirasiz. Uni tekshirish uchun brauzer ham, server ham kerak emas — FP g'oyalari va deklarativ uslub darsidagi "funksional yadro" aynan shu. Xuddi shunday — Intl.NumberFormat darsidagi somYoz (bu yerda inglizcha nomi — formatSom). O'sha darsda bitta tuzoq aytilgan edi: natijadagi guruh ajratuvchi — oddiy bo'sh joy emas, uzilmas bo'sh joy (U+00A0). Testda uni \u00a0 bilan yozamiz:
import { test } from "node:test";
import assert from "node:assert/strict";
const numberFormat = new Intl.NumberFormat("uz-UZ", {
maximumFractionDigits: 0,
});
function formatSom(price) {
const text = numberFormat
.formatToParts(price)
.map((p) => (p.type === "group" ? "\u00a0" : p.value))
.join("");
return `${text} so'm`;
}
test("formatSom: guruhlar uzilmas bo'sh joy bilan", () => {
assert.equal(formatSom(35000), "35\u00a0000 so'm");
assert.equal(formatSom(1250000), "1\u00a0250\u00a0000 so'm");
});
test("oddiy bo'sh joy bilan teng emas", () => {
assert.notEqual(formatSom(35000), "35 000 so'm");
});Terminalda ikkala test ham ✔ (bizda Node 24 da sinaldi). Ko'zga bir xil ko'rinadigan ikki satr teng emas — test buni aniq ushlaydi. Formatlovchining o'zini (Intl) emas, o'zimizning formatSom ni tekshiryapmiz: Intl ning natijasi brauzerga qarab farq qiladi, bizning funksiyamiz esa bir xil bo'lishi kerak.
6.2 Tashqi narsalarga bog'liq kod
Qiyini — funksiya o'zidan tashqaridagi narsaga tayanganda: joriy vaqt, internet, localStorage. Ularning javobi har safar boshqacha. Yechim — tashqi narsani tashqaridan berish:
- Joriy vaqt — parametr: funksiya ichida
new Date()ni o'zi olmaydi, soatni chaqiruvchi beradi:isOpen(hour). Testda istalgan soatni berasiz (Dateasoslari darsida aytgan edik). - Tarmoq —
fetcho'rniga soxta (mock) funksiya. U tarmoqqa bormaydi: oldindan tayyorlangan javobni qaytaradi va qanday chaqirilganini eslab qoladi. Buning uchunnode:testdamockbor. Request va Response darsida "bog'liqlikni tashqaridan berish" degan g'oyani ko'rgan edik — testlar uning eng katta foydalanuvchisi.
mock ning to'liq imkoniyatlarini (taymerlarni "to'xtatish", spy) frontend testlash qismida Vitest bilan o'rganamiz (Vitest I) — u node:test ga juda o'xshaydi, faqat assert.equal(a, b) o'rniga expect(a).toBe(b) yoziladi. Hozir bilish shart emas: bugungi Vazifalar qadamida mock ning bitta, eng sodda ishlatilishini ko'rasiz.
7. Ko'p uchraydigan xatolar
7.1 Obyektni equal bilan solishtirish
Values have same structure but are not reference-equal — massiv yoki obyekt uchun deepEqual kerak. Xabarning o'zi shuni aytib turibdi: "tuzilishi bir xil".
7.2 Funksiyani throws ga chaqirib berish
assert.throws(applyDiscount(-5000), RangeError) — xato throws dan oldin chiqadi va test "kutilmagan xato" bilan yiqiladi. To'g'risi — arrow: () => applyDiscount(-5000).
7.3 Asinxron testda await yo'q
async funksiya tashlagan xato — rad etilgan Promise (Promise asoslari). Uni assert.rejects tekshiradi va unga albatta await kerak: await assert.rejects(api.ochir(999), { kod: "TOPILMADI" }). await siz test tekshiruv tugashini kutmay "o'tdi" deb chiqadi. Test funksiyasining o'zi ham async bo'lsin: test("…", async () => { … }).
7.4 Testlar bir-biriga bog'liq
Bitta umumiy o'zgaruvchini hamma test o'zgartiradi — tartib o'zgarsa, testlar tasodifan yiqiladi. Har test o'z ma'lumotini yaratsin yoki beforeEach ishlating.
7.5 Hech qachon yiqilmagan test
Testni yozdingiz — u darhol yashil. Kodni ataylab buzib ko'ring (>= ni > ga). Test qizarmasa — u hech narsani tekshirmayapti.
8. Mashqlar
1-mashq (oson): Tekshiruvni to'ldiring
O'zingiz to'ldiring. Massivni solishtirish uchun qaysi tekshiruv kerak?
assert.(["osh", "manti"], ["osh", "manti"]);
Endi o'tgan darsdagi bookingError uchun uchta assert yozing: 25 kishi, 4 kishi soat 19 da, 4 kishi soat 23 da.
Yechim
import assert from "node:assert/strict";
const MAX_GUESTS = 20;
const BOOKING_START = 10; // soat
const BOOKING_END = 22;
function bookingError(booking) {
if (booking.guests < 1) {
return "Kamida 1 kishi";
}
if (booking.guests > MAX_GUESTS) {
return `Ko'pi bilan ${MAX_GUESTS} kishi`;
}
if (booking.hour < BOOKING_START || booking.hour > BOOKING_END) {
return "Bron 10:00–22:00 oralig'ida";
}
return null;
}
assert.equal(
bookingError({ guests: 25, hour: 12 }),
"Ko'pi bilan 20 kishi",
);
assert.equal(bookingError({ guests: 4, hour: 19 }), null);
assert.equal(
bookingError({ guests: 4, hour: 23 }),
"Bron 10:00–22:00 oralig'ida",
);
console.log("Uchala tekshiruv o'tdi");Konsolda:
Uchala tekshiruv o'tdinull ham oddiy qiymat — equal bilan solishtiriladi. 23 — chegaradan bitta ko'p, shuning uchun aynan uni tanladik.
2-mashq (o'rta): isOpen uchun testlar
«Bahor» soat 7:00 dan 23:00 gacha ochiq. isOpen(hour) funksiyasini yozing: hour — soat, 0 dan 23 gacha butun son. Ochilish va yopilish soatlariga nom bering. Keyin describe ichida chegaraviy testlar yozing: 6, 7, 22, 23.
Yechim
import { describe, test } from "node:test";
import assert from "node:assert/strict";
const OPENING_HOUR = 7;
const CLOSING_HOUR = 23;
function isOpen(hour) {
return hour >= OPENING_HOUR && hour < CLOSING_HOUR;
}
describe("isOpen", () => {
test("6:00 — hali yopiq", () => {
assert.equal(isOpen(6), false);
});
test("7:00 — ochildi", () => {
assert.equal(isOpen(7), true);
});
test("22:00 — ochiq", () => {
assert.equal(isOpen(22), true);
});
test("23:00 — yopildi", () => {
assert.equal(isOpen(23), false);
});
});Ishga tushirsangiz — to'rttasi ham ✔, tests 4, suites 1. Endi qizilni ko'ring: < ni <= ga almashtiring — "23:00 — yopildi" testi ✖ bo'ladi (true !== false). Demak, test haqiqatan chegarani himoya qiladi.
3-mashq (qiyin): Avval qizil
Jasur aka yangi qoida aytdi: "10 tadan ortiq ko'k choy buyurtma qilinsa, choy 4 000 so'mdan". Avval test yozing va uning yiqilishini ko'ring, keyin teaCost(qty) ("choy narxi": qty ta choy uchun jami) ni tuzating. Boshlang'ich kod — eski qoida: choy doim 5 000 so'm.
const TEA_PRICE = 5000;
function teaCost(qty) {
return qty * TEA_PRICE;
}Yechim
1-qadam (qizil): testlar yozildi, eski kod bilan "11 ta — arzon" yiqiladi (55000 !== 44000).
2-qadam (yashil):
import { describe, test } from "node:test";
import assert from "node:assert/strict";
const TEA_PRICE = 5000;
const BULK_PRICE = 4000; // ulgurji narx
const BULK_FROM = 10; // shundan KO'P bo'lsa arzon
function teaCost(qty) {
const price = qty > BULK_FROM ? BULK_PRICE : TEA_PRICE;
return qty * price;
}
describe("teaCost", () => {
test("10 ta — oddiy narx", () => {
assert.equal(teaCost(10), 50000);
});
test("11 ta — har biri 4 000", () => {
assert.equal(teaCost(11), 44000);
});
test("0 ta — 0", () => {
assert.equal(teaCost(0), 0);
});
});E'tibor bering: 11 ta choy (44 000) 10 tadan (50 000) arzon chiqyapti. Test buni ochiq ko'rsatdi — bu kod xatosi emas, qoidaning g'alati natijasi. Uni Jasur akaga ko'rsatish kerak. Testlar ko'pincha talabdagi bo'shliqni ham topadi.
4-mashq: Vazifalar qadami — tekshiruv skriptlari testlarga aylanadi
Branch: chore/node-test. Ilova kodi (assets/js) o'zgarmaydi — faqat tekshiruv/ papkasi va package.json.
Nima o'zgaradi.
tekshiruv/{royxat,paket,saqlash,marshrut}.js(/ chiqaruvchi skriptlar) →*.test.jsfayllar:describe/testvaassert.equal/deepEqual/throws/match/rejects. Eski skriptlar o'chiriladi.xotira.js(Node'dalocalStorageo'rinbosari) qoladi.tekshiruv/api.js(haqiqiy API'ga boradi) →tekshiruv/api.tarmoq.js. Nomi*.test.jsemas —npm testuni ishga tushirmaydi, internetsiz ham yashil.package.jsonga ikki skript:"test": "node --test"va"test:tarmoq": "node --test tekshiruv/api.tarmoq.js".READMEdagi "Tekshirish" bo'limi yangilanadi.
Oldin va keyin. Eski skriptdagi yordamchi va bitta tekshiruv:
// Oldin (tekshiruv/royxat.js)
function tekshir(nomi, haqiqiy, kutilgan) {
const belgi = haqiqiy === kutilgan ? "✅" : "❌";
console.log(`${belgi} ${nomi}: ${haqiqiy}`);
}
const royxat = VazifalarRoyxati.dan(boshlangichVazifalar);
tekshir("yangi id", royxat.qosh("Sabzi olish #bozor").id, 4);// Keyin (tekshiruv/royxat.test.js)
// Har test o'z ro'yxati bilan: testlar bir-biriga bog'liq bo'lmasin
function yangiRoyxat() {
return VazifalarRoyxati.dan(boshlangichVazifalar);
}
test("yangi id — eng kattasidan keyingisi", () => {
assert.equal(yangiRoyxat().qosh("Sabzi olish").id, 4);
});
test("o'chirilgan id qayta berilmaydi", () => {
const royxat = yangiRoyxat();
royxat.qosh("Sabzi olish");
royxat.ochir(4);
assert.equal(royxat.qosh("Choy damlash").id, 5);
});Asosiy farq: eski skript bitta umumiy royxat ni ketma-ket o'zgartirardi — tekshiruvlar bir-biriga bog'liq edi. Endi har test yangiRoyxat() bilan o'z ro'yxatini oladi. tekshir yordamchisi kerak emas — uning ishini test va assert qiladi.
Xulqni qotiruvchi testlar. Eski skriptlardagi tekshiruvlarga qo'shimcha, ilovaning hozirgi xulqini yozib qo'yadigan testlar qo'shiladi. Ular "to'g'ri xulq qanday bo'lishi kerak" degan savolga emas, "hozir aynan nima qilyapti" degan savolga javob beradi. Bunday testlar characterization tests (xulqni qotiruvchi testlar) deyiladi — ularni Legacy kod va texnik qarz darsida yana uchratamiz. Ular keyingi darsdagi refaktordan oldin "xavfsizlik to'ri" bo'ladi. Masalan:
eksportMatnining aniq matni: 2 bo'sh joy, maydonlar tartibi, begona maydon chiqmaydi;paketniOqining har xatosi — turi,kodva matni (JSON, TUZILMA, VERSIYA, VAZIFA,cause);saqlaxotira to'lgandafalseqaytaradi,yuklaxotira yopiq bo'lsanull;filtrniYozURL o'zgarmasapushStatechaqirmaydi;holat.jsning xato matnlari;api.js— har metodning HTTP metodi, manzili, tanasi va sarlavhalari, status →kodjadvali;royxat.almashtir(99)(noma'lum id) —TypeError.
Xato matnini tekshiradigan yordamchi (paket.test.js dan) — throws ning ikkinchi argumenti funksiya ham bo'lishi mumkin:
// Xato turi, kodi va matni — uchalasi ham foydalanuvchiga ko'rinadi
function xatoBilan(matn, kod, xabar) {
assert.throws(() => paketniOqi(matn), (xato) => {
assert.ok(xato instanceof VazifaXatosi);
assert.equal(xato.name, "VazifaXatosi");
assert.equal(xato.kod, kod);
assert.equal(xato.message, xabar);
return true;
});
}api.js testlari internetsiz ishlaydi: global fetch o'rniga soxta funksiya qo'yiladi (api.test.js):
// fetch chaqiruvlarini yozib boradi va berilgan javobni qaytaradi
function soxtaFetch(javob = () => new Response("null")) {
return mock.method(globalThis, "fetch", async (url, sozlama) =>
javob(url, sozlama));
}
afterEach(() => mock.restoreAll());mock.method(obyekt, "nomi", yangiFunksiya) obyektning metodini vaqtincha almashtiradi va har chaqiruvni fetch.mock.calls ga yozadi. Test shu ro'yxatdan "qaysi URL'ga, qaysi metod bilan so'rov ketdi?" deb so'raydi. afterEach da mock.restoreAll() — asl fetch qaytariladi, keyingi testga soxtasi o'tib ketmaydi.
Natija. npm test — haqiqiy chiqishning boshi va oxiri:
▶ apiKlient so'rovlari
✔ royxat — GET, tanasiz (45.2753ms)
✔ qosh, yangila, ochir, almashtir — metod, manzil, tana (2.0672ms)
✔ muvaffaqiyatsiz: faqat yozuvlarga ?muvaffaqiyatsiz=1 (1.6976ms)
✔ bo'sh javob — null (0.5178ms)
✔ apiKlient so'rovlari (51.3082ms)
...
ℹ tests 57
ℹ suites 10
ℹ pass 57
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 239.729157 test, 10 guruh, chorak soniya. royxat.test.js da 18 ta test, shundan 5 tasi Unicode regex darsidagi teg tekshiruvlari — kengaytirildi: bo'sh joysiz "Sabzi #bozor,#uy.", U+02BB kod nuqtasi bilan oʻ, '#o'quv', ##bozor, #ДОМ. npm run test:tarmoq (haqiqiy API) — 2/2. assets/js ga tegilmadi.
Testlar haqiqatan himoya qiladimi? Qizilni ko'ramiz. royxat.js ga vaqtincha eski (v3) teglariniOl ni — split(" ") bilan ishlaydiganini — qaytarib, node --test tekshiruv/royxat.test.js ni ishga tushirdik. Yiqilgan to'rtta test (har biridan faqat nomi va actual qiymati qoldirilgan):
✖ teg: vergul va nuqta tegga yopishmaydi actual: [ '#bozor,', '#uy' ]
✖ teg: o'zbekcha apostrof — ', ʻ (U+02BB), ’ actual: [] expected: [ "#o'quv" ] ('#o'quv' dan)
✖ teg: emoji tegga kirmaydi actual: [ '#bozor🛒', '#🛒' ]
✖ teg emas: # yolg'iz, ##, so'z ichidagi # actual: [ '##', '##bozor' ]Hisobot: tests 18 · pass 14 · fail 4. Testlar eski xatoni darhol ushladi — endi u jimgina qaytib kela olmaydi. Yangi kodni qaytargach — hammasi ✔.
Yechim (buyruqlar)
git switch -c chore/node-test
# ... testlarni yozing, eski skriptlarni o'chiring ...
npm test
git add package.json README.md tekshiruv
git commit -m "test: tekshiruv skriptlari node:test testlariga \
aylantirildi, xulqni qotiruvchi testlar qo'shildi"
git push -u origin chore/node-test
gh pr create --fill
gh pr merge --mergegit add tekshiruv o'chirilgan fayllarni ham commit'ga qo'shadi. Commit turi — test: (testlar qo'shildi), branch — chore/ (ilova xulqiga tegmaydigan ish). Diff: 14 fayl, +666 −174 — asosan yangi testlar.
Bundan keyin har refaktor qadamida tekshiruv bitta buyruq: npm test. Keyingi darsda aynan shu testlar himoyasida kod hidlarini tuzatamiz.
9. Real ishda
- Har jiddiy loyihada testlar bor. Pull request ochilganda testlar avtomatik ishga tushadi (CI — uzluksiz integratsiya); bittasi yiqilsa, PR birlashtirilmaydi.
- Test kutubxonalari. Frontend'da ko'pincha Vitest yoki Jest, Node backend'da
node:testham, ular ham. Tuzilishi bir xil:describe,test/it, tekshiruv.node:testni bilgan odam ularga bir kunda o'tadi. - Test turlari. Bugungi testlar — unit testlar: bitta funksiya yoki modul. Undan kattaroqlari — integratsiya testlari (bir nechta modul birga,
api.tarmoq.jskabi haqiqiy server bilan) va E2E (end-to-end) testlar: haqiqiy brauzerda foydalanuvchi kabi bosish. Ularni 22-qismda o'rganamiz. - Intervyu. "Unit test nima?", "Qanday holatlarni test qilasiz?", "TDD nima?", "Mock nima uchun kerak?" — junior intervyusining odatiy savollari. Ko'pincha kichik funksiya berib, "unga test yozing" deyishadi.
Xulosa
- Avtomatik test — kodni tekshiradigan kod: bir marta yoziladi, bitta buyruq bilan har safar hamma holatni tekshiradi.
node:assert/strict:equal(son, satr,null),deepEqual(massiv, obyekt),throws(arrow bilan),ok,match; asinxronda —await assert.rejects.node:test:test(nomi, fn), guruh —describe(it=test), tayyorgarlik —beforeEach; har test mustaqil.node --test*.test.jsfayllarni o'zi topadi;package.jsonda"test": "node --test"→npm test.- Chegaraviy holatlar: chegaradan bitta kam, aynan o'zi, bitta ko'p; bo'sh va noto'g'ri kirish.
- Qizil → yashil → toza: xatoni avval testga aylantiring va yiqilishini ko'ring.
Keyingi dars: Code smell va refactoring — koddagi "hid"larni tanib, testlar himoyasida kichik va xavfsiz qadamlar bilan tuzatamiz.
Manbalar
- Node.js hujjatlari: "Test runner" (
node:test) — nodejs.org/api/test.html (v24) - Node.js hujjatlari: "Assert" (
node:assert/strict) — nodejs.org/api/assert.html - Kent Beck, "Test-Driven Development: By Example", 2002
- Michael Feathers, "Working Effectively with Legacy Code", 2004 — characterization tests
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!