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.

🕑 12 daqiqa o‘qish 📄 819 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Tekshiriladigan kod
  2. Har test uchun toza baza
  3. Modul testi topa olmaydigan xato
  4. Tezlik: xotira, fayl va qaytarish
  5. Integratsiya testini qachon yozish kerak
  6. Xulosa

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:

Python
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:

Python
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.

Python
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"
Terminal
pytest -q test_baza.py
Natija
....                                                             [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:

Python
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:

Python
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
Terminal
pytest -q test_band.py
Natija
...                                                              [100%]
3 passed in 0.01s

Endi band_qil dan AND band = 0 shartini olib tashlaymiz - bu juda oson qilinadigan xato:

Python
def band_qil(ulanish, kitob_id):
    kursor = ulanish.execute(
        "UPDATE kitob SET band = 1 WHERE id = ?",
        (kitob_id,),
    )
    return kursor.rowcount == 1
Terminal
pytest -q --tb=line test_band.py
Natija
.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:

Python
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
Baza testlari qaysi xatolarni tutadi

Modul testi ko'ra olmaydigan narsalar ro'yxati uzun:

Xato turiMisol
SQL sintaksisiVergul tushib qolgan, ustun nomi noto'g'ri
Shart mantig'iAND band = 0 unutilgan
TartiblashORDER BY yo'q yoki noto'g'ri ustun bo'yicha
Sxema mosligiKod yangi ustunni kutadi, migratsiya bajarilmagan
CheklovlarNOT NULL, UNIQUE, tashqi kalit ishlayaptimi
Tur o'girishSana 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:

Har test uchun toza bazani qanday olish mumkin Xotirada qayta yaratish Har test uchun yangi baza va sxema + eng sodda, eng toza - sxema katta bo'lsa sekin Tranzaksiyani qaytarish Sxema bir marta, har testdan keyin ROLLBACK + tez, haqiqiy bazada ham - kod COMMIT qilsa buziladi Jadvallarni tozalash Har testdan oldin TRUNCATE yoki DELETE + COMMIT bilan ham ishlaydi - jadval tartibini bilish kerak Kichik loyihada birinchisi yetadi; sxema o'sgach ikkinchisiga o'tiladi Uchalasida ham maqsad bitta: test boshlanganda baza ma'lum bir holatda bo'lsin
Toza holat - integratsiya testining asosiy talabi

Tranzaksiyani qaytarish usulini ko'ramiz. Bunda sxema bir marta yaratiladi, har testdan keyin esa o'zgarishlar bekor qilinadi:

Python
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
Terminal
pytest -q test_qaytarish.py
Natija
...                                                              [100%]
3 passed in 0.01s

Sxema bir marta yaratildi, lekin har test bo'sh jadvalni ko'rdi. Ikkinchi test aynan shuni tasdiqlaydi.

SQLite ishlab chiqarish bazangizga teng emas

Xotiradagi SQLite tez va qulay, lekin u PostgreSQL yoki MySQL emas. Farqlar haqiqiy va ular testdan o'tib ketadi:

MasalaSQLitePostgreSQL
Ustun turlariDeyarli maslahatQat'iy tekshiriladi
VARCHAR(10)Uzunlik cheklanmaydiCheklanadi
Tashqi kalitSukut bo'yicha o'chiqDoim yoqiq
Sana va vaqtMatn sifatida saqlanadiAlohida tur
Bir vaqtda yozishButun baza qulflanadiQator darajasida
RETURNING, oyna funksiyalariCheklanganTo'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 #

YozingYozmang
Har bir SQL so'rovi uchunHar bir funksiya uchun
Migratsiya to'g'ri bajarilganiniBaza kutubxonasining o'zini
Tranzaksiya chegaralariniHisob-kitob mantig'ini
Cheklov va indekslarniFoydalanuvchi interfeysini
Ikki modul orasidagi bog'lanishniBitta funksiya ichidagi mantiqni

O'ng ustundagi narsalar uchun modul testi yetarli - va u yuz baravar tez ishlaydi.

Amaliy topshiriq
  1. baza.py va conftest.py ni yozib, to'rtta testni ishga tushiring.
  2. test_yangi_baza_bosh nima uchun kerakligini tushuntiring.
  3. ORDER BY yil ni olib tashlang - qaysi test yiqiladi?
  4. AND band = 0 ni olib tashlab, xatoni o'zingiz ko'ring.
  5. Yangi funksiya yozing: bosh_kitoblar(ulanish) - faqat band bo'lmaganlarini.
  6. Unga ikkita integratsiya testi yozing.
  7. nom ustuniga UNIQUE qo'shib, takroriy kitob uchun test yozing.
  8. Tranzaksiyani qaytarish usulini yozib, uchta testni ishga tushiring.
  9. Ikkinchi testni birinchisidan oldin qo'ying - natija o'zgaradimi?
  10. 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 execute chaqirilganini 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 COMMIT bo'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.

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.