Mundarija (23)
- Bu darsda
- 1. Nega bu kerak?
- 2. Testcontainers qanday ishlaydi
- 2.1 Sukut xulq: Ryuk va port
- 2.2 Kanondagi istisno
- 3. Migratsiyalar va snapshot
- 3.1 before: bir marta tayyorlash
- 3.2 Har testdan oldin: restore
- 3.3 Kanon vaqtlari
- 4. Qachon ishlatish va qachon emas
- 4.1 Opt-in
- 4.2 Qayta ishlatish va CI
- 4.3 Global setup: bitta konteyner hamma fayllar uchun
- 4.4 Boshqa konteynerlar
- 5. Loyiha qadami: vazifalar-api konteyner testlari (holat 65, 3-qism)
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): parolni log'da yashirish
- 3-mashq (qiyin): qaysi strategiya arzon?
- 8. Real ishda
- Xulosa
- Manbalar
Testcontainers: testlar uchun haqiqiy Postgres konteyneri Node'da
Qisqacha: Testcontainers — test boshida haqiqiy bazani Docker konteynerida o'zi ko'tarib, oxirida o'chiradigan kutubxona. Test uchun oldindan baza o'rnatish, port va parol sozlash kerak emas:
new PostgreSqlContainer("postgres:18").start()— vagetConnectionUri()tayyor. Kanonda: konteyner 5.0 s,prisma migrate deploy4.1 s,snapshot()0.8 s, har testdan oldinrestoreSnapshot()~0.4 s; 10/10 test o'tdi, qoldiq konteyner 0. Narxi — Docker kerak va har ishga tushirish ~10 s qo'shadi, shuning uchun kanonda bu testlar alohida (TESTCONTAINERS=1).
Bu darsda
- Testcontainers nima muammoni hal qilishini tushuntirasiz.
@testcontainers/postgresqlbilan konteyner ko'tarib, migratsiyalarni qo'llaysiz.snapshot()/restoreSnapshot()bilan har testni toza bazada boshlaysiz.- Ryuk, tasodifiy port, sukut parol, global setup, konteynerni qayta ishlatish va CI haqida bilasiz.
vazifalar-apiga konteyner testlarini qo'shasiz.
Oldin bilishingiz kerak: Test DB strategiyalari, Image, container va registry, Prisma Migrate chuqur.
Oldingi darsda test bazasini tozalashning to'rt usulini ko'rdik. Hammasida bitta shart bor edi: test bazasi allaqachon mavjud va to'g'ri sxemada. Bugun bu shartni ham olib tashlaymiz.
1. Nega bu kerak?
Yangi dasturchi vazifalar-api ni klonladi va npm test berdi. Postgres testlari SKIP bo'ldi: kompyuterida 127.0.0.1:5499 da vazifalar_test bazasi yo'q. Uni yaratish uchun README'ni o'qib, Docker konteynerini ishga tushirib, sxemani qo'llash kerak. Boshqa dasturchida baza bor, lekin eski sxemada — testlar g'alati yiqiladi. CI serverida esa yana boshqa sozlama.
Testcontainers g'oyasi: test kerakli bazani o'zi ko'taradi. Har ishga tushirishda — yangi, toza, aniq versiyali Postgres. Test tugadi — konteyner o'chdi. Hamma kompyuterda va CI'da bir xil.
O'xshatish: har safar ijaraga olingan yangi, toza mashina. Kimdir uni avval qanday ishlatgani sizga ta'sir qilmaydi, ishingiz bitgach qaytarasiz.
sequenceDiagram
participant T as Test
participant D as Docker
participant P as Postgres konteyner
T->>D: start postgres:18
D->>P: yaratadi, port ochadi
T->>P: prisma migrate deploy
T->>P: snapshot (template)
loop har test
T->>P: restoreSnapshot
T->>P: test so'rovlari
end
T->>D: stopDiagrammaga qarang: qimmat ishlar (konteyner, migratsiya) bir marta, har test oldidan esa faqat arzon tiklash (restore).
2. Testcontainers qanday ishlaydi
Testcontainers — Docker API'si ustidagi kutubxona (Java'da boshlangan, hozir Node, Go, Python, .NET uchun ham bor). Node versiyasi ikki paket:
testcontainers— umumiy: istalgan image'dan konteyner (GenericContainer).@testcontainers/postgresql— Postgres uchun tayyor modul: sukut parol, baza nomi, "tayyor bo'lguncha kutish" va snapshot.
npm i -D --save-exact testcontainers@12.2.1 \
@testcontainers/postgresql@12.2.1Docker ishlab turishi shart (Docker Desktop yoki Linux'da Docker Engine — Konteyner nima va Docker'ni o'rnatish). Testcontainers Docker'ga o'zi ulanadi.
Eng kichik misol:
import { PostgreSqlContainer } from "@testcontainers/postgresql";
const konteyner =
await new PostgreSqlContainer("postgres:18").start();
const url = konteyner.getConnectionUri();
// url — postgres://<foydalanuvchi>:<parol>@<host>:<port>/<baza>
// … testlar …
await konteyner.stop();start() image'ni (bo'lmasa) yuklaydi, konteynerni yaratadi va Postgres ulanishni qabul qilguncha kutadi. Shuning uchun keyingi qatorda baza tayyor. getConnectionUri() — tayyor ulanish satri. Port har safar tasodifiy. Foydalanuvchi, parol va baza nomi esa sukut bo'yicha test (12.2.1 paket kodida shunday; withUsername, withPassword bilan o'zgartiriladi).
2.1 Sukut xulq: Ryuk va port
Ikki narsani bilib qo'ying (hujjat bo'yicha):
- Ryuk — Testcontainers yoniga ko'taradigan kichik "qorovul" konteyner. Test jarayoni kutilmaganda o'lsa (yiqilish, Ctrl+C), Ryuk uning konteynerlarini o'zi o'chiradi — kompyuterda "yetim" konteynerlar to'planmaydi.
- Port — konteynerning 5432 porti hostdagi tasodifiy portga bog'lanadi va sukut bo'yicha barcha tarmoq interfeyslarida ochiladi. Parol esa tasodifiy emas — sukut
test. Konteyner faqat test davomida yashaydi, lekin shu vaqtda tarmoqdagi boshqa kompyuter ham ulanishi mumkin. Umumiy tarmoqda (ofis, Wi-Fi).withPassword(randomUUID())bering.
Tasodifiy port muhim: bir vaqtda ikki loyihaning testi yoki ikki test jarayoni bir-biriga xalaqit bermaydi. Kodga hech qachon port yozmang — doim getConnectionUri() yoki getPort().
2.2 Kanondagi istisno
Kanonning o'z qoidasi bor edi: hamma konteyner kanon27-* nomli va faqat 127.0.0.1 da (egasining kompyuterida boshqa konteynerlar ham ishlaydi). Shuning uchun kanon:
- konteynerga nom berdi —
kanon27-tc-<pid>; - Ryuk'ni o'chirdi (
TESTCONTAINERS_RYUK_DISABLED=true) — Ryuk konteynerining nomi qoidaga to'g'ri kelmaydi; o'rnigaafter()da konteynerni o'zi to'xtatadi; - port bo'yicha istisno qildi: barcha interfeyslarda, lekin faqat test davomida (parol — sukut
test).
O'zingizning loyihangizda sukut xulqni qoldiring: Ryuk yoqiq — xavfsizroq. Kanon tekshiruvidan keyin qoldiq konteyner: 0.
3. Migratsiyalar va snapshot
3.1 before: bir marta tayyorlash
before(async () => {
let t = performance.now();
const k = await new PostgreSqlContainer("postgres:18")
.withName(`kanon27-tc-${String(process.pid)}`)
// diskka yozmaydi — tezroq, to'xtaganda yo'qoladi
.withTmpFs({ "/var/lib/postgresql": "rw" })
.start();
konteyner = k;
vaqtlar["konteyner"] = performance.now() - t;
t = performance.now();
const r = spawnSync(process.execPath,
[join("node_modules", "prisma", "build", "index.js"),
"migrate", "deploy"], {
env: { ...process.env, DATABASE_URL: k.getConnectionUri() },
encoding: "utf8",
});
if (r.status !== 0) {
throw new Error(`migrate deploy: ${r.stdout}${r.stderr}`);
}
vaqtlar["migratsiya"] = performance.now() - t;
t = performance.now();
await k.snapshot(); // migratsiyalangan bo'sh baza → template
vaqtlar["snapshot"] = performance.now() - t;
});Uch qadam:
- Konteyner.
withTmpFs— Postgres ma'lumot papkasini xotiradagi diskka (tmpfs) qo'yadi: yozuvlar tez, konteyner o'chganda hech narsa qolmaydi. Test bazasi uchun ideal. - Migratsiya. Xuddi production deploy'idagi buyruq (Migratsiyani deploy jarayonida qo'llash):
prisma migrate deploy, faqatDATABASE_URL— konteynerniki. Shunday qilib testlar haqiqiy migratsiyalar sxemasida ishlaydi — qo'lda yozilgan test sxemasida emas. Migratsiya yiqilsa — test ham yiqiladi, xabari bilan. - Snapshot.
snapshot()— migratsiyalangan bo'sh bazani shablon sifatida saqlaydi. Ichida — oldingi darsdagiCREATE DATABASE … TEMPLATE(Test DB strategiyalari).
3.2 Har testdan oldin: restore
/** Har test toza bazada: restore (template'dan qayta yaratish) —
* pool'ni oldin yopish kerak (restore ochiq ulanishlarni uzadi) */
async function toza(): Promise<Pool> {
if (konteyner === undefined) {
throw new Error("konteyner ishga tushmagan");
}
await pool?.end();
const t = performance.now();
await konteyner.restoreSnapshot();
vaqtlar["restore (oxirgi)"] = performance.now() - t;
const yangi = pgPool(konteyner.getConnectionUri(), { max: 5 });
pool = yangi;
return yangi;
}restoreSnapshot() joriy bazani o'chirib, shablondan qayta yaratadi. 12.2.1 paket kodida bu ikki buyruq: DROP DATABASE "test" WITH (FORCE) va CREATE DATABASE "test" WITH TEMPLATE …. WITH (FORCE) — bazaga ulangan hamma sessiyani majburan uzadi, shuning uchun oldingi darsdagi 55006 xatosi bu yerda chiqmaydi.
Lekin eski hovuzning ulanishlari ham uziladi. Hovuz ularni "tirik" deb o'ylaydi va keyingi so'rov xato bilan yiqiladi. Shuning uchun avval hovuzni yopamiz (pool.end()), restore'dan keyin esa yangi hovuz ochamiz. Har testda — yangi hovuz.
3.3 Kanon vaqtlari
Yakuniy tekshiruvdan (TESTCONTAINERS=1):
--- Testcontainers (TESTCONTAINERS=1): [Testcontainers vaqtlari] konteyner 5001 ms, migratsiya 4063 ms, snapshot 827 ms, restore (oxirgi) 401 ms ℹ tests 10 ℹ pass 10 ℹ fail 0 ℹ skipped 0 ; kanon27-tc qoldiqlari: 0- Konteyner start5 001 ms
- prisma migrate deploy4 063 ms
- snapshot827 ms
- restore (har test)401 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-25: Testcontainers 12.2.1, postgres:18 (tmpfs), Docker Desktop (WSL2), i5-12500H
Bir martalik narx 10 soniya (kanon boshqa ishga tushirishlarda konteyner 5.0–11.2 s, migratsiya 4.1–5.7 s o'lchagan — mashina yuklamasiga bog'liq). Har test oldidan — 0.4 s: 0.1 s) dan sekin, lekin to'liq toza baza, trigger'lar va sequence'lar ham boshlang'ich holatda.TRUNCATE (
Tekshirib ko'ring: Nega konteyner har test uchun emas, har fayl uchun bir marta ko'tariladi?
Javob
Konteyner va migratsiya ~9 s. 10 ta test uchun alohida konteyner — 90 s. Bir marta ko'tarib, testlar orasida faqat restoreSnapshot() (0.4 s) qilish — 10 + 4 s atrofida. Izolyatsiya bir xil: har test baribir toza bazada boshlanadi.
4. Qachon ishlatish va qachon emas
4.1 Opt-in
Kanon Testcontainers testlarini ixtiyoriy qildi:
const YOQIQ = process.env["TESTCONTAINERS"] === "1";
if (!YOQIQ) {
test("Testcontainers (BT-11)", {
skip: "TESTCONTAINERS=1 bilan ishga tushiriladi (Docker kerak)",
}, () => {});
} else {
process.env["TESTCONTAINERS_RYUK_DISABLED"] = "true";
const { PostgreSqlContainer } =
await import("@testcontainers/postgresql");
// … before, after, toza, testlar …
}Sabab: kundalik npm test tez bo'lishi kerak (kanonda 112 test ~14 s, lokal baza bilan). Docker'ni har saqlashda ishga tushirish og'ir. TESTCONTAINERS=1 npm test — commit yoki push oldidan, CI'da esa doim. await import(…) — paket faqat kerak bo'lganda yuklanadi (Dinamik import() va top-level await).
Boshqa yo'l: lokal baza testlarini butunlay Testcontainers'ga o'tkazish. Kichik jamoada bitta yondashuv sodda; katta loyihada ikkalasi.
4.2 Qayta ishlatish va CI
- Qayta ishlatish (
.withReuse(); 12.2.1 paket kodidaTESTCONTAINERS_REUSE_ENABLE=falseuni o'chiradi): konteyner test tugagach o'chmaydi, keyingi ishga tushirishda shu konteyner olinadi — 5 s tejaladi. Lokal ishlab chiqish uchun qulay, CI uchun emas. Kanonda sinalmagan. - CI — GitHub Actions'ning Linux runner'larida Docker bor, Testcontainers qo'shimcha sozlamasiz ishlaydi (hujjat; kanonda CI sinalmagan — kurs CI'si 33–34-qismlarda).
- Image versiyasi —
postgres:18emas, aniq teg (postgres:18.6) yoki digest: aks holda bir kuni yangi minor versiya bilan testlar boshqacha ishlashi mumkin. Production versiyasi bilan bir xil bo'lsin.
| Lokal test bazasi | Testcontainers | |
|---|---|---|
| Tayyorlash | qo'lda (README) | avtomatik |
| Sxema | qo'lda qo'llanadi | migratsiyalardan, har safar |
| Bir martalik narx | 0 | ~10 s |
| Docker | shart emas | shart |
| Versiya | dasturchida har xil | kodda aniq |
4.3 Global setup: bitta konteyner hamma fayllar uchun
Kanonda konteyner bitta test faylining before ida ko'tariladi. Testcontainers'li fayllar ko'paysa, har fayl o'z konteynerini ko'taradi — har biriga ~10 s. Node 24 test runner'ida buning yechimi bor: global setup — barcha test fayllaridan oldin bir marta ishlaydigan fayl (--test-global-setup bayrog'i, node:test va node:assert).
Kanonda sinalmagan. Biz konteynersiz kichik misolda sinadik (Node 24.21.0). setup.mjs:
export async function globalSetup() {
process.env.TEST_DB_URL = "postgres://test@127.0.0.1:55000/test";
console.log("globalSetup: konteyner ko'tarildi");
}
export async function globalTeardown() {
console.log("globalTeardown: konteyner to'xtadi");
}Ikkita test fayli (a.test.mjs, b.test.mjs) faqat process.env.TEST_DB_URL ni tekshiradi. Ishga tushirish (chiqishdan tanlangan qatorlar):
node --test --test-global-setup=setup.mjsglobalSetup: konteyner ko'tarildi
✔ a fayli URL ni ko'radi (1.077ms)
✔ b fayli URL ni ko'radi (0.7554ms)
ℹ tests 2
ℹ pass 2
ℹ fail 0
globalTeardown: konteyner to'xtadiNima bo'ldi? globalSetup bitta marta, eng boshida ishladi. U qo'ygan muhit o'zgaruvchisini ikkala fayl ham ko'rdi — fayllar alohida jarayonlarda bo'lsa ham. globalTeardown eng oxirida ishladi. Haqiqiy loyihada globalSetup ichida new PostgreSqlContainer(…).start() va migratsiya bo'ladi. Ehtiyot bo'ling: hamma fayl bitta bazaga tushadi va parallel ishlaydi — har faylga alohida baza kerak (Test DB strategiyalari dagi parallel testlar muammosi).
4.4 Boshqa konteynerlar
Xuddi shunday Redis, Mongo, Kafka — @testcontainers/redis va boshqa modullar yoki GenericContainer("redis:8"). vazifalar-api keshi hozircha lokal Redis bilan sinaladi (TEST_REDIS_URL) — konteynerga o'tkazish keyingi qadam bo'lishi mumkin.
5. Loyiha qadami: vazifalar-api konteyner testlari (holat 65, 3-qism)
Ikki dev bog'liqlik:
npm i -D --save-exact testcontainers@12.2.1 \
@testcontainers/postgresql@12.2.1Yangi fayl test/konteyner.test.ts. Bu darsda — uning karkasi, migratsiya testi va shartnoma testlari. Cheklovlar, trigger'lar va RLS testlari (BT-13 bloki) Repository va SQL'ni testlash darsida qo'shiladi; ular ishlatadigan fabrika — Factory va fixture'lar da. Kanon kodi (qatorlar telefon uchun bo'lingan, izohlar qisqartirilgan):
// konteyner.test.ts — Testcontainers bilan HAQIQIY Postgres 18:
// test o'zi konteyner ko'taradi, migratsiyalarni
// (prisma migrate deploy) qo'llaydi, snapshot oladi,
// har testdan oldin restore.
// Opt-in: TESTCONTAINERS=1 (Docker kerak, ~10 s);
// oddiy npm test — SKIP.
import { after, before, beforeEach, describe, test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { join } from "node:path";
import type { Pool } from "pg";
import { pgPool, PgOmbori } from "../src/vazifalar/pg-ombor.ts";
import { omborShartnomasi } from "./ombor-shartnoma.ts";
const YOQIQ = process.env["TESTCONTAINERS"] === "1";
if (!YOQIQ) {
test("Testcontainers (BT-11)", {
skip: "TESTCONTAINERS=1 bilan ishga tushiriladi (Docker kerak)",
}, () => {});
} else {
process.env["TESTCONTAINERS_RYUK_DISABLED"] = "true";
const { PostgreSqlContainer } =
await import("@testcontainers/postgresql");
type Ishlayotgan = Awaited<ReturnType<
InstanceType<typeof PostgreSqlContainer>["start"]>>;
let konteyner: Ishlayotgan | undefined;
let pool: Pool | undefined;
const vaqtlar: Record<string, number> = {};
let db: Pool; // joriy test bazasi (beforeEach — toza())
before(async () => {
// … «before: bir marta tayyorlash» bo'limidagi kod …
});
after(async () => {
await pool?.end();
await konteyner?.stop();
console.log(` [Testcontainers vaqtlari] ${Object.entries(vaqtlar)
.map(([k, v]) => `${k} ${v.toFixed(0)} ms`).join(", ")}`);
});
async function toza(): Promise<Pool> {
// … «Har testdan oldin: restore» bo'limidagi kod …
}
describe("BT-11: migratsiyalar konteynerda", () => {
beforeEach(async () => { db = await toza(); });
test("hamma migratsiya qo'llangan, sxema obyektlari joyida",
async () => {
const { rows } = await db.query<{ m: string }>(
"SELECT migration_name AS m FROM _prisma_migrations "
+ "WHERE finished_at IS NOT NULL ORDER BY 1");
assert.equal(rows[0]?.m, "0_init");
const { rows: [s] } = await db.query<
{ trg: number; mv: number; rls: number }>(`SELECT
(SELECT count(*)::int FROM pg_trigger
WHERE NOT tgisinternal) AS trg,
(SELECT count(*)::int FROM pg_matviews) AS mv,
(SELECT count(*)::int FROM pg_class
WHERE relrowsecurity) AS rls`);
assert.deepEqual(s, { trg: 5, mv: 1, rls: 5 });
});
});
// BT-10: shartnoma testlari ham konteynerda — har test
// template'dan toza baza (TRUNCATE kerak emas)
omborShartnomasi("postgres (Testcontainers)", async () => {
const p = await toza();
await p.query("INSERT INTO foydalanuvchilar (email, toliq_ism) "
+ "VALUES ('test@example.com', 'Test')");
return new PgOmbori(p, 1);
});
}before va toza ichi yuqoridagi bo'limlarda to'liq — faylga o'sha kodni qo'ying (… qatorlari o'rniga). Ikki muhim test:
- Migratsiya testi —
0_initqo'llangan, 5 ta trigger, 1 materialized view, 5 ta RLS'li jadval bor. Bu "migratsiyalar haqiqatan sxemani to'liq quradimi" savoliga javob. ORM sxemasidan yaratilgan baza bilan bu test yiqilardi: trigger va RLS Prisma sxemasida yo'q (Prisma Migrate chuqur). - Shartnoma testlari —
PgOmborixotira va fayl omborlari o'tgan o'sha testlardan konteynerda ham o'tadi.
Bu yerda kanon muhim xatoni topdi. Testcontainers Postgres modulining sukut superuser'i — postgres emas, test. Birinchi ishga tushirishda migrate deploy yiqildi: role "postgres" does not exist. Sabab — 106-rollar.sql dagi ALTER DEFAULT PRIVILEGES FOR ROLE postgres (Rollar va huquqlar): migratsiya ma'lum foydalanuvchi nomiga bog'lanib qolgan edi. Managed bazalarda (boshqa admin nomi) ham xuddi shunday yiqilardi. Kanon FOR ROLE CURRENT_USER ga tuzatdi.
Odatda qo'llangan migratsiya fayli o'zgartirilmaydi — yangi migratsiya yoziladi (Migratsiya tushunchasi). Bu yerda istisno qilindi: kurs hali nashr qilinmagan va yangi bazada eski 106 baribir yiqiladi — tuzatuvchi 109-migratsiya yordam bermasdi. Haqiqiy loyihada bunday holatda production'da allaqachon qo'llangan faylning checksum'i o'zgaradi va Prisma ogohlantiradi — jamoa bilan kelishib hal qilinadi. Bu topilmaning o'zi Testcontainers foydasini ko'rsatadi: "faqat mening kompyuterimda ishlaydi" turidagi xato birinchi toza bazada ochildi.
Ishga tushirish (Docker yoqiq):
TESTCONTAINERS=1 npm testNatija tests 10, pass 10 (shartnoma + migratsiya + keyingi darslardagi testlar bilan birga), konteyner oxirida o'chadi. Hali commit qilmang.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Testcontainers Docker'ni topa olmadi degan xato | Docker ishlamayapti | Docker Desktop/Engine'ni yoqing |
role "postgres" does not exist |
migratsiya admin nomiga bog'langan | CURRENT_USER, nomga bog'lanmang |
| restore'dan keyin birinchi so'rov yiqiladi | eski hovuz ulanishlari uzilgan | avval pool.end(), keyin yangi hovuz |
| Testlar 10 s sekinlashdi | har testda yangi konteyner | before da bir marta |
| Kompyuterda eski konteynerlar to'planadi | Ryuk o'chiq va stop() chaqirilmagan |
Ryuk yoqiq qoldiring yoki after da stop() |
| CI'da testlar boshqacha | postgres:latest yoki suzuvchi teg |
aniq versiya teg |
Test kodida port 5432 |
konteyner porti tasodifiy | getConnectionUri() |
7. Mashqlar
Mashqlar kurs/mashqlar/27/63-testcontainers/ da.
1-mashq (oson): to'ldiring
- Konteyner ulanish satrini beradigan metod —
- Jarayon yiqilsa konteynerlarni o'chiradigan "qorovul" —
- Migratsiyalangan bazani shablon qilish —
- Har testdan oldin shablondan tiklash —
- Ma'lumot papkasini xotiraga qo'yadigan sozlama —
Yechim
- getConnectionUri — port har safar boshqa; kodga yozmang.
- Ryuk — sukut yoqiq; kanon nomlash qoidasi tufayli o'chirgan.
- snapshot — ichida
CREATE DATABASE … TEMPLATE. - restoreSnapshot — kanonda ~0.4 s; oldin hovuzni yoping.
- withTmpFs — tez, konteyner bilan yo'qoladi.
2-mashq (o'rta): parolni log'da yashirish
Kanon testi xato bo'lganda migrate deploy chiqishini log'ga yozadi — unda ulanish satri (parol bilan) bo'lishi mumkin. parolniYashir(matn) funksiyasini yozing: matndagi har postgres://foydalanuvchi:parol@ (yoki postgresql://) bo'lagida parolni *** ga almashtirsin. Sinang: postgres://test:Xk9q2Lm@localhost:55123/test, parolsiz URL va ikki URL bor matn.
Yechim
function parolniYashir(matn) {
return matn.replace(
/(postgres(?:ql)?:\/\/[^:/@\s]+:)[^@\s]+@/g,
"$1***@",
);
}
console.log(parolniYashir(
"postgres://test:Xk9q2Lm@localhost:55123/test"));
console.log(parolniYashir(
"postgres://postgres@127.0.0.1:5499/vazifalar"));
console.log(parolniYashir(
"A=postgresql://a:1@h/x B=postgres://b:2@h/y",
));Konsolda:
postgres://test:***@localhost:55123/test
postgres://postgres@127.0.0.1:5499/vazifalar
A=postgresql://a:***@h/x B=postgres://b:***@h/yBirinchi guruh — sxema://foydalanuvchi: gacha, keyin parol (@ gacha) almashadi. Parolsiz URL'da : dan keyin @ kelmaydi — o'zgarmadi. g bayrog'i — matndagi hamma URL (Almashtirish chuqur va xavfsiz dinamik regex). Test paroli tasodifiy va vaqtinchalik bo'lsa ham, log'larni tozalash odati — productionda haqiqiy parollarni qutqaradi (Sirlarni boshqarish).
3-mashq (qiyin): qaysi strategiya arzon?
Kanon raqamlarini oling:
- Testcontainers — bir martalik 5 001 + 4 063 + 827 ms, har test 401 ms;
- lokal baza + TRUNCATE — bir martalik 0, har test 106 ms;
- har testga yangi konteyner — har test 5 001 + 4 063 ms.
vaqt(strategiya, n) funksiyasini yozing va n = 10, 100, 500 testlar uchun soniyalarda (1 xona) jadval chiqaring.
Yechim
const STRATEGIYALAR = {
"TRUNCATE (lokal)": { bir: 0, har: 106 },
"Testcontainers + restore": { bir: 5001 + 4063 + 827, har: 401 },
"har testga konteyner": { bir: 0, har: 5001 + 4063 },
};
function vaqt(s, n) {
return (s.bir + s.har * n) / 1000;
}
for (const [nom, s] of Object.entries(STRATEGIYALAR)) {
const qator = [10, 100, 500].map((n) => vaqt(s, n).toFixed(1));
console.log(`${nom}: ${qator.join(" | ")} s`);
}Konsolda:
TRUNCATE (lokal): 1.1 | 10.6 | 53.0 s
Testcontainers + restore: 13.9 | 50.0 | 210.4 s
har testga konteyner: 90.6 | 906.4 | 4532.0 sLokal baza + TRUNCATE hamma joyda tez — lekin tayyor baza talab qiladi. Testcontainers bir martalik ~10 s va har testga 4 baravar qimmatroq tiklash bilan izolyatsiya va avtomatik sozlash beradi. Har testga yangi konteyner — 500 testda 75 daqiqa: hech qachon. Amalda aralash: tez testlar lokal, to'liq tekshiruv Testcontainers bilan (kanondagi opt-in). Restore'ni har test emas, har describe bloki uchun qilish ham narxni kamaytiradi.
8. Real ishda
- Java/Spring dunyosida Testcontainers — standart; Node'da ham tez tarqalmoqda (NestJS e2e testlari — 30-qismda).
- CI — GitHub Actions/GitLab'da Docker bilan; ko'p jamoalar PR'da faqat Testcontainers testlarini ishlatadi.
- Mikroxizmatlar — bitta testda Postgres + Redis + Kafka konteynerlari.
- Intervyuda: "Integration testlarda bazani qanday tayyorlaysiz?", "Testcontainers nima va uning narxi?", "Testlar orasida bazani qanday tozalaysiz?"
Xulosa
- Testcontainers — test o'zi haqiqiy bazani konteynerda ko'taradi: hamma kompyuterda va CI'da bir xil.
new PostgreSqlContainer("postgres:18").start()→getConnectionUri(); port tasodifiy, parol sukuttest.before: konteyner →prisma migrate deploy→snapshot(); har test:restoreSnapshot()(avval hovuzni yoping).- Kanon: ~10 s bir martalik, 0.4 s har test; 10/10, qoldiq 0; Ryuk sukut yoqiq (kanon nom qoidasi uchun o'chirgan).
- Toza bazadagi birinchi ishga tushirish yashirin xatoni ochdi: migratsiya
postgresroliga bog'langan edi.
Keyingi dars: Factory va fixture'lar — test ma'lumotini qo'lda emas, fabrika bilan: faker uz_UZ_latin, seed, minimal ma'lumot va bog'lanishli fabrikalar.
Manbalar
- Testcontainers for Node.js: https://node.testcontainers.org/ — PostgreSQL moduli (snapshot/restore): https://node.testcontainers.org/modules/postgresql/
- Ryuk va konfiguratsiya (
TESTCONTAINERS_RYUK_DISABLED, reuse): https://node.testcontainers.org/configuration/ - PostgreSQL 18 — CREATE DATABASE TEMPLATE: https://www.postgresql.org/docs/18/manage-ag-templatedbs.html
- Kod va vaqtlar — kanon o'lchovi (IlmHamroh, 2026-10): holat 65
test/konteyner.test.ts, Testcontainers 12.2.1, postgres:18, Node 24.21.0.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!