IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis64/65-dars14 daqiqa
Mundarija (22)

Test factory va fixture'lar: faker uz_UZ_latin, seed va bog'lanishli fabrikalar

Qisqacha: Factory (fabrika) — test ma'lumotini yaratadigan funksiya: testda faqat shu test uchun muhim maydon yoziladi (vazifa(db, { matn: "Non olish" })), qolgani — yaroqli sukut qiymat. Bog'langan narsa kerak bo'lsa, fabrika uni o'zi yaratadi (egasi berilmasa — yangi foydalanuvchi). Tasodifiy, lekin real ko'rinishdagi ma'lumot — faker (fakerUZ_UZ_latin), va seed bilan har ishga tushirishda bir xil: biz ishga tushirdik — seed(27) dan keyin doim Sadoqat Tohirova. Fixture esa — oldindan yozib qo'yilgan qat'iy ma'lumot to'plami; kichik va o'zgarmas holatlar uchun yaxshi, lekin tez eskiradi.

Bu darsda

  • Fixture va factory farqini va qachon qaysi biri kerakligini tushuntirasiz.
  • Testda faqat muhim maydonlarni yozadigan fabrika yasaysiz.
  • Bog'lanishli fabrika (vazifa → egasi) va noyob maydonlar uchun ketma-ket raqam ishlatasiz.
  • @faker-js/faker ni o'zbekcha lokal va seed bilan deterministik qilasiz.
  • Builder naqshi va fishery kutubxonasi haqida bilasiz.

Oldin bilishingiz kerak: Test DB strategiyalari, Testcontainers, Seeding va test ma'lumotlari, Vaqt va tasodifni boshqarish.

Oldingi darsda har test toza bazada boshlanadigan qildik. Bo'sh baza — yaxshi boshlanish. Endi har test unga o'ziga kerakli ma'lumotni qanday qo'yishini tartibga solamiz.

1. Nega bu kerak?

Test "boshqa foydalanuvchida xuddi shu matnli vazifa bo'lishi mumkin"ni tekshiradi. Qo'lda yozilgan variant:

ts
const { rows: [a] } = await db.query(
  "INSERT INTO foydalanuvchilar (email, toliq_ism) "
  + "VALUES ('a@test.example', 'Aziz Karimov') RETURNING id");
const { rows: [b] } = await db.query(
  "INSERT INTO foydalanuvchilar (email, toliq_ism) "
  + "VALUES ('b@test.example', 'Malika Tosheva') RETURNING id");
await db.query(
  "INSERT INTO vazifalar (egasi_id, matn) VALUES ($1, $2)",
  [a.id, "Non olish"]);
await db.query(
  "INSERT INTO vazifalar (egasi_id, matn) VALUES ($1, $2)",
  [b.id, "Non olish"]);

Uch muammo:

  1. Shovqin. Test nimani tekshirayotgani (matn bir xil, egalar har xil) INSERT'lar orasida yo'qolib ketgan.
  2. Mo'rtlik. Ertaga foydalanuvchilar ga majburiy ustun qo'shilsa (masalan til), yuzlab testdagi INSERT'lar yiqiladi.
  3. Takror. Har test bir xil INSERT'larni nusxalaydi.

Fabrika bilan:

ts
await vazifa(db, { matn: "Non olish" });
assert.ok(await vazifa(db, { matn: "Non olish" }));

Har chaqiruv o'z egasini yaratadi — ikki xil foydalanuvchi. Testda faqat muhim narsa qoldi: matn.

O'xshatish: restoranda "osh bering" deysiz. Oshpaz qozon, guruch, sabzi va go'shtni o'zi tanlaydi. "Go'shtsiz" desangizgina tafsilot aytasiz. Fabrika — shu oshpaz: siz faqat muhim farqni aytasiz.

2. Fixture va factory

Fixture — testlar uchun oldindan tayyorlangan qat'iy ma'lumot: JSON fayl yoki SQL skript ("3 ta foydalanuvchi, 10 ta vazifa"). Playwright darslarida "fixture" so'zini boshqa ma'noda ko'rgansiz — test uchun tayyorlangan muhit (Playwright I: e2e testlar asoslari); ma'lumot fixture'i ham shu oiladan.

Fixture Factory
Ma'lumot oldindan yozilgan, bir xil test chaqirganda yaratiladi
Testda ko'rinadimi yo'q — boshqa faylda ha — muhim maydonlar testda
Sxema o'zgarsa hamma fixture qayta yoziladi bitta fabrika tuzatiladi
Yaxshi joyi katta qat'iy namuna, hisobot testi ko'p kichik, mustaqil testlar

Fixture'ning yashirin xavfi — sirli mehmon (mystery guest): test "3-foydalanuvchining 7-vazifasi bajarilgan" deb kutadi, lekin nega — fixture faylini ochmaguncha bilib bo'lmaydi. Fabrikada test kerakli holatni o'zi aniq yaratadi.

vazifalar-api da ikkalasi bor. 26-qism namuna fayllari (003-namuna-asosiy.sql va boshqalar) — jonli server va qo'lda sinash uchun fixture. Seeding va test ma'lumotlari darsidagi prisma/seed.ts — demo ma'lumot. Konteyner testlari esa bo'sh bazada fabrika bilan ishlaydi.

3. Fabrika qoidalari

3.1 Minimal va yaroqli sukut

Fabrika chaqirilganda hech narsa bermasangiz ham — yaroqli obyekt chiqishi kerak: hamma NOT NULL va CHECK cheklovlaridan o'tadigan. Test faqat o'zi tekshirayotgan maydonni o'zgartiradi (ust — ustidan yoziladigan qiymatlar):

ts
export async function vazifa(
  pool: Pool,
  ust: Partial<Pick<VazifaQatori,
    "egasi_id" | "matn" | "bajarildi">> = {},
): Promise<VazifaQatori> { … }

Partial<Pick<…>> — faqat ruxsat etilgan maydonlar, hammasi ixtiyoriy (Utility tiplar). Test id yoki versiya ni bera olmaydi — ularni baza va trigger'lar beradi.

3.2 Noyob maydonlar: ketma-ket raqam

foydalanuvchilar.email — noyob. vazifalar.matn — bir egasining faol vazifalari orasida noyob (014-migratsiyadagi EXCLUDE). Tasodifiy qiymat bir kun to'qnashadi va test "ba'zan" yiqiladi. Shuning uchun noyob maydonga ketma-ket raqam qo'shiladi: f1@test.example, f2@test.example, Non olish 3.

Email domeni — test.example: .example — hech kimga tegishli bo'lmagan zaxira domen (RFC 2606). Test hech qachon haqiqiy odamga xat yubormaydi.

3.3 Bog'lanishli fabrika

Vazifaga egasi kerak (tashqi kalit). Test egasini bermasa — fabrika uni o'zi yaratadi:

ts
const egasi = ust.egasi_id ?? (await foydalanuvchi(pool)).id;

Test "bitta foydalanuvchining ikki vazifasi" ni tekshirsa — egasini bir marta yaratib, ikkalasiga beradi. Muhim narsa testda ko'rinadi.

flowchart TD
  T["vazifa(db, ust)"] --> E{ust.egasi_id bormi?}
  E -->|yo'q| F["foydalanuvchi(db)"]
  F --> I
  E -->|ha| I[INSERT vazifalar]
  I --> S{ust.matn bormi?}
  S -->|yo'q| M[faker + ketma-ket raqam]
  S -->|ha| R[test bergan matn]
  M --> Q[RETURNING id, tartib, versiya]
  R --> Q

Diagrammaga qarang: test bermagan har narsani fabrika o'zi to'ldiradi — bog'langan foydalanuvchini ham, matnni ham. Test bergan qiymat esa doim ustun.

3.4 Faqat kerakli miqdor

Fabrika test so'ragandan ortiq yaratmasin. "Har vazifaga 3 ta izoh va 2 ta teg" ni sukut qilish oson, lekin har test sekinlashadi va chalg'ituvchi ma'lumot paydo bo'ladi (ro'yxat testida kutilmagan izohlar). Izoh kerak bo'lsa — test o'zi chaqiradi.

4. Faker: real ko'rinishdagi ma'lumot

4.1 O'zbekcha lokal

@faker-js/faker — ism, manzil, telefon, matn kabi soxta, lekin real ko'rinadigan ma'lumot generatori. U lokallarga bo'lingan: fakerUZ_UZ_latin — o'zbek lotin ismlari.

bash
npm i -D --save-exact @faker-js/faker@10.6.0

vazifalar-api da u 39-holatdan beri bor (seed uchun). Biz ishga tushirdik (Node 24.21.0, @faker-js/faker 10.6.0). Chiqishda birinchi va oxirgi qatorni solishtiring:

js
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";

faker.seed(27);
for (let i = 0; i < 3; i++) console.log(faker.person.fullName());
faker.seed(27);
console.log("qayta seed:", faker.person.fullName());
text
Sadoqat Tohirova
Umar Sadriddinov
Sulton Toyipov
qayta seed: Sadoqat Tohirova

4.2 Seed — takrorlanadigan tasodif

faker.seed(27) — tasodifiy sonlar generatorini aniq nuqtadan boshlaydi (Vaqt va tasodifni boshqarish dagi urug'li generator). Bir xil seed — bir xil ketma-ketlik: biz skriptni ikki marta yurgizdik, chiqish bir xil. Qayta seed(27) dan keyin yana Sadoqat Tohirova.

Nega bu muhim? Test CI'da yiqildi, xabarda — "Sulton Toyipov". Seed bo'lmasa, lokalda bu holatni qayta yaratib bo'lmaydi: har safar boshqa ism. Seed bilan — xuddi o'sha ma'lumot. Kanon fabrikasi har test oldidan seed'ni qayta o'rnatadi: testlar tartibi o'zgarsa ham, har test o'z ma'lumotini bir xil oladi.

4.3 Faker tuzoqlari

Biz yana bir nechta maydonni sinadik (seed(1)):

js
faker.seed(1);
console.log("seed 1:", faker.person.fullName());
console.log(faker.location.city(), "|", faker.phone.number());
console.log(faker.internet.email());
text
seed 1: Oynur Halilyeva
Bo`ka | 435-562-8064 x5118
Omina16@yahoo.com

Uchta topilma:

  1. **Shahar Bo\ka** — lokal ma'lumotida tutuq belgisi `` `` (backtick) bilan. Kursimizda esa oddiy ' ishlatiladi. Test bunday matnni ko'rsatsa — qo'rqmang, lekin o'quvchiga ko'rinadigan demo ma'lumotda tekshiring.
  2. Telefon — AQSh formati: o'zbek lokalida telefon shabloni yo'q, faker zaxira (inglizcha) lokalni oldi. +998 kerak bo'lsa — o'zingiz yasang: `+998 90 ${faker.string.numeric(3)} …`.
  3. Email — yahoo.com: haqiqiy domen. Testdan xat ketib qolsa — kimningdir haqiqiy qutisiga. Domenni majburlang:
js
faker.seed(1);
console.log(faker.internet.email({ provider: "example.com" }));
console.log(faker.phone.number({ style: "international" }));
text
Oynur.Avazyeva@example.com
+15547280645

provider — example.com. Xalqaro telefon ham +1 (AQSh). Kanon fabrikasi emailni faker'ga umuman bermaydi — f<n>@test.example: noyob va xavfsiz.

Tekshirib ko'ring: Nega fabrikada faker.internet.email() dan ko'ra `f${n}@test.example` yaxshiroq?

Javob

Ikki sabab. (1) Noyoblik: faker tasodifiy, 1 000 ta testda ikkita bir xil email chiqishi mumkin — UNIQUE cheklovi testni "ba'zan" yiqitadi; ketma-ket raqam to'qnashmaydi. (2) Xavfsizlik: faker haqiqiy domenlar (yahoo.com) beradi; .example esa hech kimga tegishli emas. Faker'ni faqat "ko'rinishi muhim, noyobligi muhim emas" maydonlarga bering (ism).

5. Builder va fishery

Kanon fabrikasi — oddiy funksiyalar. Katta loyihalarda ikki naqsh uchraydi (kanonda sinalmagan, hujjat bo'yicha):

  • Builder naqshi — zanjir bilan: vazifaBuilder().bajarilgan().tegBilan("uy").egasi(f).build(). Ko'p "holat" (bajarilgan, o'chirilgan, muddati o'tgan) bo'lsa o'qilishi oson.
  • fishery — fabrikalar uchun TypeScript kutubxonasi: Factory.define<Vazifa>(({ sequence }) => ({ id: sequence, matn: \Vazifa ${sequence}` })), vazifaFactory.build({ matn: "Non" }), buildList(5), onCreate(bazaga yozish),transient parametrlar. Ketma-ket raqam (sequence) va params(bizningust`) — xuddi shu g'oyalar.

Kichik loyihada o'z funksiyalaringiz yetarli: 40 qator, bog'liqliksiz. Fabrikalar ko'payib, "holatlar" murakkablashsa — fishery.

6. Loyiha qadami: test/fabrika.ts (holat 65, 4-qism)

Yangi fayl test/fabrika.ts (kanon kodi; qatorlar telefon uchun bo'lingan):

ts
// fabrika.ts — test ma'lumoti FACTORY bilan: testda faqat test
// uchun MUHIM maydonlar yoziladi, qolgani yaroqli sukut.
// Faker (uz_UZ_latin) har test boshida seed bilan — deterministik
// (yiqilgan testni qayta yaratish mumkin). Ketma-ket raqam (n) —
// noyob maydonlar (email, matn) to'qnashmasin (014: faol vazifalar
// orasida matn noyob).
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";
import type { Pool } from "pg";

let n = 0;
export function fabrikaniBoshla(seed = 27): void {
  faker.seed(seed);
  n = 0;
}

export type FoydalanuvchiQatori = {
  id: number; email: string; toliq_ism: string;
};
export type VazifaQatori = {
  id: number; egasi_id: number; matn: string; bajarildi: boolean;
  tartib: number; versiya: number;
};

export async function foydalanuvchi(
  pool: Pool,
  ust: Partial<Pick<FoydalanuvchiQatori,
    "email" | "toliq_ism">> = {},
): Promise<FoydalanuvchiQatori> {
  n++;
  const { rows: [f] } = await pool.query<FoydalanuvchiQatori>(
    "INSERT INTO foydalanuvchilar (email, toliq_ism) VALUES ($1, $2) "
    + "RETURNING id, email, toliq_ism",
    [ust.email ?? `f${String(n)}@test.example`,
      ust.toliq_ism ?? faker.person.fullName()]);
  if (f === undefined) throw new Error("foydalanuvchi yaratilmadi");
  return f;
}

/** Egasi berilmasa — o'zi yaratadi (relation bilan factory) */
export async function vazifa(
  pool: Pool,
  ust: Partial<Pick<VazifaQatori,
    "egasi_id" | "matn" | "bajarildi">> = {},
): Promise<VazifaQatori> {
  const egasi = ust.egasi_id ?? (await foydalanuvchi(pool)).id;
  n++;
  const bajarildi = ust.bajarildi ?? false;
  const { rows: [v] } = await pool.query<VazifaQatori>(
    `INSERT INTO vazifalar
       (egasi_id, matn, bajarildi, bajarilgan_vaqt)
     VALUES ($1, $2, $3, CASE WHEN $3 THEN now() END)
     RETURNING id, egasi_id, matn, bajarildi, tartib, versiya`,
    [egasi, ust.matn ?? `${faker.helpers.arrayElement(
      ["Hisobot", "Non olish", "Kitob o'qish", "Mashq"],
    )} ${String(n)}`, bajarildi]);
  if (v === undefined) throw new Error("vazifa yaratilmadi");
  return v;
}

E'tibor bering:

  • bajarilgan_vaqt — bazada "bajarildi bo'lsa, vaqt ham bo'lsin" degan cheklov bor: CHECK (bajarildi = (bajarilgan_vaqt IS NOT NULL)) (CREATE TABLE va cheklovlar). Fabrika buni o'zi to'g'ri qiladi: test faqat { bajarildi: true } deydi. Sxemaning bunday qoidalarini bilish — fabrikaning vazifasi, testniki emas.
  • RETURNING — fabrika baza bergan qiymatlarni (id, tartib, versiya — trigger'lardan) qaytaradi. Test ularni tekshira oladi.
  • SQL parametrli — $1, $2 (Parametrli so'rovlar va SQL injection); faker matnida ' bo'lsa ham hech narsa buzilmaydi.
  • fabrikaniBoshla() — seed va hisoblagichni nolga qaytaradi.

test/konteyner.test.ts da importni qo'shing va har beforeEach da fabrikani boshlang:

ts
import { fabrikaniBoshla, foydalanuvchi, vazifa } from "./fabrika.ts";

// "BT-11: migratsiyalar konteynerda" blokida:
beforeEach(async () => { db = await toza(); fabrikaniBoshla(); });

Fabrikani ishlatadigan testlar — keyingi darsda. Hali commit qilmang.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
duplicate key … email ba'zan tasodifiy noyob maydon ketma-ket raqam
CI'da yiqilgan testni lokalda qaytarib bo'lmaydi seed yo'q faker.seed() har test oldidan
Sxemaga ustun qo'shildi — 200 test yiqildi testlarda qo'lda INSERT bitta fabrika
Test nimani tekshirishi tushunarsiz fixture'dagi "sirli mehmon" muhim maydon testda, fabrika bilan
Testlar sekin fabrika keraksiz izoh/teg yaratadi minimal sukut
Testdan haqiqiy odamga xat ketdi faker'ning real domenlari .example domen
violates check constraint fabrikada sukut qiymat cheklovga mos emas sukutni sxema qoidalari bilan moslang

8. Mashqlar

Mashqlar kurs/mashqlar/27/64-fabrika/ da.

1-mashq (oson): to'ldiring

  • Testlar uchun oldindan yozilgan qat'iy ma'lumot —
  • Takrorlanadigan tasodif uchun faker metodi —
  • Noyob email uchun fabrika ishlatadigan narsa —
  • Hech kimga tegishli bo'lmagan domen — test.
  • O'zbek lotin lokali — faker
Yechim
  • fixture — kichik qat'iy holatlar uchun; sxema o'zgarsa qayta yoziladi.
  • seed — kanonda fabrikaniBoshla(27).
  • ketma-ket raqam — f1@, f2@…
  • example — RFC 2606 zaxira domeni.
  • UZ_UZ_latin — fakerUZ_UZ_latin.

2-mashq (o'rta): umumiy fabrika yasovchi

fabrikaYasa(sukut) funksiyasini yozing: sukut — (n) => obyekt (n — ketma-ket raqam, 1 dan). Qaytgan fabrikada build(ust) (sukut + ust birlashmasi) va buildList(soni, ust) bo'lsin; boshla() — hisoblagichni nolga. foydalanuvchiF fabrikasini (id, email: f<n>@test.example, til: "uz") yasab sinang: bitta oddiy, bitta { til: "ru" }, 2 talik ro'yxat, keyin boshla() va yana bitta.

Yechim
js
function fabrikaYasa(sukut) {
  let n = 0;
  return {
    build(ust = {}) {
      n++;
      return { ...sukut(n), ...ust };
    },
    buildList(soni, ust = {}) {
      return Array.from({ length: soni }, () => this.build(ust));
    },
    boshla() {
      n = 0;
    },
  };
}

const foydalanuvchiF = fabrikaYasa((n) => ({
  id: n,
  email: `f${n}@test.example`,
  til: "uz",
}));

console.log(foydalanuvchiF.build());
console.log(foydalanuvchiF.build({ til: "ru" }));
console.log(foydalanuvchiF.buildList(2).map((f) => f.email));
foydalanuvchiF.boshla();
console.log(foydalanuvchiF.build().id);

Konsolda:

text
{ id: 1, email: 'f1@test.example', til: 'uz' }
{ id: 2, email: 'f2@test.example', til: 'ru' }
[ 'f3@test.example', 'f4@test.example' ]
1

{ ...sukut(n), ...ust } — keyingi spread oldingisini yozib yuboradi: test bergan qiymat ustun (Spread va rest operatori). Har fabrikaning o'z hisoblagichi bor (closure). boshla() — kanondagi fabrikaniBoshla() ning vazifasi: har test bir xil raqamlardan boshlansin. fishery'dagi sequence, build, buildList — xuddi shu g'oya.

3-mashq (qiyin): bog'lanishli fabrika

Xotiradagi "baza" ({ foydalanuvchilar: [], vazifalar: [] }) bilan foydalanuvchi(db, ust) va vazifa(db, ust) fabrikalarini yozing. vazifa egasi berilmasa — o'zi foydalanuvchi yaratsin. bajarildi: true bo'lsa — bajarilganVaqt ham qo'yilsin (soxta soat: "2026-10-25T10:00:00Z"), aks holda null. Uch ssenariy: (a) vazifa(db) ikki marta — nechta foydalanuvchi? (b) bitta egaga ikki vazifa; (c) { bajarildi: true }. Har ssenariydan keyin bazadagi sonlarni chiqaring.

Yechim
js
let n = 0;
const SOAT = "2026-10-25T10:00:00Z";

function foydalanuvchi(db, ust = {}) {
  n++;
  const f = { id: db.foydalanuvchilar.length + 1,
    email: `f${n}@test.example`, ...ust };
  db.foydalanuvchilar.push(f);
  return f;
}

function vazifa(db, ust = {}) {
  const egasiId = ust.egasiId ?? foydalanuvchi(db).id;
  n++;
  const bajarildi = ust.bajarildi ?? false;
  const v = {
    id: db.vazifalar.length + 1,
    egasiId,
    matn: ust.matn ?? `Vazifa ${n}`,
    bajarildi,
    bajarilganVaqt: bajarildi ? SOAT : null,
  };
  db.vazifalar.push(v);
  return v;
}

const sanoq = (db) =>
  `foydalanuvchi ${db.foydalanuvchilar.length}, ` +
  `vazifa ${db.vazifalar.length}`;

let db = { foydalanuvchilar: [], vazifalar: [] };
vazifa(db);
vazifa(db);
console.log("a)", sanoq(db));

db = { foydalanuvchilar: [], vazifalar: [] };
const aziz = foydalanuvchi(db);
vazifa(db, { egasiId: aziz.id });
vazifa(db, { egasiId: aziz.id });
console.log("b)", sanoq(db));

db = { foydalanuvchilar: [], vazifalar: [] };
const v = vazifa(db, { bajarildi: true });
console.log("c)", sanoq(db), "|", v.bajarilganVaqt);

Konsolda:

text
a) foydalanuvchi 2, vazifa 2
b) foydalanuvchi 1, vazifa 2
c) foydalanuvchi 1, vazifa 1 | 2026-10-25T10:00:00Z

(a) — har vazifa o'z egasini yaratdi: test "egasi kim" deb qiziqmasa, bu to'g'ri va izolyatsiyalangan. (b) — test egani o'zi yaratib berdi, chunki "bir egada ikki vazifa" testning mazmuni. (c) — fabrika bog'liq maydonni (bajarilganVaqt) o'zi to'ldirdi: bazadagi cheklov hech qachon testni chalg'itmaydi. Soatni o'zgarmas qildik — natija deterministik (Vaqt va tasodifni boshqarish).

9. Real ishda

  • Rails (FactoryBot), Django (factory_boy), Laravel (model factories) — fabrika naqshi backend testlarining standarti; Node'da fishery, @mswjs/data yoki o'z funksiyalari.
  • Demo va ko'rgazma — seed skriptlari ham ko'pincha o'sha fabrikalarni ishlatadi (Seeding va test ma'lumotlari).
  • Shaxsiy ma'lumot — testlarda va staging'da haqiqiy foydalanuvchi ma'lumoti emas, faker; production nusxasi ishlatilsa — anonimlashtirish.
  • Intervyuda: "Test ma'lumotini qanday tayyorlaysiz?", "Fixture va factory farqi?", "Beqaror testlarning sabablari?"

Xulosa

  • Fabrika — testda faqat muhim maydon, qolgani yaroqli sukut; sxema o'zgarsa — bitta joyda tuzatiladi.
  • Noyob maydonlar — ketma-ket raqam; email — .example domen.
  • Bog'lanishli fabrika: egasi berilmasa o'zi yaratadi; bog'liq maydonlar (bajarilgan vaqt) fabrikada.
  • Faker uz_UZ_latin + seed — real ko'rinadigan, lekin takrorlanadigan ma'lumot (biz sinadik: seed(27) → Sadoqat Tohirova).
  • Faker tuzoqlari: ` tutuq belgisi, AQSh telefoni, haqiqiy email domenlari.

Keyingi dars: Repository va SQL'ni testlash — cheklovlar, trigger'lar va RLS'ni haqiqiy bazada tekshiramiz; nega pg-mem 15 ta migratsiyadan birortasini ham ko'tarolmadi; va holat 65 ni commit qilamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Test factory va fixture'lar: faker uzUZlatin, seed va bog'lanishli fabrikalar — IlmHamroh