19-bo‘lim

Testlash

node:test bilan sinovlar, turlar ish vaqtida tekshirilmasligi va tur sinovlarini yozishning uch usuli.

🕑 23 daqiqa o‘qish 📄 776 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Birinchi sinov
  2. describe bilan guruhlash
  3. Sinov turni tekshira olmaydi
  4. Tur sinovlari: @ts-expect-error
  5. Tur sinovlari: turlar tengligi
  6. Soxta obyektlar va satisfies
  7. Jadvalli sinovlar
  8. Xulosa

Sinovlar va turlar bir-birini almashtirmaydi - ular turli narsalarni tekshiradi.

Bu bobda ikkalasini ham yozamiz va nima uchun ikkalasi ham kerakligini aniq ko'ramiz.

Node.js 18 dan beri sinov ishga tushirgichi o'zida bor - qo'shimcha paket kerak emas.

Birinchi sinov #

TS
export function chegirma(narx: number, foiz: number): number {
  return narx - (narx * foiz) / 100;
}
TS
import { test } from "node:test";
import assert from "node:assert/strict";
import { chegirma } from "./dokon";

test("chegirma foizni ayiradi", () => {
  assert.equal(chegirma(50000, 20), 40000);
});

test("nol foiz narxni ozgartirmaydi", () => {
  assert.equal(chegirma(50000, 0), 50000);
});
Terminal
node --import tsx --test src/dokon.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - chegirma foizni ayiradi
ok 2 - nol foiz narxni ozgartirmaydi
# tests 2
# pass 2
# fail 0

node --import tsx TypeScript ni to'g'ridan-to'g'ri ishga tushiradi - oldindan kompilyatsiya qilish shart emas.

Chiqish TAP formatida bo'ladi va har bir sinov uchun duration_ms qatorini ham chiqaradi. Bu qiymat har safar o'zgaradi, shuning uchun darslikda faqat natija qatorlarini ajratib ko'rsatamiz - grep aynan shuning uchun. O'zingizda grep siz ishga tushiring: to'liq chiqish ancha batafsil.

node:assert/strict - assert.equal ni === ga aylantiradi. Oddiy node:assert da u == ishlatadi va "5" == 5 o'tib ketadi.

Eng ko'p ishlatiladigan tasdiqlashlar:

YozuvNimani tekshiradi
assert.equal(a, b)a === b (strict rejimda)
assert.deepEqual(a, b)Obyekt va massivlarni ichma-ich
assert.ok(x)x rost qiymat ekanini
assert.throws(f)f istisno tashlashini
await assert.rejects(p)p rad etilishini
assert.match(s, /re/)Satr naqshga mos kelishini

Obyektlarni solishtirganda equal emas, deepEqual kerak: ikkita alohida obyekt hech qachon === bo'lmaydi.

Xato bo'lsa:

TS
export function chegirma(narx: number, foiz: number): number {
  return narx - foiz;
}
TS
import { test } from "node:test";
import assert from "node:assert/strict";
import { chegirma } from "./dokon";

test("chegirma foizni ayiradi", () => {
  assert.equal(chegirma(50000, 20), 40000);
});
Terminal
node --import tsx --test src/dokon.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
not ok 1 - chegirma foizni ayiradi
# tests 1
# pass 0
# fail 1

Funksiya 50000 - 20 ni hisobladi - turlar jihatidan hammasi to'g'ri, mantiq esa noto'g'ri. Turlar buni hech qachon tuta olmaydi.

describe bilan guruhlash #

TS
export type Mahsulot = { nomi: string; narx: number };

export function jami(mahsulotlar: Mahsulot[]): number {
  return mahsulotlar.reduce((a, m) => a + m.narx, 0);
}
TS
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { jami, type Mahsulot } from "./dokon";

describe("jami", () => {
  it("bosh royxat uchun nol", () => {
    assert.equal(jami([]), 0);
  });

  it("narxlarni qoshadi", () => {
    const m: Mahsulot[] = [
      { nomi: "Olma", narx: 12000 },
      { nomi: "Anor", narx: 25000 },
    ];
    assert.equal(jami(m), 37000);
  });
});
Terminal
node --import tsx --test src/dokon.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - jami
# tests 2
# pass 2
# fail 0

import { jami, type Mahsulot } - 11-bobdagi tur importining qisqa shakli: bitta qatorda ham qiymat, ham tur.

Sinov turni tekshira olmaydi #

Bu - bobning eng muhim qismi:

TS
import { test } from "node:test";
import assert from "node:assert/strict";

type Mahsulot = { nomi: string; narx: number };

const javob = { nomi: "Olma", narx: "12000" };
const m = javob as unknown as Mahsulot;

test("narx son bolishi kerak", () => {
  assert.equal(typeof m.narx, "number");
});
Terminal
echo "--- tsc ---"
tsc --noEmit && echo "xato yo_q"
echo "--- sinov ---"
node --import tsx --test src/tur.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
--- tsc ---
xato yo_q
--- sinov ---
not ok 1 - narx son bolishi kerak
# tests 1
# pass 0
# fail 1

m ning turi Mahsulot deb aytilgan, m.narx esa number. Lekin ish vaqtida u satr edi.

Sabab 1-bobdan ma'lum: turlar kompilyatsiyadan keyin yo'qoladi. typeof m.narx haqiqiy qiymatga qaraydi, turga emas.

Ikkalasi ham kerak: ular turli xatolarni tutadi Turlar tutadi Notogri turdagi argument Yetishmayotgan maydon null tekshirilmagan joy Unutilgan await Unionning unutilgan a'zosi Notogri yozilgan maydon nomi har bir qatorda, bepul Sinovlar tutadi Notogri formula Chetki holatlar (bosh royxat) Notogri tartib Tashqi malumot kutilgandek emas as bilan berilgan yolgon vada Kutubxonaning ozgargan xulqi faqat yozilgan holatlar uchun Uchinchi tur: tur sinovlari Turlarning o'zi to'g'ri yozilganini tekshiradi - @ts-expect-error yoki Teng<A, B> bilan. Kutubxona yozayotgan bo'lsangiz, bular oddiy sinovlar kabi muhim.
Turlar - har bir qatorda, sinovlar - siz yozgan holatlarda

Tur sinovlari: @ts-expect-error #

Turlarning o'zini tekshirish uchun ish vaqti kerak emas - kompilyatorning o'zi yetarli:

TS
export function chegirma(narx: number, foiz: number): number {
  return narx - (narx * foiz) / 100;
}
TS
import { chegirma } from "./dokon";

chegirma(50000, 20);

// @ts-expect-error - satr qabul qilinmasligi kerak
chegirma("50000", 20);

// @ts-expect-error - ikkita argument talab qilinadi
chegirma(50000);

export {};
Terminal
tsc --noEmit && echo "tur sinovi otdi"
Natija
tur sinovi otdi

Bu fayl hech qachon ishga tushmaydi. Uning vazifasi - tsc ni o'tkazish.

Endi kimdir imzoni bo'shashtirsa:

TS
export function chegirma(narx: any, foiz: any): any {
  return narx - (narx * foiz) / 100;
}
TS
import { chegirma } from "./dokon";

// @ts-expect-error - satr qabul qilinmasligi kerak
chegirma("50000", 20);

export {};
Terminal
tsc --noEmit 2>&1 | fold -sw 74
Natija
src/dokon.tur-sinov.ts(3,1): error TS2578: Unused '@ts-expect-error'
directive.

18-bobda ko'rgan TS2578 bu yerda sinovning yiqilishi sifatida ishlaydi: kutilgan xato yo'qolgan, demak himoya zaiflashgan.

Tur sinovlari: turlar tengligi #

@ts-expect-error "bu xato bo'lishi kerak" deydi. Aniq turni tekshirish uchun boshqa usul kerak:

TS
export function chegirma(narx: number, foiz: number): number {
  return narx - (narx * foiz) / 100;
}
TS
import { chegirma } from "./dokon";

type Teng<A, B> =
  (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
    ? true
    : false;

const sinov1: Teng<ReturnType<typeof chegirma>, number> = true;
const sinov2: Teng<Parameters<typeof chegirma>, [number, number]> = true;

console.log(sinov1, sinov2);
Terminal
tsc --noEmit && echo "tsc: xato yo_q"
tsx src/teng.ts
Natija
tsc: xato yo_q
true true
TS
export function chegirma(narx: number, foiz: number): string {
  return String(narx - foiz);
}
TS
import { chegirma } from "./dokon";

type Teng<A, B> =
  (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
    ? true
    : false;

const sinov1: Teng<ReturnType<typeof chegirma>, number> = true;
Terminal
tsc --noEmit 2>&1 | fold -sw 74
Natija
src/teng.ts(8,7): error TS2322: Type 'true' is not assignable to type
'false'.

Teng<A, B> false ga aylandi, true esa unga mos kelmadi.

Teng<A, B> qanday ishlaydi

Bu - TypeScript jamoasining o'zi ishlatadigan hiyla.

Ikkita umumlashtirilmagan funksiya turi solishtiriladi: <T>() => T extends A ? 1 : 2 va shu narsaning B bilan yozilgani. Kompilyator ularni faqat A va B aynan bir xil bo'lgandagina teng deb topadi.

Oddiy A extends B ? ... yetarli emas, chunki any hamma narsaga, never esa hamma narsadan o'tadi.

Amalda bu turni o'zingiz yozmaysiz - expect-type yoki tsd paketini olasiz. Lekin ichkarida aynan shu turadi.

Soxta obyektlar va satisfies #

TS
export type Mahsulot = { id: number; nomi: string; narx: number };

export type Ombor = {
  topish: (id: number) => Promise<Mahsulot | null>;
  saqlash: (m: Mahsulot) => Promise<void>;
};

export async function nomini(ombor: Ombor, id: number): Promise<string> {
  const m = await ombor.topish(id);
  return m === null ? "topilmadi" : m.nomi;
}
TS
import { test } from "node:test";
import assert from "node:assert/strict";
import { nomini, type Ombor } from "./ombor";

const soxta = {
  topish: async (id: number) =>
    id === 1 ? { id: 1, nomi: "Olma", narx: 12000 } : null,
  saqlash: async () => {},
} satisfies Ombor;

test("mavjud mahsulot nomini qaytaradi", async () => {
  assert.equal(await nomini(soxta, 1), "Olma");
});

test("mavjud bolmaganda topilmadi", async () => {
  assert.equal(await nomini(soxta, 99), "topilmadi");
});
Terminal
node --import tsx --test src/ombor.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - mavjud mahsulot nomini qaytaradi
ok 2 - mavjud bolmaganda topilmadi
# tests 2
# pass 2
# fail 0

nomini funksiyasi Ombor turini qabul qiladi, ya'ni haqiqiy ma'lumotlar bazasi kerak emas. Bu - bog'liqlikni tashqaridan berish naqshi va u turlar bilan juda mos tushadi.

satisfies (14-bob) bu yerda aniq bir maqsad bilan ishlatilgan - shartnoma buzilsa, sinov kompilyatsiyada yiqiladi:

TS
export type Mahsulot = { id: number; nomi: string; narx: number };

export type Ombor = {
  topish: (id: number) => Promise<Mahsulot | null>;
  saqlash: (m: Mahsulot) => Promise<void>;
};
TS
import { type Ombor } from "./ombor";

const soxta = {
  topish: async () => null,
} satisfies Ombor;

export {};
Terminal
tsc --noEmit 2>&1 | fold -sw 74
Natija
src/ombor.test.ts(5,3): error TS1360: Type '{ topish: () =>
Promise<null>; }' does not satisfy the expected type 'Ombor'.
  Property 'saqlash' is missing in type '{ topish: () => Promise<null>;
}' but required in type 'Ombor'.
Turlanmagan soxta obyektlar eskiradi

as any yoki jest.fn() bilan yozilgan soxta obyekt haqiqiy interfeys o'zgarganda jim qoladi. Sinov o'tadi, ishlab chiqarish yiqiladi.

satisfies bilan yozilgan soxta obyekt esa darhol ogohlantiradi.

Qoida: har bir soxta obyektga satisfies Interfeys qo'shing.

Jadvalli sinovlar #

TS
export type Tolov =
  | { usul: "naqd"; miqdor: number }
  | { usul: "karta"; raqam: string };

export function tavsif(t: Tolov): string {
  switch (t.usul) {
    case "naqd":
      return `naqd ${t.miqdor}`;
    case "karta":
      return `karta ${t.raqam}`;
    default: {
      const qolgan: never = t;
      return qolgan;
    }
  }
}
TS
import { test } from "node:test";
import assert from "node:assert/strict";
import { tavsif, type Tolov } from "./tolov";

const holatlar: Array<[Tolov, string]> = [
  [{ usul: "naqd", miqdor: 12000 }, "naqd 12000"],
  [{ usul: "karta", raqam: "8600" }, "karta 8600"],
];

for (const [kirish, kutilgan] of holatlar) {
  test(`${kirish.usul} uchun togri tavsif`, () => {
    assert.equal(tavsif(kirish), kutilgan);
  });
}
Terminal
node --import tsx --test src/tolov.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - naqd uchun togri tavsif
ok 2 - karta uchun togri tavsif
# tests 2
# pass 2
# fail 0

Array<[Tolov, string]> - kortejlar massivi (4-bob). Har bir holat turlangan, shuning uchun noto'g'ri holat yozib bo'lmaydi.

Unionga uchinchi a'zo qo'shilsa, tavsif dagi never tekshiruvi (6-bob) darhol ogohlantiradi - lekin sinov jadvaliga yangi qator qo'shishni u talab qilmaydi. Buni qo'lda kuzatish kerak.

Xulosa #

  • Node.js da sinov ishga tushirgichi o'zida bor: node:test va node:assert/strict.
  • node --import tsx --test TypeScript sinovlarini to'g'ridan-to'g'ri ishga tushiradi.
  • node:assert/strict === ishlatadi; oddiy node:assert esa ==.
  • Turlar va sinovlar turli xatolarni tutadi: turlar har bir qatorda, sinovlar esa siz yozgan holatlarda.
  • Ish vaqtidagi sinov turni tekshira olmaydi - turlar kompilyatsiyadan keyin yo'qoladi.
  • @ts-expect-error bilan "bu xato bo'lishi kerak" degan tur sinovini yozish mumkin; imzo bo'shashsa TS2578 chiqadi.
  • Aniq turni tekshirish uchun Teng<A, B> naqshi yoki expect-type / tsd paketi ishlatiladi.
  • Bog'liqlikni tashqaridan berish soxta obyekt yozishni osonlashtiradi.
  • Har bir soxta obyektga satisfies Interfeys qo'shing - shartnoma o'zgarsa, sinov kompilyatsiyada yiqiladi.
  • Jadvalli sinovda holatlar massivini turlang - noto'g'ri holat yozib bo'lmaydi.
Amaliy topshiriq
  1. node:test bilan ikkita sinov yozing va ularni ishga tushiring.
  2. Funksiyani ataylab buzib, not ok chiqishini ko'ring.
  3. describe va it bilan guruhlang.
  4. as unknown as bilan yolg'on tur berib, sinov uni tutishini tasdiqlang.
  5. @ts-expect-error bilan tur sinov fayli yozing.
  6. Funksiya parametrini any qilib, TS2578 ni oling.
  7. Teng<A, B> turini yozib, qaytish turini tekshiring.
  8. Qaytish turini o'zgartirib, tur sinovini yiqiting.
  9. Interfeysga satisfies bilan soxta obyekt yozing.
  10. Interfeysga yangi metod qo'shib, soxta obyekt kompilyatsiyada yiqilishini ko'ring.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.