Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi yo'l: soxta soat (mock.timers)
- 2.1 Date ni to'xtatish va surish
- 2.2 Kanon testi
- 2.3 Tuzoq: nomli import
- 3. Ikkinchi yo'l: ukol (soat parametri)
- 3.1 Kanondagi soatlar
- 3.2 Qaysi biri yaxshiroq?
- 4. Tasodif
- 4.1 UUID: ukol
- 4.2 Tuzoq: mock.method va nomli import
- 4.3 Seed: takrorlanadigan tasodif
- 4.4 faker: seed'li test ma'lumotlari
- 5. Chegaraviy vaqtlar
- 6. API qadami: test/mock.test.ts yakuni (holat 51, 6-qism)
- 7. Ko'p uchraydigan xatolar
- 7.1 Vaqtni "taxminan" tekshirish
- 7.2 Hamma taymerni soxtalash
- 7.3 Nomli importni mock.method bilan almashtirishga urinish
- 7.4 reset siz global soxta soat
- 7.5 Kriptografik bo'lmagan tasodif xavfsizlik uchun
- 8. Mashqlar
- 1-mashq (oson): natijani ayting
- 2-mashq (o'rta): eksport muddati
- 3-mashq (qiyin): seed'li tartiblash testi
- 4-mashq: API qadami
- 9. Real ishda
- Xulosa
- Manbalar
Vaqt va tasodifni boshqarish: mock.timers, soxta soat, UUID va seed
Qisqacha:
new Date(),Date.now(),crypto.randomUUID()vaMath.random()har chaqiruvda boshqa natija beradi — ular bor kodni oddiy usulda test qilib bo'lmaydi. Ikki yo'l bor. Ukol: vaqt va tasodif manbai parametr bo'ladi (XotiraOmbori(soat),sorovId(yangiId)) — testda qat'iy qiymat beriladi. Soxta soat:t.mock.timers.enable({ apis: ["Date"], now })vatick(90_000)— kod o'zgarmaydi, vaqt testda "to'xtaydi" va qo'lda suriladi. Tasodif uchun — seed: bir xil urug' har safar bir xil ketma-ketlik beradi (faker ham). Tuzoq:import { randomUUID }(nomli import) nimock.methodalmashtira olmaydi — biz sinadik.
Bu darsda
- Vaqt va tasodifga bog'liq kod nega "beqaror" test berishini tushuntirasiz.
mock.timersbilanDateni to'xtatasiz, surasiz va qaytarasiz.- Soat va ID generatorini parametr qilib (ukol) testga qulay kod yozasiz.
- Seed'li tasodif va faker bilan takrorlanadigan test ma'lumotlarini yasaysiz.
- Chegaraviy vaqtlarni (yarim tun, 24 soat + 1 ms) aniq tekshirasiz.
Oldin bilishingiz kerak: timers va timers/promises chuqur, Backend'da sana va vaqt, Mock, stub, spy va fake, crypto 1: tasodif, hash va HMAC.
1. Nega bu kerak?
vazifalar-api da vaqt va tasodif hamma joyda:
| Joy | Nimaga bog'liq |
|---|---|
yaratildi, yangilandi |
new Date() |
| Idempotency-Key muddati | 24 soat (Date.now()) |
X-Request-Id |
crypto.randomUUID() |
?dan=2026-10-24 |
Toshkent yarim tuni |
| eksport natijasi | 1 soat yashaydi |
"yangilandi PATCH'dan keyin o'zgardimi?" testini yozing. assert.notEqual(oldin, keyin) — ikki so'rov bir millisekundda bajarilsa, vaqt bir xil chiqadi va test ba'zan yiqiladi. "24 soatdan keyin kalit qayta ishlaydi" — 24 soat kutib bo'lmaydi.
Bunday testlar flaky (beqaror) bo'ladi (FT-15 atamasi): kod o'zgarmaydi, natija o'zgaradi. Ularga jamoa ishonmay qo'yadi.
Hayotiy o'xshatish: kino suratga olish. Rejissyor quyosh botishini kutmaydi — "soat 19:00" sahnasini studiyada chiroq bilan yasaydi va kerak bo'lsa yana va yana takrorlaydi. Test ham vaqtni kutmaydi — uni o'zi "o'rnatadi".
2. Birinchi yo'l: soxta soat (mock.timers)
2.1 Date ni to'xtatish va surish
timers darsida mock.timers bilan setTimeout ni soxtalagansiz. Date ham soxtalanadi:
import { mock } from "node:test";
mock.timers.enable({
apis: ["Date"],
now: new Date("2026-10-24T18:59:59.999Z"),
});
console.log(new Date().toISOString(), Date.now());
mock.timers.tick(1);
console.log(new Date().toISOString());
mock.timers.setTime(Date.parse("2026-10-25T00:00:00.000Z"));
console.log(new Date().toISOString());
mock.timers.reset();
console.log(new Date().getFullYear() >= 2026);Konsolda:
2026-10-24T18:59:59.999Z 1792868399999
2026-10-24T19:00:00.000Z
2026-10-25T00:00:00.000Z
true| Metod | Nima qiladi |
|---|---|
enable({ apis, now }) |
tanlangan API'larni soxtalaydi; now — boshlang'ich vaqt |
tick(ms) |
vaqtni surish (va o'tib ketgan taymerlarni bajarish) |
setTime(ms) |
soatni aniq vaqtga qo'yish |
runAll() |
kutib turgan hamma taymerni bajarish |
reset() |
haqiqiy soatga qaytish |
Ikkinchi qatorga qarang: 18:59:59.999 + 1 ms = 19:00:00.000 UTC — Toshkentda aynan yarim tun (UTC+5). Sana va vaqt darsidagi kun chegarasini millisekund aniqligida tekshirish mumkin.
Test ichida t.mock.timers ishlating — test tugaganda avtomatik reset. Yuqorida modul darajasidagi mock — oddiy skriptda ham ishlashi uchun.
2.2 Kanon testi
test/mock.test.ts (holat 51):
test("soxta vaqt (mock.timers Date): yaratildi va yangilandi — " +
"deterministik", async (t) => {
t.mock.timers.enable({
apis: ["Date"],
now: new Date("2026-10-24T05:00:00.000Z"),
});
const app = ilovaYarat({ ombor: new XotiraOmbori() });
const r = await request(app).post("/v1/vazifalar")
.send({ matn: "a" }).expect(201);
assert.equal(
(r.body as { yaratildi: string }).yaratildi,
"2026-10-24T05:00:00.000Z",
);
t.mock.timers.tick(90_000); // 1.5 daqiqa "o'tdi"
const p = await request(app).patch("/v1/vazifalar/1")
.send({ bajarildi: true }).expect(200);
assert.equal(
(p.body as { yangilandi: string }).yangilandi,
"2026-10-24T05:01:30.000Z",
);
});Kod (XotiraOmbori sukut soati () => new Date()) o'zgarmadi — soxta Date uni o'zi ushladi. Natija har yurishda bir xil: yaratildi 05:00:00, yangilandi 05:01:30.
Nega faqat apis: ["Date"], setTimeout emas? Biz ikkalasini sinadik. Shu testning o'zida apis: ["Date", "setTimeout"] — baribir o'tdi (186 ms): Express va supertest bu yo'lda setTimeout ni kutmadi. Lekin webhook testiga t.mock.timers.enable({ apis: ["setTimeout"] }) qo'shganimizda test osilib qoldi — 30 soniyadan keyin majburan to'xtatdik. Sabab — testning o'zidagi await kut(20): soxta setTimeout faqat tick() bilan ishlaydi, tick esa hech qachon chaqirilmadi. Qoida: faqat kerakli API'ni soxtalang.
2.3 Tuzoq: nomli import
timers darsida ko'rgansiz: import { setTimeout as kut } from "node:timers/promises" — nomli import soxta soatga bo'ysunmadi. Date global obyekt — u bilan bunday muammo yo'q. Lekin xuddi shu tuzoq tasodifda qaytadi («Tasodif» bo'limi).
Tekshirib ko'ring: Testda
t.mock.timers.enable({ apis: ["setTimeout"] })qildingiz va test hech qachon tugamayapti. Koddaawait new Promise((r) => setTimeout(r, 20))bor. Nega?
Javob
Soxta setTimeout callback'ni faqat tick() (yoki runAll()) chaqirilganda bajaradi. Kod esa await da kutib turibdi — tick ga navbat kelmaydi, promise hech qachon bajarilmaydi. Biz aynan shu holatni kanon webhook testida ko'rdik. Yechim: faqat apis: ["Date"]; taymerlarni soxtalash kerak bo'lsa — kutish o'rniga tick ni o'zingiz chaqiring va buni HTTP'siz, toza funksiya darajasida qiling. Supertest va Express esa bizning sinovda soxta setTimeout bilan ham ishladi — lekin bunga tayanmang, kutubxona ichi o'zgarishi mumkin.
3. Ikkinchi yo'l: ukol (soat parametri)
3.1 Kanondagi soatlar
Kanonda ikkala yo'l ham bor. Ombor va idempotentlik ombori soatni parametr sifatida oladi:
// src/vazifalar/xotira-ombor.ts
constructor(soat: () => Date = () => new Date(), surat?: Surat) {
this.#soat = soat;
}
// src/umumiy/idempotentlik.ts
constructor(soat: () => number = Date.now, maks = 10_000) {
this.#soat = soat;
}24 soatlik muddat testi (test/idempotentlik.test.ts) — 24 soatni bir qatorda "o'tkazadi":
let soat = 0;
// … ombor: new XotiraIdempotentlikOmbori(() => soat)
const r1 = await post({ matn: "" }, "kalit-0004"); // 422
const r2 = await post({ matn: "" }, "kalit-0004"); // saqlangan javob
soat += 24 * 60 * 60 * 1000 + 1;
const r3 = await post({ matn: "" }, "kalit-0004");
assert.equal(r3.headers.get("idempotency-replayed"), null);+ 1 — muhim: 24 soat o'tgandan keyin, aynan 24 soatda emas. Chegara qayerda — kod va test kelishgan bo'lishi kerak (Mock, stub, spy va fake darsidagi TaymerOmbori mashqi).
3.2 Qaysi biri yaxshiroq?
Ukol (soat parametri) |
Soxta soat (mock.timers) |
|
|---|---|---|
| Kod o'zgarishi | parametr qo'shiladi | yo'q |
| Global holat | yo'q | bor (test oxirida tiklanadi) |
| Parallel testlar | xavfsiz | bitta fayl ichida — ehtiyot bilan |
| Uchinchi tomon kodi | ta'sir qilmaydi | uning Date i ham soxta |
| Qachon | o'z kodingiz, ko'p ishlatiladigan joy | eski kod, kamdan-kam test |
Qaror qanday qilinadi — diagrammaga qarang:
flowchart TD
V{Vaqt yoki tasodif qayerdan?} -->|o'z kodim| U[parametr: soat, yangiId]
V -->|eski yoki begona kod| G{Global obyektmi?}
G -->|Date, globalThis.crypto| M[mock.timers / mock.method]
G -->|nomli import| X[tikuv qo'shish shart]
U --> T[test: qat'iy qiymat]
M --> TKanon ikkalasini ham ko'rsatdi: omborlarda — ukol, HTTP darajasidagi bitta testda — mock.timers. Frontend'da buning o'xshashi — vi.useFakeTimers() va vi.setSystemTime(sana) (Vitest II): g'oya bir xil, nomlari boshqa.
4. Tasodif
4.1 UUID: ukol
X-Request-Id bo'lmasa, sorovId() yangi UUID yasaydi. Kanon generatorni parametr qilgan:
export function sorovId(yangiId: () => string = randomUUID) { … }Test (test/mock.test.ts) Express'siz, middleware'ni to'g'ridan chaqiradi:
test("soxta tasodif: sorovId generatori ukol qilinadi", () => {
let n = 0;
const mw = sorovId(() => `id-${String(++n).padStart(8, "0")}`);
const sarlavhalar: Record<string, string> = {};
const req = { get: () => undefined, sorovId: "" } as unknown as
Parameters<typeof mw>[0];
const res = {
set: (k: string, v: string) => { sarlavhalar[k] = v; },
} as unknown as Parameters<typeof mw>[1];
mw(req, res, () => undefined);
assert.equal(sarlavhalar["X-Request-Id"], "id-00000001");
});Bu — unit test: req va res o'rnida minimal soxta obyektlar (stub). as unknown as — TypeScript'ga "bu to'liq Express obyekti emas, bilaman" deyish; faqat testda.
4.2 Tuzoq: mock.method va nomli import
Ukolsiz kodda crypto.randomUUID ni mock.method bilan almashtirsa bo'ladimi? Sinadik:
import { mock } from "node:test";
import crypto, { randomUUID } from "node:crypto";
mock.method(crypto, "randomUUID", () => "soxta-uuid");
console.log("default obyekt:", crypto.randomUUID());
console.log("nomli import:", randomUUID() === "soxta-uuid");
import * as ns from "node:crypto";
try {
mock.method(ns, "randomUUID", () => "x");
} catch (e) {
console.log(`${e.name}: ${e.message}`);
}Konsolda:
default obyekt: soxta-uuid
nomli import: false
TypeError: Cannot redefine property: randomUUIDUch xulosa:
crypto.randomUUID()(obyekt orqali) — almashtirildi.import { randomUUID }— almashtirilmadi: nomli import o'z bog'lanishiga ega, obyekt xususiyatini o'zgartirish unga ta'sir qilmaydi.- Modul nomlar fazosini (
import * as) umuman o'zgartirib bo'lmaydi — ESM'da u muzlatilgan.
Kanon sorov-id.ts aynan nomli import ishlatadi — shuning uchun ukol tanlangan. Global crypto.randomUUID (import'siz, globalThis.crypto) ni esa mock.method(globalThis.crypto, "randomUUID", …) almashtiradi — biz sinadik, ishladi. Qoida: o'z kodingizda tasodif manbaini parametr qiling — import usulidan qat'i nazar ishlaydi.
4.3 Seed: takrorlanadigan tasodif
Ba'zan tasodif kerak: 100 ta har xil vazifa bilan yuk testi, tasodifiy tartib bilan saralash testi. Lekin test yiqilsa, xuddi o'sha ma'lumotni qayta olish kerak. Yechim — seed (urug'): soxta tasodif generatori bir xil urug'dan bir xil ketma-ketlik beradi.
Math.random ga urug' berib bo'lmaydi. Kichik generator — mulberry32 (keng tarqalgan 32-bitli algoritm) — brauzerda ham ishlaydi:
function mulberry32(urug) {
let h = urug >>> 0;
return () => {
h = (h + 0x6d2b79f5) | 0;
let t = Math.imul(h ^ (h >>> 15), 1 | h);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const tasodif = mulberry32(42);
const narx = () => 5000 + Math.floor(tasodif() * 31) * 1000;
console.log([narx(), narx(), narx()]);
const yana = mulberry32(42);
console.log(5000 + Math.floor(yana() * 31) * 1000);Konsolda:
[ 23000, 18000, 31000 ]
23000Urug' 42 — har kompyuterda, har yurishda 23000, 18000, 31000. Ikkinchi generator ham 42 dan boshladi — birinchi qiymat yana 23 000. Test yiqilsa, logga urug'ni yozing va xuddi shu ma'lumot bilan qayta yurgizing.
Diqqat: bunday generatorlar kriptografik emas — token, parol, ID uchun hech qachon ishlatilmaydi. U yerda faqat
crypto.randomUUID()vacrypto.getRandomValues()(crypto 1).
4.4 faker: seed'li test ma'lumotlari
faker (@faker-js/faker) — ism, manzil, telefon, matn kabi "haqiqiyga o'xshash" ma'lumot yasaydi; o'zbek lotin lokali ham bor. Biz alohida papkada sinadik (@faker-js/faker@10.6.0, vazifalar-api ga kirmaydi):
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";
faker.seed(4);
const ism = () =>
`${faker.person.firstName()} ${faker.person.lastName()}`;
console.log(ism(), faker.number.int({ min: 1, max: 100 }));
faker.seed(4);
console.log(ism(), faker.number.int({ min: 1, max: 100 }));
console.log(ism());Biz ishga tushirdik (ikki marta — natija bir xil):
Mustafo Rajabov 22
Mustafo Rajabov 22
Abdulahad SarvarovaSeed 4 — ikki marta bir xil. Uchinchi qatorga qarang: ism erkak, familiya ayol shaklida — faker ma'nosini tekshirmaydi, u faqat ro'yxatlardan tanlaydi. Yana bir kuzatuv: boshqa urug'larda faker o'zbek nomlarini ’ va ` belgilari bilan yozdi (masalan "Oltinkol") — bizning kursdagi oddiy apostrof 'emas. Test ma'lumotida bunday belgilar foydali ham: teg regex'i#o‘quv` ni ham tushunishini tekshirish uchun.
Kanonda faker yo'q: testlar kam va ma'lumot qo'lda yozilgan ("Non olish #uy") — o'qish oson va chegaraviy holatlar aniq tanlangan. Faker katta, ko'p yozuvli testlar va demo ma'lumotlar uchun foydali.
5. Chegaraviy vaqtlar
Vaqt xatolari chegarada yashaydi. Kanon test/sana.test.ts dagi holatlar (holat 40):
| Holat | Nimani tekshiradi |
|---|---|
kunBoshiUtc("2026-10-24", "Asia/Tashkent") |
2026-10-23T19:00:00.000Z |
| Nyu-York yoz va qish | DST: −4 va −5 soat |
?dan=2026-10-24 |
18:30Z (Toshkentda 23-okt) chiqmaydi, 19:30Z chiqadi |
Shu ro'yxatga qo'shiladigan odatiy chegaralar: oy oxiri (31 → 1), yil oxiri, 29-fevral, aynan muddat (24 soat va 24 soat + 1 ms), UTC yarim tuni va mahalliy yarim tun farqi. Har birida — mock.timers yoki soat parametri bilan aniq vaqt.
6. API qadami: test/mock.test.ts yakuni (holat 51, 6-qism)
Bugun faylga ikki test qo'shiladi: «Kanon testi» (soxta vaqt) va «UUID: ukol» (soxta tasodif). Endi fayl to'liq (holat 51; manzil example.com ga almashtirildi — Tashqi HTTP darsidagi sabab; uzun qatorlar bo'lindi):
// mock.test.ts — test double'lar: spy (mock.fn),
// stub (mock.method),
// fake (soxta fetch, soxta ombor), soxta vaqt (mock.timers).
// Qoida: o'zimizning chegaramizni soxtalaymiz (fetch tikuvi,
// ombor interfeysi) — Express yoki Zod ichini emas
import { test } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { ilovaYarat } from "../src/ilova.ts";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
import { webhookXabarchi } from "../src/umumiy/webhook.ts";
import { sorovId } from "../src/umumiy/sorov-id.ts";
import { xotiraLogi } from "./yordamchi.ts";
const kut = (ms: number) => new Promise((r) => setTimeout(r, ms));
test("webhook: faqat false → true o'tishida, bitta marta; " +
"tana va URL to'g'ri (spy — mock.fn)", async (t) => {
// typeof fetch imzosi — chaqiruv argumentlari tipli bo'lsin
const soxtaFetch = t.mock.fn<typeof fetch>(() =>
Promise.resolve(new Response(null, { status: 204 })));
const { log, qatorlar } = xotiraLogi();
const xabarchi = webhookXabarchi("https://hook.example.com/v", {
fetchFn: soxtaFetch,
log: () => log,
});
const app = ilovaYarat({ ombor: new XotiraOmbori(), xabarchi });
await request(app).post("/v1/vazifalar")
.send({ matn: "Non" }).expect(201);
// bajarildi o'zgarmadi
await request(app).patch("/v1/vazifalar/1")
.send({ matn: "Non olish" }).expect(200);
await request(app).patch("/v1/vazifalar/1")
.send({ bajarildi: true }).expect(200);
// allaqachon true — qayta yuborilmaydi
await request(app).patch("/v1/vazifalar/1")
.send({ bajarildi: true }).expect(200);
await kut(20);
assert.equal(soxtaFetch.mock.callCount(), 1);
const [url, init] = soxtaFetch.mock.calls[0]?.arguments ?? [];
assert.equal(url, "https://hook.example.com/v");
assert.equal(init?.method, "POST");
assert.ok(typeof init.body === "string");
assert.deepEqual(
(JSON.parse(init.body) as { vazifa: unknown }).vazifa,
{ id: 1, matn: "Non olish" },
);
assert.ok(qatorlar.some(
(q) => q.msg === "webhook yuborildi" && q.status === 204,
));
});
test("webhook: tashqi server yiqilsa — javob baribir 200, " +
"xato logda (yutib yuborilmaydi)", async () => {
const { log, qatorlar } = xotiraLogi();
const xabarchi = webhookXabarchi("https://hook.example.com/v", {
fetchFn: () => Promise.reject(new TypeError("fetch failed")),
log: () => log,
});
const app = ilovaYarat({ ombor: new XotiraOmbori(), xabarchi });
await request(app).post("/v1/vazifalar")
.send({ matn: "a" }).expect(201);
await request(app).patch("/v1/vazifalar/1")
.send({ bajarildi: true }).expect(200);
await kut(20);
assert.ok(qatorlar.some(
(q) => q.msg === "webhook yuborilmadi" && q.daraja === "warn",
));
});
test("stub (mock.method): ombor.yarat bir marta chaqirildi — " +
"Idempotency-Key takrorida ombor CHAQIRILMAYDI", async (t) => {
const ombor = new XotiraOmbori();
const yarat = t.mock.method(ombor, "yarat");
const app = ilovaYarat({ ombor });
for (let i = 0; i < 3; i++) {
await request(app).post("/v1/vazifalar")
.set("Idempotency-Key", "spy-0001")
.send({ matn: "bir marta" }).expect(201);
}
assert.equal(yarat.mock.callCount(), 1);
});
test("soxta vaqt (mock.timers Date): yaratildi va yangilandi — " +
"deterministik", async (t) => {
t.mock.timers.enable({
apis: ["Date"],
now: new Date("2026-10-24T05:00:00.000Z"),
});
const app = ilovaYarat({ ombor: new XotiraOmbori() });
const r = await request(app).post("/v1/vazifalar")
.send({ matn: "a" }).expect(201);
assert.equal(
(r.body as { yaratildi: string }).yaratildi,
"2026-10-24T05:00:00.000Z",
);
t.mock.timers.tick(90_000); // 1.5 daqiqa "o'tdi"
const p = await request(app).patch("/v1/vazifalar/1")
.send({ bajarildi: true }).expect(200);
assert.equal(
(p.body as { yangilandi: string }).yangilandi,
"2026-10-24T05:01:30.000Z",
);
});
test("soxta tasodif: sorovId generatori ukol qilinadi — " +
"UUID o'rniga ma'lum qiymat", () => {
let n = 0;
const mw = sorovId(() => `id-${String(++n).padStart(8, "0")}`);
const sarlavhalar: Record<string, string> = {};
const req = { get: () => undefined, sorovId: "" } as unknown as
Parameters<typeof mw>[0];
const res = {
set: (k: string, v: string) => { sarlavhalar[k] = v; },
} as unknown as Parameters<typeof mw>[1];
mw(req, res, () => undefined);
assert.equal(sarlavhalar["X-Request-Id"], "id-00000001");
});Beshta test, uch darsdan: spy va stub (BT-05), webhook (BT-07), vaqt va tasodif (bugun). Hali commit qilmang — oxirgi qadam keyingi darsda. Bu kesimda kod holat 51 bilan bir xil: biz nusxada tekshirdik — tip 0, lint 0, testlar 67/67 (yuqoridagi fayl aynan shu ko'rinishda).
7. Ko'p uchraydigan xatolar
7.1 Vaqtni "taxminan" tekshirish
assert.ok(Date.now() - t < 1000) — mashina band bo'lsa yiqiladi. Soxta soat bilan aniq qiymat.
7.2 Hamma taymerni soxtalash
Testdagi await kut(20) tugamaydi — test osilib qoladi («Tekshirib ko'ring» savoli, biz sinadik). apis: ["Date"].
7.3 Nomli importni mock.method bilan almashtirishga urinish
«Tuzoq» bo'limi: import { randomUUID } o'zgarmaydi. Parametr (ukol).
7.4 reset siz global soxta soat
Keyingi testlar 2026-10-24 05:00 da "qotib" qoladi. t.mock.timers — avtomatik tiklanadi.
7.5 Kriptografik bo'lmagan tasodif xavfsizlik uchun
Math.random() yoki seed'li generatordan token. crypto.randomUUID(), crypto.getRandomValues().
8. Mashqlar
1-mashq (oson): natijani ayting
import { mock } from "node:test";
mock.timers.enable({ apis: ["Date"], now: 0 });
mock.timers.tick(24 * 60 * 60 * 1000);
console.log(new Date().toISOString());Yechim
1970-01-02T00:00:00.000Z — now: 0 — epoch boshi (1970-01-01 00:00 UTC), unga bir sutka qo'shildi. now ga son ham, Date ham berish mumkin.
2-mashq (o'rta): eksport muddati
Eksport natijasi 1 soat yashaydi. eksportHolati(ish, soat) funksiyasini yozing: ish = { tayyor: 1792823725000 } (ms), soat() — hozirgi ms. 1 soatgacha — "tayyor", undan keyin — "muddati o'tgan". Soat parametri bilan uch nuqtani tekshiring: 59:59.999, aynan 60:00, 60:00.001. Qaysi biri chegara — o'zingiz hal qiling va testda yozing.
Yechim
const SOAT = 60 * 60 * 1000;
function eksportHolati(ish, soat = Date.now) {
return soat() - ish.tayyor < SOAT ? "tayyor" : "muddati o'tgan";
}
const ish = { tayyor: 1792823725000 };
for (const farq of [SOAT - 1, SOAT, SOAT + 1]) {
console.log(farq, eksportHolati(ish, () => ish.tayyor + farq));
}Konsolda:
3599999 tayyor
3600000 muddati o'tgan
3600001 muddati o'tganBiz "aynan 1 soatda — muddati o'tgan" deb tanladik (<). Boshqacha tanlash ham mumkin — muhimi, chegara yozilgan va testlangan. Kanondagi idempotentlik testi ham shunday: 24 soat + 1 ms.
3-mashq (qiyin): seed'li tartiblash testi
aralashtir(massiv, tasodif) — Fisher–Yates bilan massivni aralashtiradi (yangi massiv qaytaradi). tasodif — () => 0..1 funksiya. «Seed» bo'limidagi mulberry32 bilan: (1) urug' 7 bilan ikki marta aralashtirish bir xil natija bersin; (2) natija asl massivning hamma elementlarini saqlasin; (3) asl massiv o'zgarmasin. Massiv: ["Non", "Sut", "Choy", "Guruch", "Tuxum"].
Yechim
function mulberry32(urug) {
let h = urug >>> 0;
return () => {
h = (h + 0x6d2b79f5) | 0;
let t = Math.imul(h ^ (h >>> 15), 1 | h);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function aralashtir(massiv, tasodif = Math.random) {
const m = [...massiv];
for (let i = m.length - 1; i > 0; i--) {
const j = Math.floor(tasodif() * (i + 1));
[m[i], m[j]] = [m[j], m[i]];
}
return m;
}
const asl = ["Non", "Sut", "Choy", "Guruch", "Tuxum"];
const a = aralashtir(asl, mulberry32(7));
const b = aralashtir(asl, mulberry32(7));
console.log(a.join(","));
console.log(a.join() === b.join());
console.log([...a].sort().join() === [...asl].sort().join());
console.log(asl.join(","));Konsolda:
Guruch,Sut,Choy,Tuxum,Non
true
true
Non,Sut,Choy,Guruch,TuxumBir xil urug' — bir xil tartib (2-qator). Saralab solishtirish — elementlar yo'qolmagan (3-qator). Asl massiv joyida (4-qator) — [...massiv] nusxa tufayli. Ishlab chiqarishda aralashtir(asl) — sukut Math.random, testda — seed.
4-mashq: API qadami
test/mock.test.ts ga «API qadami» bo'limidagi oxirgi ikki testni qo'shing. npm test — kanon bo'yicha jami 67 test. Keyin tajriba: (1) soxta vaqt testida apis: ["Date"] ni apis: ["Date", "setTimeout"] ga o'zgartiring; (2) birinchi webhook testining boshiga t.mock.timers.enable({ apis: ["setTimeout"] }) qo'shing. Har birida nima bo'ladi? Asl holiga qaytaring. Commit qilmang.
Yechim
67 — holat 51 ning yakuniy soni (tekshiruv-51.txt). Biz ikkala tajribani kanon nusxasida yurgizdik: (1) o'tdi — bu testda hech kim taymerni kutmaydi; (2) osilib qoldi — testning await kut(20) i soxta setTimeout ni kutadi. Ctrl+C bilan to'xtating. Xulosa — «Kanon testi» bo'limidagi qoida: faqat kerakli API'ni soxtalang.
9. Real ishda
- To'lov va obunalar: "oy oxirida yechish", "trial 14 kun" — vaqt chegarasi testlarisiz bunday kod xavfli; kompaniyalar soat obyektini (
Clock) DI orqali beradi. - Cron va fon ishlari (29-qism, navbatlar) — ertangi kunni kutmasdan soxta soat bilan tekshiriladi.
- Ma'lumotlar bazasi
now()funksiyasi (26-qism) ham vaqt manbai — testda uni ham boshqarish kerak bo'ladi. - Intervyu savollari: "Vaqtga bog'liq kodni qanday test qilasiz?", "Flaky test sabablari?", "Seed nima?", "Nega
Math.randomdan token yasash mumkin emas?".
Xulosa
- Vaqt va tasodif — flaky testlarning asosiy manbai; ularni boshqarish kerak.
t.mock.timers.enable({ apis: ["Date"], now }),tick,setTime,reset; HTTP testlarida faqatDate(soxtasetTimeouttestdagi kutishni osiltirdi).- Ukol:
soat()vayangiId()parametrlari, sukut — haqiqiysi; global holatsiz, parallel xavfsiz. mock.methoddefault obyektni almashtiradi, nomli importni — yo'q (biz sinadik); shuning uchun ukol.- Seed: bir xil urug' — bir xil ketma-ketlik (mulberry32,
faker.seed); xavfsizlik uchun faqatcrypto. - Chegaralar: yarim tun (Toshkent 19:00Z), aynan muddat va muddat + 1 ms.
Keyingi dars: Coverage va test sifati metrikalari — --experimental-test-coverage, satr va tarmoq qamrovi, chegaralar, c8, mutation testing va nega 97.98 % "hammasi tekshirilgan" degani emas; vazifalar-api v1.0.0 commit'i.
Manbalar
- Node.js hujjati: Test runner — Timers (MockTimers) (v24).
- faker: Usage — seed, localization (10.x).
- Kanon:
vazifalar-apiholat 51 (test/mock.test.ts,test/idempotentlik.test.ts,test/sana.test.ts,src/umumiy/sorov-id.ts).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!