11-bo‘lim
Integratsiya testlari va ma'lumotlar bazasi
Haqiqiy baza bilan test yozish, har test uchun toza sxema, xotiradagi baza, tranzaksiyani orqaga qaytarish va SQLite bilan ishlab chiqarish bazasi orasidagi farq.
Ushbu bo‘lim mundarijasi
Modul testi SQL so'rovidagi xatoni hech qachon topa olmaydi - chunki u so'rovni ishga tushirmaydi. Uni faqat haqiqiy baza topadi.
Bu bo'limda mana shunday testlarni yozamiz.
Tekshiriladigan kod #
Kutubxona bazasi bilan ishlaydigan kichik modul:
import sqlite3
SXEMA = """
CREATE TABLE kitob (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
muallif TEXT NOT NULL,
yil INTEGER NOT NULL,
band INTEGER NOT NULL DEFAULT 0
);
"""
def ulanish_och(yol):
ulanish = sqlite3.connect(yol)
ulanish.row_factory = sqlite3.Row
return ulanish
def sxemani_yarat(ulanish):
ulanish.executescript(SXEMA)
def kitob_qosh(ulanish, nom, muallif, yil):
kursor = ulanish.execute(
"INSERT INTO kitob (nom, muallif, yil) VALUES (?, ?, ?)",
(nom, muallif, yil),
)
return kursor.lastrowid
def muallif_boyicha(ulanish, muallif):
qatorlar = ulanish.execute(
"SELECT nom, yil FROM kitob WHERE muallif = ? ORDER BY yil",
(muallif,),
).fetchall()
return [dict(q) for q in qatorlar]
Har test uchun toza baza #
Integratsiya testining birinchi qoidasi - har test o'z bazasida ishlasin. Buni fixture bilan hal qilamiz:
import pytest
from baza import kitob_qosh, sxemani_yarat, ulanish_och
@pytest.fixture
def ulanish():
aloqa = ulanish_och(":memory:")
sxemani_yarat(aloqa)
yield aloqa
aloqa.close()
@pytest.fixture
def toldirilgan(ulanish):
kitob_qosh(ulanish, "O'tkan kunlar", "Abdulla Qodiriy", 1926)
kitob_qosh(ulanish, "Mehrobdan chayon", "Abdulla Qodiriy", 1929)
kitob_qosh(ulanish, "Kecha va kunduz", "Cho'lpon", 1936)
return ulanish
":memory:" - SQLite ning maxsus yo'li: baza diskda emas, xotirada
yaratiladi. Ulanish yopilishi bilan u yo'qoladi.
from baza import kitob_qosh, muallif_boyicha
def test_yangi_baza_bosh(ulanish):
assert muallif_boyicha(ulanish, "Abdulla Qodiriy") == []
def test_qoshilgan_kitob_topiladi(ulanish):
kitob_qosh(ulanish, "O'tkan kunlar", "Abdulla Qodiriy", 1926)
natija = muallif_boyicha(ulanish, "Abdulla Qodiriy")
assert natija == [{"nom": "O'tkan kunlar", "yil": 1926}]
def test_natija_yil_boyicha_tartiblanadi(toldirilgan):
natija = muallif_boyicha(toldirilgan, "Abdulla Qodiriy")
assert [q["yil"] for q in natija] == [1926, 1929]
def test_boshqa_muallif_qoshilmaydi(toldirilgan):
natija = muallif_boyicha(toldirilgan, "Cho'lpon")
assert len(natija) == 1
assert natija[0]["nom"] == "Kecha va kunduz"
pytest -q test_baza.py
.... [100%]
4 passed in 0.01s
Birinchi test muhim: u bazaning haqiqatan bo'sh ekanini tasdiqlaydi. Agar fixture ishlamayotgan bo'lsa yoki testlar bir-birining ma'lumotini ko'rayotgan bo'lsa - aynan shu test yiqiladi.
Modul testi topa olmaydigan xato #
Endi asosiy fikrni ko'rsatamiz. Kitobni band qilish alohida modulda turadi - shu bo'limda uni bir necha marta o'zgartiramiz:
def band_qil(ulanish, kitob_id):
kursor = ulanish.execute(
"UPDATE kitob SET band = 1 WHERE id = ? AND band = 0",
(kitob_id,),
)
return kursor.rowcount == 1
Shart ikki qismli: kitob shu id bilan topilsin va hali band
bo'lmasin. Ya'ni kitobni ikki marta band qilib bo'lmasligi kerak:
from band import band_qil
from baza import kitob_qosh
def test_bosh_kitob_band_qilinadi(ulanish):
kitob_id = kitob_qosh(ulanish, "O'tkan kunlar", "Abdulla Qodiriy", 1926)
assert band_qil(ulanish, kitob_id) is True
def test_band_kitob_ikkinchi_marta_band_qilinmaydi(ulanish):
kitob_id = kitob_qosh(ulanish, "O'tkan kunlar", "Abdulla Qodiriy", 1926)
band_qil(ulanish, kitob_id)
assert band_qil(ulanish, kitob_id) is False
def test_mavjud_bolmagan_kitob(ulanish):
assert band_qil(ulanish, 999) is False
pytest -q test_band.py
... [100%]
3 passed in 0.01s
Endi band_qil dan AND band = 0 shartini olib tashlaymiz - bu juda
oson qilinadigan xato:
def band_qil(ulanish, kitob_id):
kursor = ulanish.execute(
"UPDATE kitob SET band = 1 WHERE id = ?",
(kitob_id,),
)
return kursor.rowcount == 1
pytest -q --tb=line test_band.py
.F. [100%]
=============================== FAILURES ===============================
E assert True is False
+ where True = band_qil(<sqlite3.Connection object at 0x7cbef01276a0>, 1)
/home/dasturchi/loyiha/test_band.py:15: assert True is False
======================= short test summary info ========================
FAILED test_band.py::test_band_kitob_ikkinchi_marta_band_qilinmaydi
1 failed, 2 passed in 0.01s
Test xatoni tutdi. Va diqqat: bu xatoni hech qanday mock topa
olmasdi. Mock bilan yozilgan testda execute chaqirilgani
tekshirilardi - u esa chaqirilgan. Xato SQL ning o'zida, va uni faqat
haqiqiy baza ko'rsatadi.
Kodni qaytaramiz:
def band_qil(ulanish, kitob_id):
kursor = ulanish.execute(
"UPDATE kitob SET band = 1 WHERE id = ? AND band = 0",
(kitob_id,),
)
return kursor.rowcount == 1
Modul testi ko'ra olmaydigan narsalar ro'yxati uzun:
| Xato turi | Misol |
|---|---|
| SQL sintaksisi | Vergul tushib qolgan, ustun nomi noto'g'ri |
| Shart mantig'i | AND band = 0 unutilgan |
| Tartiblash | ORDER BY yo'q yoki noto'g'ri ustun bo'yicha |
| Sxema mosligi | Kod yangi ustunni kutadi, migratsiya bajarilmagan |
| Cheklovlar | NOT NULL, UNIQUE, tashqi kalit ishlayaptimi |
| Tur o'girish | Sana matn sifatida saqlangan va noto'g'ri qaytgan |
Shuning uchun har bir so'rov uchun kamida bitta integratsiya testi bo'lishi kerak.
Tezlik: xotira, fayl va qaytarish #
Uchta yondashuv bor va ular sezilarli darajada turlicha tezlikda ishlaydi:
Tranzaksiyani qaytarish usulini ko'ramiz. Bunda sxema bir marta yaratiladi, har testdan keyin esa o'zgarishlar bekor qilinadi:
import pytest
from baza import kitob_qosh, muallif_boyicha, sxemani_yarat, ulanish_och
@pytest.fixture(scope="module")
def umumiy_ulanish():
aloqa = ulanish_och(":memory:")
sxemani_yarat(aloqa)
yield aloqa
aloqa.close()
@pytest.fixture
def toza_ulanish(umumiy_ulanish):
umumiy_ulanish.execute("BEGIN")
yield umumiy_ulanish
umumiy_ulanish.rollback()
def test_birinchi_kitob_qoshadi(toza_ulanish):
kitob_qosh(toza_ulanish, "O'tkan kunlar", "Abdulla Qodiriy", 1926)
assert len(muallif_boyicha(toza_ulanish, "Abdulla Qodiriy")) == 1
def test_ikkinchi_test_bosh_bazani_koradi(toza_ulanish):
assert muallif_boyicha(toza_ulanish, "Abdulla Qodiriy") == []
def test_uchinchi_test_ham_bosh(toza_ulanish):
kitob_qosh(toza_ulanish, "Mehrobdan chayon", "Abdulla Qodiriy", 1929)
assert len(muallif_boyicha(toza_ulanish, "Abdulla Qodiriy")) == 1
pytest -q test_qaytarish.py
... [100%]
3 passed in 0.01s
Sxema bir marta yaratildi, lekin har test bo'sh jadvalni ko'rdi. Ikkinchi test aynan shuni tasdiqlaydi.
Xotiradagi SQLite tez va qulay, lekin u PostgreSQL yoki MySQL emas. Farqlar haqiqiy va ular testdan o'tib ketadi:
| Masala | SQLite | PostgreSQL |
|---|---|---|
| Ustun turlari | Deyarli maslahat | Qat'iy tekshiriladi |
VARCHAR(10) | Uzunlik cheklanmaydi | Cheklanadi |
| Tashqi kalit | Sukut bo'yicha o'chiq | Doim yoqiq |
| Sana va vaqt | Matn sifatida saqlanadi | Alohida tur |
| Bir vaqtda yozish | Butun baza qulflanadi | Qator darajasida |
RETURNING, oyna funksiyalari | Cheklangan | To'liq |
Shuning uchun: agar ishlab chiqarishda PostgreSQL bo'lsa, testda ham PostgreSQL ishlatilishi kerak. Buning eng qulay yo'li - konteynerda ko'tarish; Docker darsligi va CI/CD darsligi da bu ko'rsatilgan.
SQLite faqat ikki holatda to'g'ri keladi: ishlab chiqarishda ham SQLite ishlatilsa, yoki so'rovlaringiz butunlay standart SQL bo'lsa.
Integratsiya testini qachon yozish kerak #
| Yozing | Yozmang |
|---|---|
| Har bir SQL so'rovi uchun | Har bir funksiya uchun |
| Migratsiya to'g'ri bajarilganini | Baza kutubxonasining o'zini |
| Tranzaksiya chegaralarini | Hisob-kitob mantig'ini |
| Cheklov va indekslarni | Foydalanuvchi interfeysini |
| Ikki modul orasidagi bog'lanishni | Bitta funksiya ichidagi mantiqni |
O'ng ustundagi narsalar uchun modul testi yetarli - va u yuz baravar tez ishlaydi.
baza.pyvaconftest.pyni yozib, to'rtta testni ishga tushiring.test_yangi_baza_boshnima uchun kerakligini tushuntiring.ORDER BY yilni olib tashlang - qaysi test yiqiladi?AND band = 0ni olib tashlab, xatoni o'zingiz ko'ring.- Yangi funksiya yozing:
bosh_kitoblar(ulanish)- faqat band bo'lmaganlarini. - Unga ikkita integratsiya testi yozing.
nomustunigaUNIQUEqo'shib, takroriy kitob uchun test yozing.- Tranzaksiyani qaytarish usulini yozib, uchta testni ishga tushiring.
- Ikkinchi testni birinchisidan oldin qo'ying - natija o'zgaradimi?
- Loyihangizdagi eng murakkab SQL so'rovini toping va unga test yozing.
Xulosa #
- SQL dagi xatoni modul testi hech qachon topa olmaydi - uni faqat haqiqiy baza ko'rsatadi.
- Integratsiya testining birinchi qoidasi: har test o'z bazasida ishlasin.
- SQLite da
":memory:"yo'li bazani diskda emas, xotirada yaratadi. - Bo'sh bazani tekshiradigan test ham kerak - u izolyatsiya ishlayotganini tasdiqlaydi.
- Mock bilan yozilgan test
executechaqirilganini ko'radi, lekin so'rovning to'g'riligini ko'rmaydi. - Baza testlari tutadigan xatolar: SQL sintaksisi, shart mantig'i, tartiblash, sxema mosligi, cheklovlar, tur o'girish.
- Toza holatga uch yo'l bor: xotirada qayta yaratish, tranzaksiyani qaytarish, jadvallarni tozalash.
- Tranzaksiyani qaytarishda sxema bir marta yaratiladi, har testdan keyin
rollback()bajariladi. - Bu usul tez, lekin kod ichida
COMMITbo'lsa ishlamaydi. - SQLite PostgreSQL emas: turlar, tashqi kalitlar, sana va bir vaqtda yozish boshqacha ishlaydi.
- Ishlab chiqarishda PostgreSQL bo'lsa, testda ham PostgreSQL bo'lishi kerak - konteyner buni osonlashtiradi.
- Har SQL so'rovi uchun integratsiya testi yozing; hisob-kitob mantig'i uchun esa modul testi yetarli.
Keyingi bo'limda bir pog'ona yuqoriga chiqamiz: HTTP orqali ishlaydigan API ni testlash.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.