IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API50/51-dars17 daqiqa
Mundarija (30)

Vaqt va tasodifni boshqarish: mock.timers, soxta soat, UUID va seed

Qisqacha: new Date(), Date.now(), crypto.randomUUID() va Math.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 }) va tick(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) ni mock.method almashtira olmaydi — biz sinadik.

Bu darsda

  • Vaqt va tasodifga bog'liq kod nega "beqaror" test berishini tushuntirasiz.
  • mock.timers bilan Date ni 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:

js
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:

text
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):

ts
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. Kodda await 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:

ts
// 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":

ts
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 --> T

Kanon 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:

ts
export function sorovId(yangiId: () => string = randomUUID) { … }

Test (test/mock.test.ts) Express'siz, middleware'ni to'g'ridan chaqiradi:

ts
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:

js
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:

text
default obyekt: soxta-uuid
nomli import: false
TypeError: Cannot redefine property: randomUUID

Uch xulosa:

  1. crypto.randomUUID() (obyekt orqali) — almashtirildi.
  2. import { randomUUID } — almashtirilmadi: nomli import o'z bog'lanishiga ega, obyekt xususiyatini o'zgartirish unga ta'sir qilmaydi.
  3. 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:

js
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:

text
[ 23000, 18000, 31000 ]
23000

Urug' 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() va crypto.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):

js
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):

text
Mustafo Rajabov 22
Mustafo Rajabov 22
Abdulahad Sarvarova

Seed 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):

ts
// 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

js
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
js
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:

text
3599999 tayyor
3600000 muddati o'tgan
3600001 muddati o'tgan

Biz "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
js
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:

text
Guruch,Sut,Choy,Tuxum,Non
true
true
Non,Sut,Choy,Guruch,Tuxum

Bir 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.random dan 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 faqat Date (soxta setTimeout testdagi kutishni osiltirdi).
  • Ukol: soat() va yangiId() parametrlari, sukut — haqiqiysi; global holatsiz, parallel xavfsiz.
  • mock.method default obyektni almashtiradi, nomli importni — yo'q (biz sinadik); shuning uchun ukol.
  • Seed: bir xil urug' — bir xil ketma-ketlik (mulberry32, faker.seed); xavfsizlik uchun faqat crypto.
  • 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Vaqt va tasodifni boshqarish: mock.timers, soxta soat, UUID va seed — IlmHamroh