# JavaScript kodni testlash

> Test nima uchun kerak: birlik testlari, Vitest bilan yozish, expect va matcherlar, AAA naqshi hamda testga qulay kod yozish. Amaliy misollar bilan.

Manba: https://learncode.uz/javascript/testlash/
Sana: 2026-06-08

Kodni har o'zgartirishda qo'lda tekshirish uzoq va ishonchsiz. **Test** —
kodni tekshiradigan kod: bir marta yoziladi, har safar avtomatik ishlaydi.

## Nima uchun kerak

> **Atama**
>
> **Birlik testi** (unit test) — bitta funksiya yoki modulni alohida tekshiradi.
> **Integratsiya testi** — bir necha qismning birga ishlashini. **E2E test** —
> butun ilovani foydalanuvchi ko'zi bilan.
>
> Piramida qoidasi: birlik testlari ko'p va tez, E2E testlar kam va sekin.

Asosiy foyda — **regressiyadan himoya**: eski kodni o'zgartirganda nimadir
buzilsa, test darhol aytadi.

## Testsiz tekshiruv

Avval oddiy yondashuvni ko'ramiz:

```js
function discount(price, percent) {
  if (typeof price !== "number" || price < 0) {
    throw new Error("narx musbat son bo'lishi kerak");
  }
  if (percent < 0 || percent > 100) {
    throw new RangeError("foiz 0-100 oralig'ida");
  }
  return Math.round(price * (1 - percent / 100));
}

// Qo'lda tekshirish
console.log(discount(100000, 10));
console.log(discount(100000, 0));
console.log(discount(99999, 33));
```

Har o'zgarishdan keyin natijalarni **ko'z bilan** solishtirish kerak. Test
buni avtomatlashtiradi.

## Oddiy test yozuvchi

Kutubxonasiz ham tamoyilni ko'rsatish mumkin:

```js
let passedCount = 0, failedCount = 0;

function test(title, action) {
  try {
    action();
    passedCount++;
    console.log("✓ " + title);
  } catch (error) {
    failedCount++;
    console.log("✗ " + title + " — " + error.message);
  }
}

function expect(value) {
  return {
    toBe(expectedValue) {
      if (value !== expectedValue) {
        throw new Error("kutilgan " + expectedValue + ", olingan " + value);
      }
    },
    toThrow() {
      if (typeof value !== "function") throw new Error("funksiya kerak");
      try {
        value();
      } catch {
        return;
      }
      throw new Error("xato kutilgan edi, lekin bo'lmadi");
    },
  };
}

function discount(price, percent) {
  if (percent < 0 || percent > 100) throw new RangeError("foiz 0-100");
  return Math.round(price * (1 - percent / 100));
}

test("10% chegirma to'g'ri hisoblanadi", () => {
  expect(discount(100000, 10)).toBe(90000);
});

test("0% chegirmada narx o'zgarmaydi", () => {
  expect(discount(100000, 0)).toBe(100000);
});

test("natija yaxlitlanadi", () => {
  expect(discount(99999, 33)).toBe(66999);
});

test("noto'g'ri foizda xato beradi", () => {
  expect(() => discount(100, 150)).toThrow();
});

console.log("—");
console.log("O'tdi: " + passedCount + ", yiqildi: " + failedCount);
```

Haqiqiy test kutubxonalari ham aynan shu ishni bajaradi — faqat ancha
boyroq imkoniyatlar bilan.

## Vitest bilan

```bash
npm install -D vitest
```

`package.json` ga:

```json
{
  "scripts": {
    "test": "vitest"
  }
}
```

`discount.js`:

```js
export function discount(price, percent) {
  if (percent < 0 || percent > 100) throw new RangeError("foiz 0-100");
  return Math.round(price * (1 - percent / 100));
}
```

`discount.test.js`:

```js
import { describe, it, expect } from "vitest";
import { discount } from "./discount.js";

describe("discount", () => {
  it("10% chegirmani to'g'ri hisoblaydi", () => {
    expect(discount(100000, 10)).toBe(90000);
  });

  it("0% da narxni o'zgartirmaydi", () => {
    expect(discount(100000, 0)).toBe(100000);
  });

  it("noto'g'ri foizda xato beradi", () => {
    expect(() => discount(100, 150)).toThrow(RangeError);
  });
});
```

Ishga tushirish: `npm test`.

## AAA naqshi

Har test uch qismdan iborat bo'lsin:

```js
it("savatga mahsulot qo'shadi", () => {
  // Arrange — tayyorlash
  const cart = createCart();
  const product = { title: "Olma", price: 12000 };

  // Act — bajarish
  cart.add(product);

  // Assert — tekshirish
  expect(cart.count()).toBe(1);
  expect(cart.total()).toBe(12000);
});
```

Bu tuzilma testni o'qishni osonlashtiradi va bitta testda bir necha ishni
tekshirishdan saqlaydi.

## Asosiy matcherlar

| Matcher | Tekshiradi |
|---|---|
| `toBe(x)` | Aynan tenglik (`===`) — sonlar, matnlar |
| `toEqual(x)` | Tuzilma tengligi — obyekt va massivlar |
| `toBeCloseTo(x)` | Kasr sonlar (yaxlitlash xatosi bilan) |
| `toContain(x)` | Massiv yoki matn ichida bormi |
| `toThrow()` | Xato beradimi |
| `toHaveLength(n)` | Uzunlik |

<Callout type="warning" title="toBe va toEqual farqi">
Obyektlar uchun `toBe` ishlamaydi — u **bir xil obyektmi** deb so'raydi:

```js
expect({ a: 1 }).toBe({ a: 1 });      // ❌ yiqiladi
expect({ a: 1 }).toEqual({ a: 1 });   // ✅ o'tadi
```

Kasr sonlarda ham ehtiyot: `0.1 + 0.2 !== 0.3` —
[sonlar](https://learncode.uz/javascript/sonlar-math) darsidagi sabab. U yerda `toBeCloseTo`.
</Callout>

## Nimani sinash kerak

```js
function ageGroup(age) {
  if (age < 0) throw new RangeError("yosh manfiy bo'lolmaydi");
  if (age < 13) return "bola";
  if (age < 18) return "o'smir";
  if (age < 60) return "katta";
  return "keksa";
}

// Chegara qiymatlar — eng ko'p xato shu yerda
const cases = [
  [0, "bola"], [12, "bola"], [13, "o'smir"],
  [17, "o'smir"], [18, "katta"],
  [59, "katta"], [60, "keksa"],
];

for (const [age, expected] of cases) {
  const actual = ageGroup(age);
  console.log((actual === expected ? "✓" : "✗") + " " + age + " → " + actual);
}
```

Ustuvorlik tartibi:

1. **Chegara qiymatlar** — `0`, bo'sh massiv, `null`, chegaraning ikki tomoni;
2. **Xato holatlari** — noto'g'ri kirish;
3. **Asosiy holat** — odatiy ishlash;
4. **Tuzatilgan xatolar** — har buzilgan joyga test yoziladi, qaytmasligi uchun.

## Testga qulay kod

```js
// Sinash qiyin: tashqi holatga bog'liq va yon ta'siri bor
let total = 0;
function addBad(price) {
  total += price;
  console.log("Jami: " + total);
}

// Sinash oson: sof funksiya
function addGood(current, price) {
  return current + price;
}

console.log(addGood(0, 100));
console.log(addGood(100, 250));
console.log(addGood(0, 100));
```

[Sof funksiyalar](https://learncode.uz/javascript/funksional-dasturlash) testga eng qulay:
tayyorgarlik kerak emas, natija bashorat qilinadi.

Tashqi bog'liqlikni **argument sifatida** uzatish ham yordam beradi:

```js
// Qiyin: ichida vaqtga bog'lanadi
function isStaleHard(date) {
  return Date.now() - date > 86400000;
}

// Oson: vaqt tashqaridan beriladi
function isStaleEasy(date, now = Date.now()) {
  return now - date > 86400000;
}
```

## Qamrov haqida

> **100% maqsad emas**
>
> **Qamrov** (coverage) — testlar kodning necha foizini bajarishini ko'rsatadi.
> Foydali ko'rsatkich, lekin maqsad emas: 100% qamrov kod **to'g'ri** ekanini
> anglatmaydi, faqat **ishga tushganini** bildiradi.
>
> Muhimi — mantiqiy tarmoqlar va chegara holatlari sinalgani.

## Amaliy mashq

1. Sodda funksiya yozing va uni qo'lda tekshiring;
2. yuqoridagi `test`/`expect` yordamchisi bilan uni sinang;
3. chegara qiymatlar uchun test qo'shing;
4. xato beradigan holatni sinang;
5. `toBe` va `toEqual` farqini obyektda ko'ring;
6. tashqi holatga bog'liq funksiyani sof qilib qayta yozing.

## Qisqacha xulosa

- Test — kodni tekshiradigan kod; regressiyadan himoya qiladi.
- **Birlik testlari** ko'p va tez, E2E kam va sekin.
- **AAA**: Arrange → Act → Assert.
- Obyekt uchun `toEqual`, aniq qiymat uchun `toBe`, kasr uchun `toBeCloseTo`.
- Avval **chegara** va **xato** holatlarini sinang.
- Sof funksiya va tashqaridan uzatilgan bog'liqlik testni osonlashtiradi.

Keyingi darsda [tezlik va optimallashtirish](https://learncode.uz/javascript/optimallashtirish).
