IlmHamroh
JavaScript Full-stack/31-qism. Prodakshn backend va AI integratsiya34/34-dars29 daqiqa
Mundarija (35)

Test arxitekturasi va CI: parallel testlar, beqaror testlar, lokal check va minimal pipeline

Qisqacha: Testlar ko'paygan sari ikki savol muhim bo'ladi: ular tezmi va ularga ishonsa bo'ladimi. Kanon 340 ta testni Vitest'da parallel yurgizadi: sukut (16 ishchi) bilan 116 s, 4 ishchi bilan 151 s. 31-qism boshidagi birinchi to'liq tekshiruvda testlar qizil edi — lekin kod to'g'ri edi: to'rtta beqaror (flaky) test yuk ostida yiqilgan. Har birining sababi topildi va tuzatildi — tasodif emas. Tekshiruv bir buyruqda: npm run check (tip, lint, test, build). Xuddi shu qadamlar CI faylida — .github/workflows/ci.yml. Bu dars 31-qismni yakunlaydi: vazifalar-api v4.0.0 commit qilinadi.

Bu darsda

  • Backend testlarini darajalar va muhit bo'yicha tashkil qilasiz.
  • Vitest parallelligi qanday ishlashini va uning narxini kanon o'lchovida ko'rasiz.
  • Beqaror testning sababini topib, uni tasodif emas, sabab bilan tuzatasiz.
  • Lokal check va pre-push hook bilan xatoni push'dan oldin ushlaysiz.
  • Minimal CI faylini o'qiysiz va u nimani tekshirmasligini bilasiz.
  • 31-qismni yakunlaysiz: vazifalar-api v4.0.0 commit'i va nima qurilganining xaritasi.

Oldin bilishingiz kerak: GitHub Actions: birinchi CI workflow, Git hooklar: Husky va lint-staged, Test strategiyasi va flaky testlar, Testcontainers, Yuklama va stress testlari.

1. Nega bu kerak?

31-qism boshlanganda kanon 30-qism oxiridagi loyihani o'zgartirmasdan, yangi konteynerlarda to'liq tekshirdi. × belgili qatorlarga va oxiridagi Tests qatoriga qarang. Natija:

text
--- npm run test
 ↓ |asosiy| test/konteyner.test.ts (1 test | 1 skipped)
   × omborYarat(prisma): SAQLASH=prisma — fail-fast va ishlaydi 20735ms
   × deep link → /start → ulandi; /vazifalar; tugma → bajarildi; begona id — topilmadi; /yangi dialog (Redis) muddat bilan 1119ms
 FAIL  |asosiy| test/migratsiya.test.ts [ test/migratsiya.test.ts ]
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  |asosiy| test/prisma-ombor.test.ts > omborYarat(prisma): SAQLASH=prisma — fail-fast va ishlaydi
 FAIL  |asosiy| test/telegram-bot.test.ts > deep link → /start → ulandi; /vazifalar; tugma → bajarildi; begona id — topilmadi; /yangi dialog (Redis) muddat bilan
 Test Files  3 failed | 52 passed | 1 skipped (56)
      Tests  2 failed | 296 passed | 1 skipped (299)
exit 1 (99095 ms)

Kod o'zgarmagan, 30-qismda xuddi shu testlar yashil edi. Keyingi kunlardagi yugurishda (tuzatishlardan oldin) yana ikkita test yiqildi — lekin boshqa juftlik:

text
maxWorkers=sukut #1: 172823 ms |  Tests 2 failed | 298 passed | 1 skipped (301) | yiqilgan: test/auth.test.ts > POST /v1/auth/kirish > timing: noma'lum email ham argon2 bajaradi — ;test/prisma-ombor.test.ts > omborYarat(prisma): SAQLASH=prisma — fail-fast va ishlaydi;

Bir yugurishda Telegram testi yiqiladi, boshqasida auth testi, kod esa o'sha. Bu — beqaror test.

Beqaror test xatodan ham yomonroq. Xato bir marta tuzatiladi. Beqaror test esa jamoaga "qizil — bu shunchaki tasodif, qayta yurgizamiz" deb o'rgatadi. Bir oydan keyin haqiqiy xato ham "tasodif" deb o'tkazib yuboriladi. Test to'plamining qiymati — unga ishonch.

O'xshatish: tez-tez yolg'on signal beradigan yong'in signalizatsiyasi. Uch marta bekorga chalinsa, to'rtinchisida hech kim binodan chiqmaydi.

2. Testlarni tashkil qilish

2.1 Darajalar va muhit

Backend test strategiyasi darsida testlarni darajalarga bo'ldik: unit, integration, shartnoma, e2e. 31-qism oxirida vazifalar-api da 68 ta test fayli bor. Ular muhitga ko'ra ham bo'linadi — nima kerak bo'lsa, o'shanda ishlaydi:

flowchart TB
  A["npm run check"] --> T["tip + lint + build"]
  A --> V["Vitest: 68 fayl"]
  V --> U["bazasiz: unit, HTTP, shartnoma"]
  V --> D["Postgres + Redis + MinIO + Mailpit"]
  K["TESTCONTAINERS=1"] --> C["konteyner.test: o'z Postgres'i"]
  E["npm run e2e"] --> P["Playwright: haqiqiy brauzer"]

Diagrammaning chap tarmog'i — har kuni ishlaydigan npm run check. Uning ichida bazasiz testlar doim ishlaydi. Baza, Redis, MinIO yoki Mailpit kerak bo'lgan testlar — faqat ular mavjud bo'lsa. O'ng tomondagi ikkitasi alohida buyruq bilan: Testcontainers (Docker kerak, ~10 s qo'shimcha) va e2e (brauzer kerak).

Muhit yo'q bo'lsa, test yiqilmaydi, o'tkazib yuboriladi (SKIP) va sababini aytadi. Biz loyihani Docker'siz, hech qanday bazasiz to'liq yurgizib ko'rdik (Vitest 5.0.5, 4 ishchi):

text
 ↓ |asosiy| test/navbat.test.ts (1 test | 1 skipped)
 ↓ |asosiy| test/pg-ombor.test.ts (1 test | 1 skipped)
 ↓ |asosiy| test/saqlash.test.ts (1 test | 1 skipped)
 ↓ |redis-kanal| test/jonli.test.ts (1 test | 1 skipped)
 Test Files  44 passed | 24 skipped (68)
      Tests  139 passed | 38 skipped (177)

Chiqishning faqat bir qismi ko'rsatilgan. To'plam yashil — lekin 68 fayldan 24 tasi umuman ishlamadi. Muhim saboq: yashil natija "hamma narsa tekshirildi" degani emas. Har doim SKIP soniga qarang. Kanonning to'liq tekshiruvida (hamma xizmat bilan) faqat bitta fayl SKIP — konteyner.test, u alohida yuradi.

2.2 Izolyatsiya — parallellikning sharti

Testlar parallel ishlashi uchun ular bir-biriga tegmasligi kerak. Kanon buni uch joyda hal qilgan:

Umumiy resurs Muammo Kanon yechimi
Auth bazasi bir fayl TRUNCATE qilsa, boshqasining ma'lumoti yo'qoladi har fayl shablon bazadan o'z nusxasini oladi (CREATE DATABASE … TEMPLATE, ~0.3 s)
Redis navbatlari ikki test bir navbatdan ish oladi navbat prefiksi testga xos: t45-<pid>
Redis Pub/Sub kanali SSE testlari bir-birining hodisasini oladi alohida Vitest loyihasi redis-kanal, ketma-ket

Uchinchisi vitest.config.ts da:

ts
const KANAL_TESTLARI = ["test/jonli.test.ts", "test/outbox.test.ts"];

export default defineConfig({
  test: {
    env: { LOG_LEVEL: "silent" },
    testTimeout: 20_000,
    hookTimeout: 30_000,
    projects: [
      { extends: true, test: { name: "asosiy",
        include: ["test/**/*.test.ts", "src/**/*.spec.ts"],
        exclude: KANAL_TESTLARI } },
      { extends: true, test: { name: "redis-kanal",
        include: KANAL_TESTLARI, fileParallelism: false } },
    ],
  },
});

(Kanon faylidan; izohlar olib tashlangan, qatorlar bo'lingan.) Chiqishdagi |asosiy| va |redis-kanal| — shu loyihalar nomi. Ikkalasi bir yugurishda ishlaydi, lekin ikkinchisining fayllari bir-birini kutadi. testTimeout: 20_000 — Vitest sukuti 5 s; bazali testlar yuklangan mashinada 5–7 s olgani uchun oshirilgan.

3. Parallellik va tezlik

3.1 Kanon o'lchovi

Vitest test fayllarini alohida ishchi jarayonlarda (worker) parallel yurgizadi; fayl ichidagi testlar ketma-ket. Ishchilar soni — --maxWorkers. Vitest 5 hujjatiga ko'ra (2026-11), vitest run da sukut — mashinadagi mavjud parallellikning hammasi (os.availableParallelism()), kuzatuv (watch) rejimida — yarmi. Kanon mashinasida 16 mantiqiy yadro — demak sukut 16 ishchi (kanon chiqishi sonni yozmaydi, bu hujjat bo'yicha hisob). Kanon bir xil 340 testni uch xil parallellikda yurgizdi (test bazalari har yugurishdan oldin noldan quriladi; yorliqlar qisqartirilgan):

text
maxWorkers=sukut (yakuniy tekshiruv, npm run test): 116484 ms | Tests 339 passed | 1 skipped (340)
maxWorkers=8 #1: 122670 ms |  Tests 339 passed | 1 skipped (340) | yiqilgan:
maxWorkers=4 #1: 150596 ms |  Tests 339 passed | 1 skipped (340) | yiqilgan:

Grafikda — vaqt sukut parallellikka nisbatan:

340 test: yugurish vaqti (sukut = 100 %)
  • sukut (16 ishchi)100 %
  • 8 ishchi105 %
  • 4 ishchi129 %

Manba: IlmHamroh kanon o'lchovi, 2026-11-03: Vitest 5.0.5, Node 24.21.0, 340 test, Postgres 18.6 va Redis Docker'da; 116.5 / 122.7 / 150.6 s; har biri bitta yugurish, bitta mashina

3.2 Nega chiziqli emas

Ishchilar 16 dan 4 ga — to'rt baravar kamaydi. Vaqt esa atigi 29 % oshdi. 16 dan 8 ga — farq 5 %, shovqin chegarasida.

Sabab — testlar CPU'ni emas, asosan umumiy resurslarni kutadi: bitta Postgres, bitta Redis, Prisma klientini ko'tarish, argon2 hash. 16 ta ishchi bir Postgres'ga navbat bo'ladi. Bu Yuklama va stress testlari darsidagi to'yinish: ishchilar qo'shilgani bilan o'tkazuvchanlik o'smaydi, faqat kutish o'sadi. Yana bir sabab: yugurishning katta qismi modullarni yuklashga ketadi. Bizning lokal yugurishlarimizda Vitest buni o'zi ko'rsatdi: Duration 54.07s (import 85%, tests 12%, transform 3%) — vaqtning 85 % i import.

Ikki amaliy xulosa:

  • Lokal — sukut yaxshi. Bitta faylni tekshirish uchun esa butun to'plam emas: npx vitest run test/shartnoma.test.ts.
  • CI — GitHub'ning oddiy runner'ida 2–4 vCPU. 16 ishchi u yerda bo'lmaydi, beqaror testlar esa aynan yuklangan mashinada chiqdi. Kanon CI faylida --maxWorkers=2.

Tekshirib ko'ring: Testlar 4 ishchida 151 s, 16 ishchida 116 s. Jamoa "8 yadroli CI runner'ga pul to'lab, CI'ni 2 baravar tezlashtiramiz" deydi. O'lchov bu rejani qo'llab-quvvatlaydimi?

Javob

Yo'q. 4 dan 16 ishchiga — atigi 23 % tezlashuv (151 → 116 s), 8 ishchi esa 16 talikdan bor-yo'g'i 5 % sekin. Tor joy — yadrolar emas, umumiy Postgres va modullarni yuklash. Pul to'lashdan oldin profil kerak: qaysi fayllar eng uzoq yuradi, import vaqtini qisqartirib bo'ladimi, sekin fayllarni bo'lib bo'ladimi. Bu — Backend unumdorligini oshirish darsidagi "avval o'lcha" qoidasi, faqat testlar uchun.

4. Beqaror testlar: sabab bilan tuzatish

4.1 To'rt hodisa

Test strategiyasi va flaky testlar darsida beqarorlikning to'rt sababini ko'rgansiz: vaqt, tartib, "darhol va tugagach", kutish chegarasi. Kanon topgan to'rt hodisaning hammasi shularga tushadi:

Test Belgisi Sabab Tuzatish
telegram-bot dialog holati Redis'da yo'q javob holatdan oldin keladi kutib() — shart bilan kutish
prisma-ombor fail-fast 20 735 ms, timeout yolg'iz 10.7 s, yuk ostida 20 s dan oshdi o'z chegarasi 60 s
migratsiya (afterAll) hook 30 s timeout DROP DATABASE checkpoint kutadi hook chegarasi 60 s
auth timing 479 va 506 ms farqi > 20 ms mutlaq chegara yuk ostida nisbiy: max(20 ms, 15 %)

Birinchisini Asinxron komponentlarni testlash darsida ko'rdik. Qolgan uchtasi — kutish chegarasi turi. Lekin ularni tuzatishda bitta xavf bor.

4.2 Chegarani oshirish — qachon to'g'ri

"Timeout'ni oshirish" — beqaror testning eng mashhur va eng xavfli "davosi". Haqiqiy osilish (cheksiz kutayotgan Promise) ham timeout bilan ko'rinadi — chegarani 10 baravar oshirsangiz, uni shunchaki kechroq ko'rasiz.

Kanondagi uchta tuzatish boshqacha: har birida chegara o'lchovdan kelib chiqqan. Prisma testi:

ts
// yolg'iz 10.7 s (Prisma klienti ikki marta ko'tariladi),
// parallel yuk ostida 20 s dan oshdi — o'z chegarasi 60 s
test("omborYarat(prisma): SAQLASH=prisma — fail-fast va ishlaydi",
  { timeout: 60_000 }, async () => {

Kanon izohidagi o'lchovga ko'ra yolg'iz 10.7 s — test haqiqatan sekin (Prisma klientini ikki marta ko'taradi). Global chegara (20 s) o'zgarmadi: faqat shu bitta test uchun, o'lchangan vaqtning ~6 baravari. Boshqa 339 test haqiqiy osilishni hamon 20 s da ushlaydi.

4.3 Mutlaq va nisbiy chegara

To'rtinchisi qiziqroq. Login oqimi testlaridan biri vaqt hujumini (timing attack) tekshiradi. Noma'lum email uchun ham server argon2 hash'ni hisoblashi kerak. Aks holda javob vaqtidan "bunday foydalanuvchi bormi" ni bilib olish mumkin. Test ikkala holatni 9 martadan o'lchaydi va medianalarni solishtiradi:

diff
-assert.ok(Math.abs(bor - yoq) < 20, xabar);
+assert.ok(
+  Math.abs(bor - yoq) < Math.max(20, 0.15 * Math.max(bor, yoq)),
+  xabar);

(Kanon diff'idan, telefon uchun bo'lingan; xabar o'rnida kodda `bor … ms, yo'q … ms` satri turadi.) Kanon izohiga ko'ra bo'sh mashinada har kirish ~50 ms, farq bir necha ms — 20 ms chegara yetarli. Yuklangan mashinada har kirish ~500 ms ga cho'zildi (izohdagi o'lchov: 479 va 506 ms) va shovqin ham shunga yarasha o'sdi: 27 ms farq — "hujum" emas, shovqin. Nisbiy chegara (15 %) shovqin bilan birga kattalashadi.

Muhimi — test o'z vazifasini yo'qotmadi. Haqiqiy zaiflikda farq juda katta: argon2'siz noma'lum email ~2 ms, argon2 bilan ~50 ms va undan ko'p. Testdagi ikkinchi tekshiruv ham qoldi: yoq > 15 ms — noma'lum email ham hash qilinishi shart. Nisbiy chegara shovqinni o'tkazadi, zaiflikni esa baribir ushlaydi.

Diqqat: "Kanon tekshiruvidagi har qizil = beqaror test" ham noto'g'ri xulosa. Kanon parallellik o'lchovida bir nechta yugurish 22–33 tadan yiqildi. Sabab — orqa fonda to'xtamagan eski jarayonlar test bazalarini shu paytda qayta qurayotgan edi ("relation does not exist"). Bu test xatosi emas, o'lchov xatosi. Bunday yugurishlar natijalardan olib tashlangan va sababi yozib qo'yilgan. Qizil natijada birinchi savol: "sharoit to'g'rimidi?"

4.4 Beqarorlikni topish

Beqaror testni bitta yugurish ko'rsatmaydi. Hisoblab ko'ramiz: test har yugurishda 2 % ehtimol bilan yiqilsa, uni ko'rish uchun necha marta yurgizish kerak?

js
// Bitta test har yugurishda p ehtimol bilan yiqiladi
function korishEhtimoli(p, marta) {
  return 1 - (1 - p) ** marta;
}

function kerakliMarta(p, ishonch = 0.95) {
  return Math.ceil(Math.log(1 - ishonch) / Math.log(1 - p));
}

for (const p of [0.2, 0.02, 0.005]) {
  console.log(`p = ${p * 100} %: 1 yugurishda ` +
    `${(korishEhtimoli(p, 1) * 100).toFixed(1)} %, ` +
    `95 % ishonch uchun ${kerakliMarta(p)} yugurish`);
}

// To'plamda 4 ta shunday test (har biri 2 %) bo'lsa:
const qizil = 1 - (1 - 0.02) ** 4;
console.log(`to'plam qizil: ${(qizil * 100).toFixed(1)} %`);

Konsolda:

text
p = 20 %: 1 yugurishda 20.0 %, 95 % ishonch uchun 14 yugurish
p = 2 %: 1 yugurishda 2.0 %, 95 % ishonch uchun 149 yugurish
p = 0.5 %: 1 yugurishda 0.5 %, 95 % ishonch uchun 598 yugurish
to'plam qizil: 7.8 %

Ikki xulosa. Birinchisi: kam uchraydigan beqarorlikni takrorlab topish qimmat — 2 % li test uchun ~150 yugurish. Shuning uchun sabab qidiriladi, statistika emas: "nima vaqtga, tartibga yoki umumiy holatga bog'liq?". Ikkinchisi: har biri "kam uchraydigan" to'rtta test birga har 13 yugurishdan birini qizil qiladi. Jamoa uchun bu — "CI tez-tez yolg'on qizil".

Amaliy vositalar:

Vosita Nima qiladi
Ko'p marta yurgizish yuk ostida (parallel) — beqarorlik aynan shu yerda chiqadi
--sequence.shuffle testlarni tasodifiy tartibda — yashirin bog'liqlikni topadi
--sequence.seed=N o'sha tasodifiy tartibni takrorlaydi
--retry=N yiqilganini qayta yurgizadi — topish uchun, tuzatish uchun emas
Karantin sababi topilguncha alohida ro'yxatda, lekin kuzatuvda

Kanon package.json ga skript qo'shdi: "test:aralash": "vitest run --sequence.shuffle". Aralash tartib urug' (seed) bilan bo'ladi va Vitest uni chiqishda yozadi. Biz kichik to'plamda sinadik:

text
 RUN  v5.0.5 C:/Users/…/vazifalar-api
      Running tests with seed "31"
 ✓ |asosiy| test/config.test.ts > config: REDIS_URL (ixtiyoriy, faqat redis://), KESH_TTL (sukut 60, bo'sh — xato) 16ms
 ✓ |asosiy| test/asinxron.test.ts > retry backoff haqiqiy setTimeout bilan — soxta soat oldinga suriladi (test 0 ms kutadi); Retry-After 5 s hurmat qilinadi 11ms
 ✓ |asosiy| test/asinxron.test.ts > bayroqlar keshi: 30 s ichida bazaga qayta bormaydi, 30 s dan keyin — boradi; tozala() — darhol 2ms

(Buyruq: npx vitest run --sequence.shuffle --sequence.seed=31 … uchta fayl bilan; chiqish qisqartirilgan, yo'l qisqartirilgan.) asinxron.test.ts dagi retry testi fayldagi tartibda uchinchi, bu yerda — birinchi. Aralash tartibda biror test yiqilsa, chiqishdagi urug'ni --sequence.seed ga berib, xuddi o'sha tartibni qayta hosil qilasiz.

5. Lokal check va pre-push hook

5.1 npm run check

Kanonning yagona tekshiruv buyrug'i:

json
"check": "npm run tip && npm run lint && npm test && npm run build"

To'rt qadam, && bilan: birinchisi yiqilsa, qolganlari ishlamaydi. Nega build ham? TESTLAR.md dagi qoida: Vitest kodni o'z transpilyatori (oxc) bilan o'giradi, nest build esa tsc bilan. Ular dekorator metama'lumotini va aylanma importlarni har xil qayta ishlaydi — Vitest'da o'tgan kod build'da yiqilishi mumkin. Prodakshnga build boradi, demak build ham tekshiriladi.

Kanonning yakuniy tekshiruvidagi vaqtlar (bitta yugurish): tip 7.4 s, lint 16.9 s, test 116.5 s, build 30.1 s. Testlar — vaqtning ~70 %.

5.2 Pre-push hook

Git hooklar darsida Husky bilan pre-commit hook qo'ygansiz. Butun check har commit'da — juda sekin (~3 daqiqa). Uning to'g'ri joyi — pre-push: commit'lar erkin, lekin serverga yuborishdan oldin to'liq tekshiruv. Kanonda hook yo'q; mana uning eng oddiy ko'rinishi va natijasi. Biz bo'sh repoda sinadik: check ataylab yiqiladigan qilib yozilgan (Git 2.54, Node 24.21.0):

bash
#!/bin/sh
# .git/hooks/pre-push
echo "pre-push: npm run check"
npm run check --silent || {
  echo "check yiqildi - push bekor qilindi"
  exit 1
}
text
pre-push: npm run check
test: 1 yiqildi
check yiqildi - push bekor qilindi
error: failed to push some refs to '../r.git'

Hook nol bo'lmagan kod bilan chiqdi — Git push'ni bekor qildi va kod serverga ketmadi. Husky bilan xuddi shu buyruqlar .husky/pre-push fayliga yoziladi va repo orqali butun jamoaga tarqaladi.

Hook — yordamchi, qo'riqchi emas. git push --no-verify uni chetlab o'tadi, yangi klonlangan repoda esa (Husky'siz) u umuman yo'q. Majburiy tekshiruv joyi — CI.

6. Minimal CI

6.1 Fayl

GitHub Actions darsida birinchi workflow yozgansiz: checkout, Node, npm ci, npm run check. Backend uchun bir narsa yetishmaydi — xizmatlar: Postgres va Redis. GitHub Actions ularni services bilan konteyner sifatida ko'taradi.

Kanon fayli (izohlar qisqartirilgan, uzun options va run qatorlari YAML ichida bo'lingan — qiymatlari aynan; «Test bazalari» qadamida takrorlanuvchi manzil PG o'zgaruvchisiga olingan — buyruqlar bir xil, biz bash da solishtirib tekshirdik):

yaml
# .github/workflows/ci.yml — minimal CI: lokal `npm run check` bilan
# bir xil qadamlar, haqiqiy Postgres (pgvector) va Redis bilan.
# Push'da emas — qo'lda (workflow_dispatch); PR uchun `pull_request`
# ni yoqish mumkin. Sirlar yo'q: testlar tashqi API'ga bormaydi.
name: check
on:
  workflow_dispatch:
  # pull_request:
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
jobs:
  check:
    runs-on: ubuntu-24.04
    timeout-minutes: 20
    services:
      postgres:
        image: pgvector/pgvector:pg18
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 5s --health-timeout 5s
          --health-retries 20
      redis:
        image: redis:8
        ports: ["6379:6379"]
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s --health-timeout 5s
          --health-retries 20
    env:
      TEST_DATABASE_URL: postgres://postgres@127.0.0.1:5432/vazifalar_test
      TEST_PRISMA_DATABASE_URL: postgres://postgres@127.0.0.1:5432/vazifalar_test_prisma
      TEST_AUTH_DATABASE_URL: postgres://postgres@127.0.0.1:5432/vazifalar_test_auth
      TEST_NAMUNA_DATABASE_URL: postgres://postgres@127.0.0.1:5432/vazifalar
      TEST_REDIS_URL: redis://127.0.0.1:6379
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      # test bazalari — Prisma migratsiyalari; namuna — seed bilan
      - name: Test bazalari
        run: |
          PG=postgres://postgres@127.0.0.1:5432
          for b in vazifalar_test vazifalar_test_prisma \
                   vazifalar_test_auth vazifalar; do
            psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE $b"
            DATABASE_URL=$PG/$b npx prisma migrate deploy
          done
          DATABASE_URL=$PG/vazifalar npm run db:seed
      # parallellik cheklangan: DB'ga tayangan testlar yuklangan
      # mashinada beqaror bo'ldi (2 vCPU runner)
      - run: npm run tip
      - run: npm run lint
      - run: npx vitest run --maxWorkers=2
      - run: npm run build
      - name: Migratsiyalar ↔ schema.prisma (drift bo'lmasin)
        run: >-
          DATABASE_URL=$TEST_DATABASE_URL
          npx prisma migrate diff --from-config-datasource
          --to-schema prisma/schema.prisma --exit-code
      - name: Bog'liqliklar auditi
        run: npm audit --omit=dev --audit-level=high

env dagi to'rtta ulanish satri 70 belgidan uzun — ular bo'linmadi, chunki URL'ni bo'lib bo'lmaydi. Oxiridan bitta qadamda kanon to'liq manzil yozgan; biz uning o'rniga job env idagi aynan o'sha qiymatli $TEST_DATABASE_URL ni qo'ydik. Action versiyalari — GitHub Actions darsidagidek @v7. O'z loyihangizda shu action'larning eng so'nggi asosiy versiyasini qo'ying.

6.2 Fayl bo'ylab

Qism Nima qiladi
on: workflow_dispatch faqat qo'lda ishga tushadi (Actions sahifasidagi tugma yoki gh workflow run)
concurrency bir tarmoqda yangi yugurish boshlansa, eskisi bekor bo'ladi
services + options Postgres va Redis konteynerlari; --health-* — ular tayyor bo'lguncha qadamlar kutadi
env testlar shu manzillardan bazani topadi — lokaldagi bilan bir xil o'zgaruvchilar
Test bazalari to'rtta baza, migratsiyalar, namuna ma'lumoti (seed)
tip, lint, vitest, build npm run check ning o'zi, faqat --maxWorkers=2 bilan
drift schema.prisma va migratsiyalar farq qilsa — --exit-code qadamni yiqitadi
audit prod bog'liqliklarida high darajali zaiflik bo'lsa — yiqiladi

Postgres image — pgvector/pgvector:pg18, oddiy postgres:18 emas: Vektor bazalar va pgvector darsidagi migratsiya CREATE EXTENSION vector qiladi, oddiy image'da bu kengaytma yo'q.

Push'da emas, qo'lda — nega? GitHub Actions'ning bepul daqiqalari cheklangan, kursda tekshiruv lokal npm run check bilan qilinadi. PR'larda avtomatik ishlashi uchun # pull_request: izohini oching. CI narxi va byudjet — 34-qismda.

6.3 Halol: bu fayl nimani bilmaydi

Kanon bu faylni ishga tushirmagan — u faqat fayl. Shuning uchun uning ba'zi joylari tekshirilmagan:

  • Seed va migratsiyadan qurilgan baza — sinalmagan. Kanon test bazalarini o'z skripti bilan sql/ fayllaridan quradi; CI'da esa Prisma migratsiyalari ishlatiladi. Ular bir xil sxema berishi tekshirilgan (farq 0 qator), lekin CI'dagi butun yo'l — yo'q.
  • MinIO va Mailpit yo'q — fayl, email, navbat testlari CI'da SKIP bo'ladi. «Darajalar va muhit» bo'limidagi saboq: yashil CI ≠ hammasi tekshirildi.
  • Testcontainers va e2e — CI'da yo'q. Kanon ularni alohida yurgizadi; e2e (Playwright) bu qismda umuman yurgizilmadi — mashinada brauzerlar o'rnatilmagan, 31-qism frontendni o'zgartirmaydi.
  • Audit qadami bugun qizil bo'lardi. Buni keyingi bo'limda ko'ramiz.

6.4 npm audit: haqiqiy natija

Kanon prod bog'liqliklarini tekshirdi (npm audit --omit=dev). Har bo'limda uch qatorga qarang: paket va versiya diapazoni, Severity (jiddiylik) va fix available ostidagi ogohlantirish:

text
# npm audit report

minimatch  <11.0.1
Severity: moderate
minimatch has potential denial of service via globparts amplification - https://github.com/advisories/GHSA-g3hv-75wg-m249
fix available via `npm audit fix --force`
Will install archiver@4.0.2, which is a breaking change
node_modules/minimatch
  readdir-glob  *
  Depends on vulnerable versions of minimatch
  node_modules/readdir-glob
    archiver  >=5.0.0
    Depends on vulnerable versions of readdir-glob
    node_modules/archiver

multer  2.0.0 - 2.4.0
Severity: high
multer vulnerable to Denial of Service via a pending asynchronous fileFilter after request teardown - https://github.com/advisories/GHSA-q7qp-rjg7-q26m
fix available via `npm audit fix --force`
Will install @nestjs/platform-express@12.3.2, which is outside the stated dependency range
node_modules/multer
  @nestjs/platform-express  10.4.18 - 10.4.22 || 11.1.2 - 12.2.0
  Depends on vulnerable versions of multer
  node_modules/@nestjs/platform-express

5 vulnerabilities (3 moderate, 2 high)

To address all issues (including breaking changes), run:
  npm audit fix --force

Besh zaiflik, ikkitasi high. CI'dagi --audit-level=high bilan audit qadami yiqiladi. Ikkalasi ham 31-qism qo'shgan paket emas — 30-qismdan qolgan tranzitiv bog'liqliklar:

  • multer — @nestjs/platform-express ichida. Zaiflik fayl yuklashdagi fileFilter da. Kanon fayllarni busboy bilan qabul qiladi, Nest'ning FileInterceptor i ishlatilmaydi (Fayl yuklash darsi qarori). Ya'ni zaif kod yo'li loyihada chaqirilmaydi — lekin paket o'rnatilgan.
  • minimatch — archiver → readdir-glob zanjirida, eksport arxivi uchun.

npm audit fix --force ikkalasini "tuzatadi", lekin chiqishni o'qing: archiver ni 8.0.0 dan 4.0.2 ga tushiradi ("breaking change"), @nestjs/platform-express ni esa loyihadagi boshqa @nestjs/* paketlari (12.2.0) diapazonidan tashqariga chiqaradi. Semver darsidagi qoida: major o'zgarish — buzuvchi. Kanon --force qilmadi.

To'g'ri yo'llar: tuzatilgan patch versiya chiqishini kutish (masalan, butun @nestjs/* oilasini birga yangilash), package.json dagi overrides bilan tranzitiv paketni qotirish (sinalmagan) yoki vaqtincha istisno — sababi va muddati yozib qo'yilgan holda. Qaysi yo'l bo'lmasin, audit natijasini o'qish kerak: "high" har doim ham "bizda ekspluatatsiya qilinadi" degani emas, lekin buni har safar isbotlash kerak.

Tekshirib ko'ring: Hamkasbingiz CI qizil bo'lmasin deb audit qadamini o'chirib qo'ymoqchi. Yaxshiroq yechim bormi?

Javob

Bor. Qadamni o'chirish — signalni o'chirish: ertaga haqiqatan xavfli zaiflik ham ko'rinmaydi. Yaxshiroq: zaiflikni tahlil qilib (zaif yo'l loyihada chaqiriladimi), qarorni hujjatga yozish (docs/ da, sana va muddat bilan) va shu bitta maslahatni (advisory) istisno qilish yoki tuzatilgan versiyaga o'tish. Audit qadami esa boshqa zaifliklar uchun ishlayveradi.

7. Ko'p uchraydigan xatolar

Xato Oqibati To'g'risi
Beqaror testni "qayta yurgizamiz" deb qoldirish ishonch yo'qoladi, haqiqiy xato ham o'tib ketadi sabab — vaqt, tartib, holat; tuzatish yoki karantin
Hamma timeout'ni oshirish haqiqiy osilish kech ko'rinadi o'lchangan, faqat sekin test uchun chegara
Mutlaq vaqt chegarasi yuklangan muhitda CI'da yolg'on qizil nisbiy chegara yoki taqqoslash
Parallel testlar umumiy bazada bir-birini buzadi har fayl — o'z bazasi, prefiksi
Yashil natijada SKIP soniga qaramaslik yarim to'plam ishlamagan SKIP sonini kuzatish
CI'da build siz Vitest o'tadi, tsc yiqiladi check ning hamma qadami
Audit qizil — qadamni o'chirish signal yo'qoladi tahlil, hujjatlangan istisno
npm audit fix --force ni o'qimasdan major versiya o'zgaradi chiqishni o'qish, alohida PR

8. Mashqlar

Mashqlar kurs/mashqlar/31/34-ci/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).

1-mashq (oson): bo'shliqlarni to'ldiring

  • Vitest ishchilar sonini belgilovchi bayroq — --max
  • Testlarni tasodifiy tartibda yurgizish — --sequence.
  • Kanon CI faylidagi Postgres image — pgvector/pgvector:pg
  • check skriptidagi to'rtinchi qadam — npm run
  • Push'dan oldin ishlaydigan Git hook —
Yechim
  • Workers — --maxWorkers=2 (CI faylida).
  • shuffle — urug'ni --sequence.seed takrorlaydi.
  • 18 — CREATE EXTENSION vector uchun pgvector image.
  • build — Vitest (oxc) va tsc farqi uchun.
  • pre-push — --no-verify bilan chetlab o'tiladi, shuning uchun CI ham kerak.

2-mashq (o'rta): beqaror testlarni ajratish

Bir xil kod bilan to'plam besh marta yurgizildi. Har test uchun natijalar massivi berilgan (true — o'tdi). ajrat(natijalar) funksiyasini yozing: testlarni uch guruhga bo'lsin — barqaror (hammasi o'tdi), buzilgan (hammasi yiqildi), beqaror (aralash, o'tish foizi bilan). Ishora: every, filter (filter), Object.entries.

Yechim
js
function ajrat(natijalar) {
  const g = { barqaror: [], buzilgan: [], beqaror: [] };
  for (const [nom, r] of Object.entries(natijalar)) {
    if (r.every(Boolean)) g.barqaror.push(nom);
    else if (r.every((x) => !x)) g.buzilgan.push(nom);
    else {
      const otdi = r.filter(Boolean).length;
      const foiz = Math.round((otdi / r.length) * 100);
      g.beqaror.push(`${nom} (${foiz} % o'tdi)`);
    }
  }
  return g;
}

console.log(ajrat({
  "asinxron: retry": [true, true, true, true, true],
  "telegram: dialog": [true, false, true, true, false],
  "auth: timing": [true, true, true, false, true],
  "shartnoma: snapshot": [false, false, false, false, false],
}));

Konsolda:

text
{
  barqaror: [ 'asinxron: retry' ],
  buzilgan: [ 'shartnoma: snapshot' ],
  beqaror: [ "telegram: dialog (60 % o'tdi)", "auth: timing (80 % o'tdi)" ]
}

Ma'lumot namuna uchun o'ylab topilgan. buzilgan guruhi beqaror emas — u har safar yiqiladi va oddiy xato kabi tuzatiladi (ehtimol surat eskirgan). Beqarorlar esa sabab qidirishni talab qiladi. beqaror qatoridagi satrlar qo'shtirnoqda — ichida apostrof bor.

3-mashq (qiyin): fayllarni ishchilarga taqsimlash

CI'da testlarni bir nechta mashinaga (shard) bo'lish mumkin. Har fayl yugurish vaqti ma'lum bo'lsin. Eng yaxshi oddiy usul: fayllarni eng uzunidan boshlab olib, har birini hozir eng bo'sh ishchiga berish. taqsimla(fayllar, n) funksiyasini yozing. U har ishchining fayllari va jami vaqtini qaytarsin. Oxirida eng band ishchining vaqtini chiqaring — butun yugurish shuncha davom etadi. Namuna fayllar va 2, 3 ishchi bilan sinang. Ishora: toSorted (toSorted), reduce.

Yechim
js
function taqsimla(fayllar, n) {
  const ishchilar = Array.from({ length: n },
    () => ({ fayllar: [], s: 0 }));
  for (const f of fayllar.toSorted((a, b) => b.s - a.s)) {
    const bosh = ishchilar.reduce((a, b) => (b.s < a.s ? b : a));
    bosh.fayllar.push(f.nom);
    bosh.s += f.s;
  }
  return ishchilar;
}

const fayllar = [
  { nom: "navbat", s: 14 }, { nom: "auth", s: 12 },
  { nom: "prisma-ombor", s: 11 }, { nom: "telegram-bot", s: 6 },
  { nom: "shartnoma", s: 5 }, { nom: "api", s: 4 },
  { nom: "asinxron", s: 1 },
];
for (const n of [2, 3]) {
  const r = taqsimla(fayllar, n);
  for (const [i, x] of r.entries()) {
    console.log(`${n} ishchi, #${i + 1}: ${x.s} s —`,
      x.fayllar.join(", "));
  }
  console.log(`  yugurish: ${Math.max(...r.map((x) => x.s))} s`);
}

Konsolda:

text
2 ishchi, #1: 26 s — navbat, telegram-bot, shartnoma, asinxron
2 ishchi, #2: 27 s — auth, prisma-ombor, api
  yugurish: 27 s
3 ishchi, #1: 18 s — navbat, api
3 ishchi, #2: 18 s — auth, shartnoma, asinxron
3 ishchi, #3: 17 s — prisma-ombor, telegram-bot
  yugurish: 18 s

Vaqtlar namuna uchun o'ylab topilgan. Jami 53 s: ideal holatda 2 ishchi 26.5 s, 3 ishchi 17.7 s ga tushardi — taqsimot idealga juda yaqin (27 va 18 s). Lekin ishchilar soni qancha bo'lmasin, yugurish 14 soniyalik navbat dan qisqa bo'lmaydi: eng uzun fayl — pastki chegara. Kanon o'lchovidagi "ishchilar ko'p — tezlik oz" ning yana bir sababi shu.

9. Loyiha qadami: yakuniy tekshiruv va commit

9.1 Bu darsdagi o'zgarishlar

Loyihaning yakuniy bosqichida qolgan beshta o'zgarish:

  1. test/konteyner.test.ts — Testcontainers testi 31-qism sxemasiga moslandi:
diff
-    const k = await new PostgreSqlContainer("postgres:18")
+    const k = await new PostgreSqlContainer("pgvector/pgvector:pg18")
diff
-  });
+  }, 180_000);
diff
-      assert.deepEqual(s, { trg: 5, mv: 1, rls: 13 });
+      assert.deepEqual(s, { trg: 8, mv: 1, rls: 16 });

Birinchisi — pgvector kengaytmasi uchun. Ikkinchisi — beforeAll (konteyner ko'tarish + migratsiyalar) chegarasi 30 s dan 180 s ga: kanon izohiga ko'ra yuklangan mashinada migratsiyalar 30 s hook chegarasidan oshib ketgan. Bu ham «Chegarani oshirish — qachon to'g'ri» bo'limidagi holat: chegara faqat shu bitta qimmat hook uchun. Uchinchisi — sxemadagi yangi obyektlar: +3 trigger (vazifa hodisalari, audit jurnalini o'zgartirishni taqiqlovchi ikkita) va +3 RLS jadvali (vazifa hodisalari, embeddinglar, AI sarfi). Testcontainers testi sxemaning "shaklini" sanab tekshiradi — yangi jadval qo'shib, RLS'ni unutsangiz, shu yerda yiqiladi.

  1. package.json — versiya va yangi skript:
diff
-  "version": "4.0.0-alfa.4",
+  "version": "4.0.0",
json
"test:aralash": "vitest run --sequence.shuffle"
  1. .github/workflows/ci.yml — «Minimal CI» bo'limidagi fayl.
  2. TESTLAR.md — "31-qism (prodakshn va AI) testlari" bo'limi: har yangi test fayli bir qatorda (statistika, saqlash, ai, ai-qidiruv, ai-nazorat, mcp, prodakshn, kuzatuv, chidamlilik, cheklov-redis, asinxron, shartnoma), test:aralash va CI haqida eslatma.
  3. README.md — "Prodakshn va AI (31-qism, v4)" bo'limi: AI xususiyatlari, MCP server, ma'lumotlar (hodisalar, saqlash muddati, audit), kuzatuv (/health/*, /metrics).

Oldingi uch darsdagi test/asinxron.test.ts va test/shartnoma.test.ts (surati bilan) ham shu commit'ga kiradi.

9.2 Yakuniy tekshiruv

bash
npm run check
TESTCONTAINERS=1 npx vitest run test/konteyner.test.ts

Kanon yakuniy tekshiruvi (Docker'dagi PostgreSQL 18.6 + pgvector, Redis, MinIO, Mailpit; haqiqiy vazifalar_api, vazifalar_auth, vazifalar_ishchi rollari):

text
--- npm run tip
exit 0 (7411 ms)
--- npm run lint
exit 0 (16927 ms)
--- npm run build
exit 0 (30148 ms)
--- npm run test
 ↓ |asosiy| test/konteyner.test.ts (1 test | 1 skipped)
 Test Files  67 passed | 1 skipped (68)
      Tests  339 passed | 1 skipped (340)
exit 0 (116484 ms)
--- Testcontainers (TESTCONTAINERS=1):  [Testcontainers vaqtlari] konteyner 2064 ms, migratsiya 3638 ms, snapshot 385 ms, restore (oxirgi) 192 ms  Tests 10 passed (10) ; tc qoldiqlari: 0
--- prisma migrate deploy (yangi baza): 26 migrations found in prisma/migrations The following migration(s) have been applied: All migrations have been successfully applied. 
--- schema.prisma ↔ migratsiyalar (migrate diff): []
--- migratsiyalar va sql/ sxemasi farqi (pg_dump, _prisma_migrations'siz): 0 qator

(Kanon faylidan; uchta yugurishning sarlavha qatorlari olib tashlangan, yorliqlar qisqartirilgan. tc qoldiqlari: 0 — testdan keyin o'chirilmay qolgan Testcontainers konteynerlari yo'q.) Nima bo'ldi? Har qadam exit 0 bilan tugadi, 340 testdan faqat alohida yuradigan bittasi SKIP, u ham o'z yugurishida 10/10. Migratsiyalar uch xil tekshiruvda bir xil sxema berdi.

Tekshiruv Natija
tip / lint / build 0 / 0 / 0
Vitest (68 fayl) 339 o'tdi, 1 SKIP (konteyner — alohida 10/10)
Testcontainers 10/10, migratsiya 3.6 s
Migratsiyalar 26 ta, schema.prisma bilan farq yo'q, sql/ bilan farq 0 qator
Jonli (uch saqlash rejimi, haqiqiy rollar) kutilgan status kodlari: 200, 201, 204, 401, 404, 409, 412 va boshqalar

Docker'siz bo'lsangiz, «Darajalar va muhit» bo'limidagi natijani ko'rasiz: 44 fayl o'tadi, 24 tasi SKIP. Bu ham to'g'ri natija — faqat qaysi qismi tekshirilmaganini bilasiz.

9.3 Commit

bash
git add -A
M="test: asinxron (soxta soat), OpenAPI snapshot va iste'molchi"
M+=" shartnomasi, Testcontainers pgvector; ci: workflow fayli"
M+=" (qo'lda); chore: v4.0.0"
git commit -m "$M"

git status ga qarang: .env, dist/, coverage/ kirmasin. docs/openapi-snapshot.json esa kirsin — u shartnomaning bir qismi.

Ixtiyoriy: versiyaga teg qo'ying (Tag va relizlar) — git tag v4.0.0. 31-qism shu bilan tugaydi.

10. 31-qism yakuni: nima qurildi

31-qism boshida vazifalar-api NestJS 12 da ishlaydigan, 25–29-qismlarning hamma imkoniyatiga ega API edi. Endi u v4.0.0:

Yo'nalish Loyihada Tajriba (loyihaga kirmadi)
Ma'lumotlar bazasi oyma-oy bo'limli vazifa hodisalari, BRIN, statistika; maxfiylik hujjati (docs/MAXFIYLIK.md), saqlash muddati, hisobni o'chirish va anonimlashtirish SQLite, MySQL, replika va failover, PITR, sharding, TLS va shifrlash, CDC
AI vazifani qadamlarga bo'lish (JSON va SSE), semantik va gibrid qidiruv (pgvector), RAG yordamchi, zaxira model, kvota va kesh, MCP server xom LLM so'rovlari, AI SDK, nutq va rasm
Prodakshn naqshlari audit jurnali (hash zanjiri), feature flag'lar, graceful drain, /health/startup, /metrics, retry / circuit breaker / bulkhead (umumiy breaker — ochiq xato, Resilience naqshlari), Redis cheklagichi soft delete extension, multi-tenancy, OTel eksport
Test soxta soat testlari, OpenAPI surati va iste'molchi shartnomasi, beqaror testlar tuzatildi, CI fayli yuklama o'lchovi, parallellik o'lchovi

Raqamlarda (kanon tekshiruvlari, 31-qism boshi → oxiri):

Boshida Oxirida
Vitest 296 o'tdi, 2 yiqildi (beqaror) 339 o'tdi, 0 yiqildi
Migratsiyalar 20 26
Prod bog'liqliklar 41 43 (@modelcontextprotocol/sdk, prom-client)

Ikki yangi paket: @modelcontextprotocol/sdk — MCP server uchun, prom-client — metrikalar uchun. AI qatlami, flag'lar, circuit breaker va cheklagich — o'z kodimiz, paketsiz; har tanlov o'lchov bilan asoslandi.

Bu qismdagi eng qimmatli material — kanon topgan haqiqiy xatolar. 30-qismdan beri ishlamagan eksport buyrug'i. Prisma bo'limli jadvalni "o'chirmoqchi" bo'lgan migratsiya farqi. pgvector filtrli qidiruvda 0 natija. temperature: 0 da ham har xil javob beruvchi LLM. Va bu darsdagi to'rtta beqaror test. Ularning hech biri kitobda yozilmagan edi — hammasi o'lchov va tekshiruv bilan topildi.

Keyingi qism

Kod tayyor, testlar ishonchli — lekin bitta odam yozgan. 32-qism — Professional Git va jamoada ishlash. U Bisect: xatoni kiritgan commitni topish bilan boshlanadi: 339 ta test borligida regressiyani qaysi commit kiritganini git bisect run npm test ikkiga bo'lib-bo'lib bir necha qadamda topadi. Keyin — reliz avtomatikasi va CHANGELOG, jamoa workflow'lari, commit imzolash, yaxshi PR va code review. Bu darsdagi CI fayli va check shu jamoaviy ishning poydevori bo'ladi.

11. Real ishda

  • Qayerda uchraydi: har jamoada "CI qizil — kim buzdi?" savoli. Katta loyihalarda beqaror testlar alohida kuzatiladi (karantin, beqarorlik hisoboti), CI vaqti esa jamoa unumdorligining o'lchovi.
  • Vositalar: GitHub Actions (services, concurrency, matrix), Vitest --shard, --sequence.shuffle, --retry; Husky va lefthook; Dependabot va Renovate (bog'liqliklarni yangilash), npm audit va OSV-Scanner.
  • Intervyu savollari: "Beqaror testni qanday topasiz va tuzatasiz?", "Testlarni parallel ishlatganda nimaga e'tibor berasiz?", "CI pipeline'ingizda qanday qadamlar bor va nega shu tartibda?", "npm audit qizil — nima qilasiz?"

Xulosa

  • Testlar muhitga ko'ra tashkil qilinadi: bazasizlari doim, xizmat kerak bo'lganlari — mavjud bo'lsa; yashil natijada SKIP soniga qarang.
  • Parallellik izolyatsiyani talab qiladi (har faylga o'z bazasi, prefiksi); kanonda 4 → 16 ishchi atigi 23 % tezlashtirdi — tor joy umumiy resurslar.
  • Beqaror test sabab bilan tuzatiladi: kanonning to'rtta hodisasi — "javob ≠ holat", o'lchangan chegaralar, mutlaq → nisbiy chegara.
  • npm run check (tip, lint, test, build) — lokal; pre-push hook — yordamchi; majburiy tekshiruv — CI.
  • Kanon CI fayli ishga tushirilmagan: seed yo'li sinalmagan, MinIO/Mailpit testlari u yerda SKIP, audit qadami bugun 2 ta high zaiflik bilan qizil — --force qilinmaydi.
  • 31-qism yakuni: vazifalar-api v4.0.0 — 339 test, 26 migratsiya, AI va prodakshn naqshlari, o'lchov bilan asoslangan qarorlar.

Keyingi dars: Bisect: xatoni kiritgan commitni topish — 32-qism boshlanadi: regressiyani kiritgan commit'ni testlar yordamida avtomatik topamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Test arxitekturasi va CI: parallel testlar, beqaror testlar, lokal check va minimal pipeline — IlmHamroh