15 β Integratsiya testlari¶
π README Β· β¬ οΈ Oldingi: 14 β BDD va spetsifikatsiya Β· Keyingi: 16 β Ma'lumotlar bazasi va tashqi servislarni testlash β‘οΈ
Bu bobda: ikki yoki undan ko'p komponent birga to'g'ri ishlashini tekshiradigan integratsiya testlarini o'rganamiz. Unit testda har modul yakka, qo'shnilari mock edi; bu yerda modullarni real birlashtiramiz va "alohida ishlaydi, birga ishlamaydi" muammosini ochamiz. Integratsiya chegaralarini (qayerda unit tugaydi), test diametri tushunchasini, muhitni tayyorlash variantlarini (real / in-memory fake / Testcontainers) va izolyatsiya qoidasini ko'ramiz. Jonli misol β
Ombor(sqlite) +BuyurtmaXizmati+Hisobchimodullarini realsqlite3bilan birga sinash.Halollik / Eslatma: bu bob β IV qism ("integratsiya va yuqori darajadagi testlar") ochilishi va 03-bobdagi piramidaning ikkinchi qatlamini chuqurlashtiradi. Bu yerda umumiy integratsiya g'oyasini beramiz; ma'lumotlar bazasini chuqur (16-bob), HTTP/API'ni (17-bob), mikroservis kontraktlarini (18-bob) keyingi boblarga qoldiramiz. Docker va Testcontainers bu muhitda yo'q β uni kontseptual tushuntiramiz, amaliyotni esa standart kutubxonadagi
sqlite3va in-memory fake bilan jonli ko'rsatamiz. Hamma Python namunalari haqiqatan ishga tushirib, chiqishi tekshirilgan (Python 3.14,pytest9.0.3).
"Alohida ishlaydi, birga ishlamaydi"¶
Tasavvur qiling: mashina ustaxonasi dvigatelni alohida stendda sinab ko'rdi β zo'r ishlayapti. G'ildiraklarni alohida sinadi β mukammal. Vites qutisini alohida β ideal. Keyin hammasini yig'ib, mashinani yo'lga chiqarsa β vites dvigatelga ulanmaydi, chunki ikkalasining biriktirgichi (kontrakt) mos kelmaydi. Har detal to'g'ri edi, lekin birikma noto'g'ri.
Dasturda ham aynan shunday bo'ladi. Siz BuyurtmaXizmatini unit testda sinaysiz β Omborni mock qilib. Test yashil. Omborni alohida sinaysiz β u ham yashil. Lekin ishlab chiqarishda xizmat omborga topish("A1") deb murojaat qiladi-yu, ombor dict o'rniga tuple qaytaradi yoki SQL ustun nomi noto'g'ri yozilgan. Ikki modul alohida to'g'ri, lekin birga ishlamaydi.
Integratsiya testi β aynan shu bo'shliqni yopadi: ikki yoki undan ko'p komponent (modul, klass, qatlam, tizim) birga, real holatda to'g'ri ishlashini tekshiradi. Unit test "g'isht" ni, integratsiya testi "g'ishtlar orasidagi suvoq"ni sinaydi.
Asosiy g'oya: unit test komponentni izolyatsiyada (qo'shnilar soxta) tekshiradi; integratsiya testi komponentlarni birga (qo'shnilar real) tekshiradi. Mock unit testda tezlik beradi, lekin u yashirgan haqiqat β ikki modul orasidagi kontrakt β faqat integratsiyada ochiladi.
Mock nimani yashiradi¶
07β08-boblarda ko'rdik: mock qo'shnining xulqini siz aytgancha soxtalashtiradi. Bu kuch ham, zaif joy ham. Quyidagi unit test mock'langan omborga ishonadi:
from unittest.mock import MagicMock
def test_unit_mock_ombor():
soxta_ombor = MagicMock()
soxta_ombor.topish.return_value = {"kod": "A1", "narx": 100, "zaxira": 5}
xizmat = BuyurtmaXizmati(soxta_ombor, Hisobchi(soliq_foiz=12))
natija = xizmat.buyurtma_ber("A1", 2)
assert natija["summa"] == 224 # 200 + 12%
soxta_ombor.zaxira_kamaytir.assert_called_once_with("A1", 2)
# -> PASS
Bu test doim o'tadi β chunki soxta_ombor.topish siz aytgan dictni qaytaradi. Lekin haqiqiy Ombor sqlite'dan tuple qaytarsa-chi? Yoki topish umuman bunaqa metodga ega bo'lmasa? Mock buni bilmaydi va aytmaydi. Test yashil, kod buzuq. Integratsiya testi esa real omborni ulab, bu yolg'onni fosh qiladi.
Integratsiya chegaralari: qayerda unit tugaydi¶
"Integratsiya" β keng so'z. Ikki sof funksiyani birga chaqirish ham texnik jihatdan "integratsiya", lekin biz buni unit deymiz. Demak chegara qayerda? Foydali mezon β jarayon va tashqi dunyo chegarasi. Test quyidagilardan birortasini real kesib o'tsa, u integratsiya testidir:
| Chegara | Misol | Nega integratsiya |
|---|---|---|
| Ma'lumotlar bazasi | SQL, ORM, sqlite/Postgres | Sxema, so'rov, tranzaksiya real ishlashi kerak (16-bob) |
| Tashqi API / tarmoq | HTTP, gRPC | Serializatsiya, status kod, kontrakt (17, 18-bob) |
| Fayl tizimi | fayl o'qish/yozish, format | Real disk, kodlash, yo'l |
| Message queue | Kafka, RabbitMQ, SQS | Xabar formati va yetkazib berish |
| Modullar orasi | xizmat β repozitoriy | Ikki o'z koddagi modulning kontrakti |
Narrow va broad integratsiya test¶
Integratsiya testlar diametri bo'yicha ikki turga bo'linadi (Martin Fowler atamasi):
- Narrow (tor) integratsiya test β sizning kodingiz va bitta tashqi bog'liqlik (masalan, faqat
Omborβ real sqlite). Tashqi servisning o'zi yengil yoki soxta. Tez, barqaror, fokuslangan. - Broad (keng) integratsiya test β bir nechta real komponent va, ehtimol, tashqi tizimlar bir yo'lda birga ishlaydi (xizmat β DB β keshlar). Realroq, lekin sekinroq, mo'rtroq va sozlash murakkab β bu E2E'ga (19-bob) yaqinlashadi.
Maslahat: ko'p narrow integratsiya test + oz broad test yozing. Tor test buzilganini aniqlash oson (qaysi chegara buzilgani ravshan); keng test yiqilsa, sababni topish uchun ko'p joyni tekshirishingizga to'g'ri keladi.
Test diametri: qancha komponent?¶
"Test diametri" β testda qancha komponent birga ishlatilishining o'lchovi. Yakka funksiyadan (eng kichik) butun tizimgacha (eng katta) cho'ziladi. Diametr o'sgani sari ishonch ortadi, lekin tezlik tushadi va mo'rtlik oshadi.
Trade-off (03-bobni rivojlantirib): integratsiya test = realroq ishonch, chunki mock yashirgan birikma xatolarini tutadi. Ammo narxi bor: - Sekinroq β real DB/IO unit testdan o'nlab marta sekin. - Mo'rtroq (flaky) β tashqi holat, tartib, vaqt bog'liqligi flaky'likka olib keladi (24-bob). - Sozlash murakkab β DB ko'tarish, sxema yaratish, tozalash kerak.
Shu sababli piramida (03-bob): integratsiya testlar kam, lekin muhim β unit qatlamidan oz, E2E'dan ko'p. Eng tor diametrni tanlang, qaysi biri xatoni tutsa.
Jonli misol: uch modulni birlashtiramiz¶
Uchta modul bilan ishlaymiz. Ular birga buyurtma berish oqimini tashkil qiladi:
Omborβ mahsulot zaxirasinisqlite3bazasida saqlaydi (DB chegarasi).Hisobchiβ sof mantiq: narx va miqdordan yakuniy summani (soliq bilan) hisoblaydi. DB'ni bilmaydi.BuyurtmaXizmatiβ ikkovini birlashtiradi: omborni tekshiradi, hisobchidan summani oladi, zaxirani kamaytiradi.
import sqlite3
class Ombor:
def __init__(self, ulanish):
self.db = ulanish
def yarat_sxema(self):
self.db.execute(
"CREATE TABLE mahsulot ("
" kod TEXT PRIMARY KEY, nom TEXT NOT NULL,"
" narx INTEGER NOT NULL, zaxira INTEGER NOT NULL)"
)
self.db.commit()
def qoshish(self, kod, nom, narx, zaxira):
self.db.execute(
"INSERT INTO mahsulot (kod, nom, narx, zaxira) VALUES (?, ?, ?, ?)",
(kod, nom, narx, zaxira),
)
self.db.commit()
def topish(self, kod):
qator = self.db.execute(
"SELECT kod, nom, narx, zaxira FROM mahsulot WHERE kod = ?", (kod,)
).fetchone()
if qator is None:
return None
return {"kod": qator[0], "nom": qator[1], "narx": qator[2], "zaxira": qator[3]}
def zaxira_kamaytir(self, kod, miqdor):
self.db.execute(
"UPDATE mahsulot SET zaxira = zaxira - ? WHERE kod = ?", (miqdor, kod)
)
self.db.commit()
class Hisobchi: # sof mantiq, DB bilmaydi
def __init__(self, soliq_foiz=12):
self.soliq_foiz = soliq_foiz
def yakuniy_summa(self, narx, miqdor):
oraliq = narx * miqdor
return oraliq + oraliq * self.soliq_foiz // 100
class ZaxiraYetarliEmas(Exception):
pass
class BuyurtmaXizmati: # ikkovini BIRLASHTIRADI
def __init__(self, ombor, hisobchi):
self.ombor = ombor
self.hisobchi = hisobchi
def buyurtma_ber(self, kod, miqdor):
mahsulot = self.ombor.topish(kod)
if mahsulot is None:
raise KeyError(f"mahsulot topilmadi: {kod}")
if mahsulot["zaxira"] < miqdor:
raise ZaxiraYetarliEmas(f"{kod}: {miqdor} so'raldi, {mahsulot['zaxira']} bor")
summa = self.hisobchi.yakuniy_summa(mahsulot["narx"], miqdor)
self.ombor.zaxira_kamaytir(kod, miqdor)
return {"kod": kod, "miqdor": miqdor, "summa": summa}
Integratsiya testi: real sqlite bilan¶
Endi mock yo'q. Ombor haqiqiy sqlite3 bazasiga ulanadi, Hisobchi real, va BuyurtmaXizmati ularni real birlashtiradi. pytest fixture'i har test uchun toza in-memory baza beradi:
import sqlite3, pytest
def yangi_ombor(yol=":memory:"):
ombor = Ombor(sqlite3.connect(yol))
ombor.yarat_sxema()
return ombor
@pytest.fixture
def xizmat():
ombor = yangi_ombor() # haqiqiy in-memory sqlite
ombor.qoshish("A1", "Daftar", 100, 5)
return BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
def test_integ_buyurtma_zaxirani_kamaytiradi(xizmat):
natija = xizmat.buyurtma_ber("A1", 2) # Act
assert natija["summa"] == 224 # 200 + 12%
# Endi DB'dan O'QIB tasdiqlaymiz β mock yolg'on gapira olmaydi:
assert xizmat.ombor.topish("A1")["zaxira"] == 3
# -> PASS
Diqqat qiling: oxirgi assert omborga qaytib o'qiydi. Unit testda mock shunchaki "men chaqirildim" deb javob berardi (assert_called_once_with); bu yerda biz haqiqiy yozuvni β INSERT keyin UPDATE keyin SELECT zanjiri to'g'ri ishlaganini β real DB'dan tasdiqlaymiz. Bu integratsiyaning butun mohiyati.
Istisno yo'li ham real ishlashi kerak β zaxira yetmasa, DB o'zgarmasligi lozim:
def test_integ_zaxira_yetarli_emas(xizmat):
with pytest.raises(ZaxiraYetarliEmas):
xizmat.buyurtma_ber("A1", 99)
assert xizmat.ombor.topish("A1")["zaxira"] == 5 # o'zgarmadi
# -> PASS
Fayl-asosli (in-memory emas) real sqlite bilan ham β bir ulanishda yozib, boshqa ulanishda o'qib, ma'lumot haqiqatan diskka yozilganini tasdiqlash mumkin:
def test_integ_fayl_sqlite(tmp_path):
yol = tmp_path / "savdo.db"
ombor = yangi_ombor(str(yol))
ombor.qoshish("B2", "Ruchka", 50, 10)
xizmat = BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
xizmat.buyurtma_ber("B2", 4)
# YANGI ulanish bilan o'sha faylni qayta ochib, yozilganini tasdiqlaymiz:
boshqa = Ombor(sqlite3.connect(str(yol)))
assert boshqa.topish("B2")["zaxira"] == 6
# -> PASS
Butun paket haqiqiy pytest chiqishi:
test_fake.py . [ 12%]
test_integ.py ..... [ 75%]
test_kontrakt_buzilishi.py .. [100%]
============================== 8 passed in 0.60s ==============================
Integratsiya tutadi, unit tutmaydi: kontrakt buzilishi¶
Endi eng muhim qismi β mock yashirgan, integratsiya ochgan xatoni jonli ko'ramiz. Faraz qiling, dasturchi zaxira_kamaytirda SQL ustun nomini xato yozdi: jadvalda ustun zaxira, lekin so'rovda qoldiq deb yozilgan:
class XatoOmbor:
def __init__(self, ulanish):
self.db = ulanish
def yarat_sxema(self):
self.db.execute("CREATE TABLE mahsulot (kod TEXT PRIMARY KEY, zaxira INTEGER)")
self.db.commit()
def qoshish(self, kod, zaxira):
self.db.execute("INSERT INTO mahsulot (kod, zaxira) VALUES (?, ?)", (kod, zaxira))
self.db.commit()
def zaxira_kamaytir(self, kod, miqdor):
# XATO: 'qoldiq' degan ustun sxemada YO'Q (to'g'risi 'zaxira').
self.db.execute("UPDATE mahsulot SET qoldiq = qoldiq - ? WHERE kod = ?",
(miqdor, kod))
self.db.commit()
Unit test (mock bilan) bu xatoni ko'rmaydi β chunki mock haqiqiy SQL ishlatmaydi:
def test_unit_mock_xatoni_yashiradi():
soxta = MagicMock() # haqiqiy SQL umuman ishlamaydi
soxta.zaxira_kamaytir("A1", 1)
soxta.zaxira_kamaytir.assert_called_once_with("A1", 1)
# -> PASS (lekin xato hali kodda turibdi!)
Integratsiya test (real sqlite) esa xatoni darhol ochadi:
def test_integ_xatoni_ochadi():
ombor = XatoOmbor(sqlite3.connect(":memory:"))
ombor.yarat_sxema()
ombor.qoshish("A1", 5)
with pytest.raises(sqlite3.OperationalError): # "no such column: qoldiq"
ombor.zaxira_kamaytir("A1", 1)
# -> PASS (integratsiya kontrakt buzilishini TUTDI)
Real DB'da chaqirilganda aniq xato: sqlite3.OperationalError: no such column: qoldiq. Mana shu β integratsiya testi tutadigan, unit testi printsipial tuta olmaydigan xato sinfi: kod va tashqi tizim (DB sxemasi) orasidagi kontrakt buzilishi. Bunday moslikni mikroservislararo formal tekshirish 18-bob: Kontrakt testlarda chuqurlashtiriladi.
Eslatma: bu mock'ning yomonligini bildirmaydi. Mock unit testni tez va fokuslangan qiladi (xizmatning mantiqini sinaydi). Muammo β faqat mock'larga tayanish: u holda hech bir test real birikmani tekshirmaydi. To'g'ri yondashuv β ikkalasi: unit (mantiq, tez) + integratsiya (birikma, realroq).
Test muhitini tayyorlash: real vs fake vs Testcontainers¶
Integratsiya testida tashqi bog'liqlikni qanday ta'minlaysiz? Uchta asosiy yo'l bor, har birining o'rni boshqa.
In-memory fake (tez, kamroq realistik)¶
Fake β bog'liqlikning ishlaydigan, lekin soddalashtirilgan implementatsiyasi (07-bobdagi taksonomiyada "fake"). Mock'dan farqi: fake haqiqatan ishlaydi (real holatni saqlaydi), faqat soddaroq (xotirada, diskka tegmasdan). Bizning Omborning dict-asosli faki:
class FakeOmbor:
def __init__(self):
self._mahsulotlar = {}
def qoshish(self, kod, nom, narx, zaxira):
self._mahsulotlar[kod] = {"kod": kod, "nom": nom, "narx": narx, "zaxira": zaxira}
def topish(self, kod):
return self._mahsulotlar.get(kod)
def zaxira_kamaytir(self, kod, miqdor):
self._mahsulotlar[kod]["zaxira"] -= miqdor
def test_fake_ombor_bilan_xizmat():
ombor = FakeOmbor()
ombor.qoshish("A1", "Daftar", 100, 5)
xizmat = BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
natija = xizmat.buyurtma_ber("A1", 2)
assert natija["summa"] == 224
assert ombor.topish("A1")["zaxira"] == 3 # fake real holatni saqlaydi
# -> PASS
BuyurtmaXizmati kodi umuman o'zgarmaydi β u faqat topish/zaxira_kamaytir kontraktiga tayanadi, kim bajarishidan qat'i nazar. Bu β fake'ning kuchi: tez (DB yo'q), lekin real xulq (holat saqlanadi). Zaifligi: u real SQL/sxemani sinamaydi β yuqoridagi qoldiq xatosini fake ham tutmaydi.
Real bog'liqlik (realistik, sekinroq)¶
Yuqorida ko'rganimiz β real sqlite3. SQL haqiqatan bajariladi, sxema tekshiriladi, kontrakt buzilishi ushlanadi. sqlite3 Python standart kutubxonasida, hech narsa o'rnatmaysiz va in-memory rejimida (:memory:) deyarli fake'dek tez. Kamchiligi: prod'da Postgres/MySQL ishlatsangiz, sqlite ulardan ozgina farq qiladi (tip tizimi, SQL dialekti) β shuning uchun u prod DB'ning to'liq o'rnini bosmaydi.
Testcontainers (eng realistik, Docker kerak) β kontseptual¶
Eng yuqori ishonch uchun aynan prod'dagi DB'ni (real Postgres) test paytida ko'tarish kerak. Testcontainers kutubxonasi shuni qiladi: test boshlanganda Docker konteynerda real Postgres ko'taradi, test tugaganda o'chiradi. Shunday ko'rinadi (eskiz β bu muhitda Docker yo'q, ishga tushirmaymiz):
# KONSEPTUAL eskiz β Docker talab qiladi, bu yerda ishlatilmaydi.
# pip install testcontainers[postgres]
from testcontainers.postgres import PostgresContainer
import psycopg
def test_real_postgres():
with PostgresContainer("postgres:18") as pg: # konteyner ko'tariladi
ulanish = psycopg.connect(pg.get_connection_url())
ombor = Ombor(ulanish)
ombor.yarat_sxema()
# ... aynan prod'dagi Postgres'da integratsiya testi ...
# blok tugagach konteyner avtomatik o'chadi
Trade-off jadvali β qaysi muhitni tanlash:
Variant Tezlik Realistiklik Sozlash Qachon In-memory fake β‘ eng tez past (SQL/sxema yo'q) oson Xizmat mantiqini tez sinash; minglab test Real (sqlite) tez o'rtaβyuqori (real SQL) oson Repozitoriy/so'rovlarni sinash; standart tanlov Testcontainers sekin (Docker start) eng yuqori (prod DB) murakkab (Docker, CI) DB'ga xos xulq (tranzaksiya, tip, indeks) muhim bo'lganda Amalda ko'pincha ikkalasi: lokal/tez fikr uchun fake yoki sqlite, CI'da (27-bob) Testcontainers bilan prod DB'da. Til-mustaqil: Testcontainers JS, PHP, Java, Go'da ham mavjud β g'oya bir xil.
Test izolyatsiyasi: har test toza holatdan¶
Integratsiyada eng katta tuzoq β umumiy holat (shared state). Real DB testdan testga ma'lumotni saqlaydi. Agar 1-test mahsulot qo'shsa va 2-test uni kutmasa, 2-test boshqa testning iflosligidan yiqiladi (yoki yolg'ondan o'tadi). Bu 04-bobdagi "Independent" (mustaqillik) tamoyilining buzilishi va flaky'likning (24-bob) keng tarqalgan sababi.
Yechim: har integratsiya testi toza holatdan boshlasin. Bizning fixture'imiz buni avtomatik beradi β har test uchun yangi in-memory baza yaratiladi:
def test_integ_har_test_toza_holatdan(xizmat):
# Oldingi test 2 dona sotgan bo'lsa ham, bu test toza zaxira (5) ko'radi:
assert xizmat.ombor.topish("A1")["zaxira"] == 5
# -> PASS
@pytest.fixture har testga qaytadan chaqiriladi, demak har test yangi :memory: baza, yangi seed bilan boshlanadi β testlar bir-biriga sizib o'tmaydi.
Toza holatni ta'minlashning umumiy strategiyalari:
| Strategiya | Qanday | Tezlik |
|---|---|---|
| Yangi baza har testga | Bizdagidek :memory: qayta yaratish |
Tez (sqlite) |
| Tranzaksiya + rollback | Test boshida tranzaksiya, oxirida bekor | Eng tez (real DB) |
| Jadvallarni tozalash | Har testdan oldin DELETE/TRUNCATE |
O'rta |
Diqqat: real (fayl/server) DB bilan bitta umumiy bazani ko'p testga ulashish β eng tez ko'rinadi, lekin eng xavfli. Test tartibi o'zgarsa (
pytestrandom tartibda yoki parallel ishlatsa), testlar bir-biriga aralashadi. Tranzaksiya-rollback yondashuvi β chuqur 16-bobda; hozircha qoidani eslang: har test o'z toza holatini o'zi yaratsin.
Piramidadagi o'rni: kam, lekin muhim¶
03-bobdagi piramidada integratsiya β o'rta qatlam: unit'dan kam, E2E'dan ko'p. Nega kam? Chunki har integratsiya test sekinroq va mo'rtroq. Nega muhim? Chunki ular unit tutmaydigan xato sinfini β birikma/kontrakt xatolarini β tutadi.
Qachon ko'proq integratsiya kerak:
- Mikroservis arxitektura β tizim ko'p mustaqil xizmatdan iborat; ko'pchilik xato xizmatlar orasida, ichida emas. (Arxitektura: ../arxitektura/README.md.)
- Ko'p IO β DB, fayl, tashqi API ko'p bo'lgan tizim (masalan ma'lumot quvuri). Mantiq oddiy, lekin integratsiya nozik.
- Yupqa mantiq qatlami β agar koddagi mantiq oz, asosiy ish DB/tashqi servisda bo'lsa, ko'p unit test ozgina foyda beradi; integratsiya ishonchni ko'taradi.
Qachon kamroq: murakkab biznes mantiqli, lekin oz IO'li tizim (masalan hisob-kitob, qoidalar dvigateli). Bu yerda mantiqning ko'p qismi sof funksiya β unit test arzon va kuchli.
Asosiy g'oyalar (bobni qisqacha)¶
- Integratsiya testi ikki yoki undan ko'p komponent birga to'g'ri ishlashini tekshiradi; unit yakka modulni (qo'shnilar mock), integratsiya esa modullarni real birga sinaydi.
- "Alohida ishlaydi, birga ishlamaydi" β har modul to'g'ri, lekin birikma (kontrakt) noto'g'ri. Mock buni yashiradi, integratsiya ochadi.
- Integratsiya chegarasi β test real kesib o'tadigan tashqi nuqta: DB, tashqi API, fayl, message queue, modullar orasi.
- Narrow vs broad: ko'p tor test (kod + bitta bog'liqlik, fokuslangan) + oz keng test (ko'p komponent, E2E'ga yaqin) yozing.
- Test diametri β qancha komponent qamralgani. Katta diametr = realroq ishonch, lekin sekinroq, mo'rtroq, murakkabroq sozlash (trade-off).
- Muhit tanlovi: in-memory fake (tez, kamroq realistik) Β· real sqlite (tez, real SQL, standart tanlov) Β· Testcontainers (prod DB, eng realistik, Docker kerak β kontseptual).
- Izolyatsiya shart: har integratsiya test toza holatdan boshlasin (yangi baza / tranzaksiya-rollback / tozalash) β umumiy holat flaky'likka olib keladi.
- Piramidada kam, lekin muhim; mikroservis va ko'p-IO tizimlarda ko'proq integratsiya kerak.
Mashqlar¶
Oson¶
1-mashq. Quyidagi to'rt testdan qaysilari unit, qaysilari integratsiya ekanini ayting va sababini yozing: (a) sof yakuniy_summa(100, 2) funksiyasini tekshiradi; (b) real sqlite omborga yozib, qayta o'qiydi; (c) MagicMock ombor bilan xizmatni sinaydi; (d) xizmat β real DB β o'qib tasdiqlaydi.
2-mashq. Hisobchi va BuyurtmaXizmatini real FakeOmbor bilan birlashtirib, "zaxira aniq yetarli" (zaxira = so'ralgan miqdor) holatini sinaydigan integratsiya testi yozing. Test buyurtmadan keyin zaxira 0 ekanini tasdiqlasin.
3-mashq. Nima uchun test_unit_mock_ombor testi Ombordagi SQL ustun nomi xatosini (qoldiq) tuta olmaydi? Bir jumlada tushuntiring.
O'rta¶
4-mashq. pytest fixture yozing: har test uchun yangi in-memory sqlite Ombor yaratib, ikkita mahsulot ("A1" zaxira 5, "B2" zaxira 0) bilan seed qilsin. Shu fixture bilan ikki test yozing: "A1" buyurtmasi muvaffaqiyatli, "B2" buyurtmasi ZaxiraYetarliEmas chiqaradi.
5-mashq. Test izolyatsiyasi buzilgan stsenariy: ikki integratsiya test bitta umumiy Ombor obyektidan foydalansa (fixture'siz, modul darajasida yaratilsa) qanday muammo yuzaga keladi? Tartib o'zgarganda nima bo'ladi? Yechimini ayting.
6-mashq. BuyurtmaXizmatiga shunday integratsiya testi yozingki, u ikki ketma-ket buyurtmani tekshirsin ("A1" zaxira 5: avval 2 dona, keyin 2 dona) va oxirida real DB'dan zaxira 1 ekanini tasdiqlasin. Bu test nimani isbotlaydi (unit test isbotlay olmaydigan)?
Qiyin¶
7-mashq. Jamoadosh: "Hamma integratsiya testni Testcontainers bilan real Postgres'da yozaylik β eng realistik-ku." Bu fikrning trade-offlarini yozing: qachon to'g'ri, qachon ortiqcha? Qaysi holatda in-memory fake yoki sqlite afzal? Tezlik, CI murakkabligi va piramida nuqtai nazaridan baholang.
8-mashq. FakeOmbor (in-memory) va real sqlite3 Ombor ikkalasi ham topish/zaxira_kamaytir kontraktini bajaradi. Bitta parametrlangan test yozingki (06-bobdagi parametrize bilan), u ikkala implementatsiyada ham bir xil integratsiya stsenariysini sinasin. Bu nima foyda beradi va qaysi xato sinfini fake versiya baribir tutmaydi?
Yechimlar
1-mashq yechimi¶
- (a) Unit. Yakka sof funksiya, hech qanday tashqi chegara kesilmaydi.
- (b) Integratsiya. Real DB chegarasi kesiladi (sqlite'ga yozish/o'qish).
- (c) Unit. Ombor mock β real bog'liqlik yo'q, faqat xizmat mantig'i izolyatsiyada sinaladi.
- (d) Integratsiya. Xizmat + real DB birga ishlaydi va natija DB'dan tasdiqlanadi.
2-mashq yechimi¶
def test_integ_zaxira_aniq_yetarli():
ombor = FakeOmbor()
ombor.qoshish("A1", "Daftar", 100, 3)
xizmat = BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
xizmat.buyurtma_ber("A1", 3) # zaxira = so'ralgan miqdor
assert ombor.topish("A1")["zaxira"] == 0
# -> PASS
3-mashq yechimi¶
Mock haqiqiy SQL'ni umuman bajarmaydi β zaxira_kamaytir siz aytgancha (hech narsa) "ishlaydi", shuning uchun noto'g'ri ustun nomi (qoldiq) hech qachon real bazaga urilmaydi va xato yuzaga kelmaydi.
4-mashq yechimi¶
import pytest
@pytest.fixture
def xizmat2():
ombor = yangi_ombor()
ombor.qoshish("A1", "Daftar", 100, 5)
ombor.qoshish("B2", "Ruchka", 50, 0)
return BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
def test_integ_a1_muvaffaqiyatli(xizmat2):
natija = xizmat2.buyurtma_ber("A1", 2)
assert natija["summa"] == 224
assert xizmat2.ombor.topish("A1")["zaxira"] == 3
# -> PASS
def test_integ_b2_zaxira_yoq(xizmat2):
with pytest.raises(ZaxiraYetarliEmas):
xizmat2.buyurtma_ber("B2", 1)
# -> PASS
5-mashq yechimi¶
Agar ikki test bitta umumiy Omborni baham ko'rsa, umumiy holat muammosi yuzaga keladi: 1-test zaxirani kamaytirsa, 2-test endi toza emas, "iflos" holatni ko'radi. pytest testlarni odatda fayl tartibida, lekin parallel/random tartibda ham ishlatishi mumkin β tartib o'zgarsa, natija o'zgaradi va test flaky bo'ladi (goh o'tadi, goh yiqiladi). Yechim: fixture orqali har testga yangi baza berish (yoki tranzaksiya-rollback), shunda testlar mustaqil bo'ladi (04-bob "Independent").
6-mashq yechimi¶
def test_integ_ikki_ketma_ket_buyurtma(xizmat):
xizmat.buyurtma_ber("A1", 2) # 5 -> 3
xizmat.buyurtma_ber("A1", 2) # 3 -> 1
assert xizmat.ombor.topish("A1")["zaxira"] == 1
# -> PASS
Bu test holatning to'planishini isbotlaydi: birinchi UPDATE natijasi DB'da saqlanib, ikkinchi buyurtma uni hisobga oladi. Mock'li unit test buni isbotlay olmaydi β chunki mock haqiqiy holatni saqlamaydi (har chaqiruvda siz bergan qiymatni qaytaradi, oldingi UPDATEni "eslamaydi").
7-mashq yechimi¶
Qachon to'g'ri: prod'da Postgres ishlatilsa va testda DB'ga xos xulq muhim bo'lsa β masalan tranzaksiya izolyatsiya darajalari, aniq tip tizimi, indeks/cheklov xulqi, dialektga xos SQL. Bunda sqlite yetarli emas, Testcontainers prod'dagi DB'ni aynan beradi.
Qachon ortiqcha: - Tezlik: Testcontainers har test sessiyasida Docker konteyner ko'taradi β sekin start. Minglab test uchun bu fikr-mulohaza halqasini (27-bob) sekinlashtiradi. - CI murakkabligi: Docker mavjudligini talab qiladi; ba'zi CI muhitida sozlash murakkab. - Piramida: integratsiya testlar kam bo'lishi kerak. Xizmat mantiqini sinash uchun fake/sqlite yetarli va tez; faqat DB-ga xos xulqni sinaydigan oz testda Testcontainers ishlating.
Afzal qachon fake/sqlite: mantiq-og'ir, DB-ga xos xulq muhim bo'lmagan testlar uchun in-memory fake (eng tez) yoki sqlite (real SQL, tez). Amalda: ko'p fake/sqlite + oz Testcontainers.
8-mashq yechimi¶
import sqlite3, pytest
def make_fake():
o = FakeOmbor(); o.qoshish("A1", "Daftar", 100, 5); return o
def make_sqlite():
o = yangi_ombor(); o.qoshish("A1", "Daftar", 100, 5); return o
@pytest.mark.parametrize("ombor_yarat", [make_fake, make_sqlite],
ids=["fake", "sqlite"])
def test_integ_ikkala_implementatsiya(ombor_yarat):
ombor = ombor_yarat()
xizmat = BuyurtmaXizmati(ombor, Hisobchi(soliq_foiz=12))
natija = xizmat.buyurtma_ber("A1", 2)
assert natija["summa"] == 224
assert ombor.topish("A1")["zaxira"] == 3
# -> PASS (2 ta: fake va sqlite)
Foydasi: bir xil kontrakt testi ikki implementatsiyaga ham qo'llanadi β fake'ning real sqlite bilan bir xil xulqda ekanini tasdiqlaydi (fake "ishonchli o'rinbosar"ligini sinaydi). Bu β kontrakt testlarining (18-bob) sodda ko'rinishi.
Fake baribir tutmaydigan xato sinfi: real SQL/sxema xatolari β noto'g'ri ustun nomi (qoldiq), tip mosligi, cheklov buzilishi. Fake Python dict ustida ishlaydi, SQL bajarmaydi; shuning uchun bunday xatolarni faqat real sqlite (yoki Testcontainers) versiyasi tutadi. Aynan shu sabab β fake yagona muhit bo'la olmaydi.
π README Β· β¬ οΈ Oldingi: 14 β BDD va spetsifikatsiya Β· Keyingi: 16 β Ma'lumotlar bazasi va tashqi servislarni testlash β‘οΈ