17-bo‘lim

Xossaviy testlash

Misollar o'rniga qoidalarni tekshirish, Hypothesis bilan kirishlarni avtomatik yaratish, qisqartirish (shrinking) va qaysi xossalarni izlash kerak.

🕑 16 daqiqa o‘qish 📄 897 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Misolning chegarasi
  2. Qoidani yozish
  3. Birinchi urinish: maxsus holat
  4. Ikkinchi urinish: tugatuvchi va qochirish
  5. Topilgan misolni muhrlab qo'yish
  6. Qaysi xossalarni izlash kerak
  7. Strategiyalar
  8. Xulosa

Shu paytgacha biz misollar yozdik: kirish shu bo'lsa, chiqish shu bo'lsin. Har misolni qo'limiz bilan tanladik.

Xossaviy testlashda esa boshqacha: siz qoidani yozasiz, kirishlarni esa kompyuter o'zi o'ylab topadi. Yuzlab kirishni.

Misolning chegarasi #

Ro'yxatni matnga aylantirib, keyin qaytaradigan ikkita funksiya:

Python
def kodla(qatorlar):
    """Ro'yxatni bitta satrga aylantiradi."""
    return ";".join(qatorlar)


def dekodla(matn):
    """Satrni ro'yxatga qaytaradi."""
    return matn.split(";")

Misollar bilan yozilgan test:

Python
from kodlash import dekodla, kodla


def test_uchta_element():
    assert dekodla(kodla(["olma", "anor", "uzum"])) == ["olma", "anor", "uzum"]


def test_bitta_element():
    assert dekodla(kodla(["olma"])) == ["olma"]


def test_uzbekcha_harflar():
    assert dekodla(kodla(["shaftoli", "o'rik"])) == ["shaftoli", "o'rik"]
Terminal
pytest -q -p no:randomly test_misol.py
Natija
...                                                              [100%]
3 passed in 0.01s

Uchta test yashil. Kod to'g'ri ko'rinadi.

Qoidani yozish #

Endi misol emas, qoida yozamiz: qanday ro'yxat bo'lmasin, uni kodlab, keyin dekodlasak - o'zi qaytishi kerak.

Terminal
pip install hypothesis
Natija
Collecting hypothesis
  Downloading hypothesis-6.168.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.7 kB)
Collecting sortedcontainers<3.0.0,>=2.1.0 (from hypothesis)
  Downloading sortedcontainers-2.4.0-py2.py3-none-any.whl.metadata (10 kB)
Downloading hypothesis-6.168.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 12.0 MB/s  0:00:00
Downloading sortedcontainers-2.4.0-py2.py3-none-any.whl (29 kB)
Installing collected packages: sortedcontainers, hypothesis

Successfully installed hypothesis-6.168.0 sortedcontainers-2.4.0
Python
from hypothesis import given, settings
from hypothesis import strategies as st

from kodlash import dekodla, kodla


@settings(derandomize=True)
@given(st.lists(st.text()))
def test_kodlab_dekodlaganda_ozi_qaytadi(qatorlar):
    assert dekodla(kodla(qatorlar)) == qatorlar
Terminal
pytest -q -p no:randomly --tb=line test_xossa.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
E   AssertionError: assert [''] == []
      
      Left contains one more item: ''
      Use -v to get more diff
    Failing test case: test_kodlab_dekodlaganda_ozi_qaytadi(
        qatorlar=[],
    )
/home/dasturchi/loyiha/test_xossa.py:10: AssertionError: assert [''] == []
======================= short test summary info ========================
FAILED test_xossa.py::test_kodlab_dekodlaganda_ozi_qaytadi - Assertio...
1 failed in 1.88s

Hypothesis xatoni topdi - va topgan misoli eng kichigi: bo'sh ro'yxat.

kodla([]) bo'sh satr beradi, dekodla("") esa [""] qaytaradi - ichida bitta bo'sh element bo'lgan ro'yxat. Uchta qo'lda yozilgan misolimiz bu holatni umuman ko'rmagan.

Qisqartirish (shrinking) - Hypothesis ning asosiy kuchi

Hypothesis birinchi urinishda odatda murakkab misol topadi: uzun ro'yxat, g'alati belgilar bilan.

Keyin u qisqartirish bosqichiga o'tadi: misolni asta-sekin soddalashtirib, xato hamon takrorlanishini tekshiradi. Natijada siz o'qiy oladigan eng kichik misolni olasiz - bu yerda [].

Aynan shuning uchun xossaviy testlash foydali: u nafaqat xato topadi, balki uni tushunarli shaklda ko'rsatadi.

derandomize=True - har ishga tushirishda bir xil misollar yaratilsin degani. Uni yozmasangiz, Hypothesis har safar yangi kirishlar sinaydi va ba'zan boshqa xato topadi.

Birinchi urinish: maxsus holat #

Tabiiy javob - bo'sh ro'yxatni alohida qarash:

Python
def kodla(qatorlar):
    """Ro'yxatni bitta satrga aylantiradi."""
    if not qatorlar:
        return ""
    return ";".join(qatorlar)


def dekodla(matn):
    """Satrni ro'yxatga qaytaradi."""
    if matn == "":
        return []
    return matn.split(";")
Terminal
pytest -q -p no:randomly --tb=line test_xossa.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
E   AssertionError: assert [] == ['']
      
      Right contains one more item: ''
      Use -v to get more diff
    Failing test case: test_kodlab_dekodlaganda_ozi_qaytadi(
        qatorlar=[''],
    )
/home/dasturchi/loyiha/test_xossa.py:10: AssertionError: assert [] == ['']
======================= short test summary info ========================
FAILED test_xossa.py::test_kodlab_dekodlaganda_ozi_qaytadi - Assertio...
1 failed in 0.60s

Hypothesis darhol keyingi misolni topdi: [''] - ichida bitta bo'sh satr bo'lgan ro'yxat. Uni kodlasak bo'sh satr chiqadi, dekodlaganda esa endi bo'sh ro'yxat qaytadi.

Va bu tasodif emas. Muammo chuqurroqda: ajratuvchi belgi "elementlar yo'q" va "bitta bo'sh element" holatlarini farqlay olmaydi - ikkalasi ham bo'sh satrga aylanadi.

Bundan tashqari yana bitta xato bor, Hypothesis unga hali yetib kelmadi: element ichida ; belgisi bo'lsa nima bo'ladi?

Ikkinchi urinish: tugatuvchi va qochirish #

Ikkala muammoni ham hal qiladigan yechim - ajratuvchi o'rniga tugatuvchi ishlatish va maxsus belgilarni qochirish (escaping):

Python
QOCHIRISH = "\\"
TUGATUVCHI = ";"


def kodla(qatorlar):
    """Har elementdan keyin ';' qo'yadi; element ichidagi ';' qochiriladi."""
    bolaklar = []
    for qator in qatorlar:
        qochirilgan = qator.replace(QOCHIRISH, QOCHIRISH * 2)
        qochirilgan = qochirilgan.replace(TUGATUVCHI, QOCHIRISH + TUGATUVCHI)
        bolaklar.append(qochirilgan + TUGATUVCHI)
    return "".join(bolaklar)


def dekodla(matn):
    """Qochirilgan ';' ni hisobga olib, satrni ro'yxatga qaytaradi."""
    natija, joriy, qochirish = [], [], False
    for belgi in matn:
        if qochirish:
            joriy.append(belgi)
            qochirish = False
        elif belgi == QOCHIRISH:
            qochirish = True
        elif belgi == TUGATUVCHI:
            natija.append("".join(joriy))
            joriy = []
        else:
            joriy.append(belgi)
    return natija

Endi uchala holat bir-biridan farq qiladi:

Ro'yxatKodlangani
[]bo'sh satr
[""];
["a", "b"]a;b;
["a;b"]a\;b;
Terminal
pytest -q -p no:randomly test_xossa.py test_misol.py
Natija
....                                                             [100%]
4 passed in 0.63s

Yashil. Endi biz bir necha yuz kirish uchun qoidaning bajarilishini bilamiz - uchta misol uchun emas.

Topilgan misolni muhrlab qo'yish #

Hypothesis har ishga tushirishda boshqa kirishlar sinaydi. Muhim holat topilgan bo'lsa, uni @example bilan doimiy qiling:

Python
from hypothesis import example, given, settings
from hypothesis import strategies as st

from kodlash import dekodla, kodla


@settings(derandomize=True)
@given(st.lists(st.text()))
@example([])
@example([""])
@example([";"])
@example(["a\\b"])
def test_kodlab_dekodlaganda_ozi_qaytadi(qatorlar):
    assert dekodla(kodla(qatorlar)) == qatorlar
Terminal
pytest -q -p no:randomly test_xossa.py
Natija
.                                                                [100%]
1 passed in 0.63s

Endi bu to'rtta holat har doim sinaladi, qolganlari esa tasodifiy tanlanadi. Bu ikki usulning eng yaxshi tomonlarini birlashtiradi.

Qaysi xossalarni izlash kerak #

To'rtta eng foydali xossa turi Aylanma yo'l dekodla(kodla(x)) == x Saqlash va o'qish, shifrlash va deshifrlash, JSON va obyekt O'zgarmas qoida len(saralangan) == len(asl) Natija haqida har doim rost bo'ladigan gap Takrorlash bezarar tozala(tozala(x)) == tozala(x) Ikkinchi marta qo'llash hech narsani o'zgartirmaydi Ishonchli o'lchov tez_usul(x) == sodda_usul(x) Tez yechim sekin, lekin aniq yechim bilan solishtiriladi
Xossa topolmasangiz, shu to'rttadan boshlang

Qolgan uchtasini ham amalda yozamiz:

Python
def tozala(matn):
    """Ortiqcha bo'shliqlarni olib tashlaydi."""
    return " ".join(matn.split())


def sekin_saralash(sonlar):
    """Sodda, lekin aniq usul - ishonchli o'lchov sifatida ishlatiladi."""
    natija = list(sonlar)
    for i in range(len(natija)):
        for j in range(len(natija) - 1 - i):
            if natija[j] > natija[j + 1]:
                natija[j], natija[j + 1] = natija[j + 1], natija[j]
    return natija
Python
from hypothesis import given, settings
from hypothesis import strategies as st

from matn import sekin_saralash, tozala


@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_saralash_uzunlikni_saqlaydi(sonlar):
    assert len(sorted(sonlar)) == len(sonlar)


@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_saralangan_royxat_tartibda(sonlar):
    natija = sorted(sonlar)

    assert all(natija[i] <= natija[i + 1] for i in range(len(natija) - 1))


@settings(derandomize=True)
@given(st.text())
def test_tozalash_takrorlanganda_ozgarmaydi(matn):
    bir_marta = tozala(matn)

    assert tozala(bir_marta) == bir_marta


@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_tez_va_sekin_usul_bir_xil(sonlar):
    assert sorted(sonlar) == sekin_saralash(sonlar)
Terminal
pytest -q -p no:randomly test_xossalar.py
Natija
....                                                             [100%]
4 passed in 0.84s

To'rtta test, lekin ularning har biri yuzlab kirishda sinaldi. Nechta ekanini ko'rish mumkin:

Terminal
pytest -q -p no:randomly --hypothesis-show-statistics test_xossalar.py
Natija
....                                                             [100%]
======================== Hypothesis Statistics =========================
test_xossalar.py::test_saralash_uzunlikni_saqlaydi:

  - during generate phase (0.10 seconds):
    - Typical runtimes: < 1ms, of which < 1ms in data generation
    - 100 passing, 0 failing, and 14 invalid test cases

  - Stopped because settings.max_examples=100


test_xossalar.py::test_saralangan_royxat_tartibda:

  - during generate phase (0.08 seconds):
    - Typical runtimes: < 1ms, of which < 1ms in data generation
    - 100 passing, 0 failing, and 15 invalid test cases

  - Stopped because settings.max_examples=100


test_xossalar.py::test_tozalash_takrorlanganda_ozgarmaydi:

  - during generate phase (0.05 seconds):
    - Typical runtimes: < 1ms, of which < 1ms in data generation
    - 100 passing, 0 failing, and 0 invalid test cases

  - Stopped because settings.max_examples=100


test_xossalar.py::test_tez_va_sekin_usul_bir_xil:

  - during generate phase (0.10 seconds):
    - Typical runtimes: ~ 0-1 ms, of which < 1ms in data generation
    - 100 passing, 0 failing, and 12 invalid test cases

  - Stopped because settings.max_examples=100


4 passed in 0.35s

Har test uchun 100 ta misol sinaldi. Bu son sukut bo'yicha shunday va uni @settings(max_examples=1000) bilan oshirish mumkin.

Strategiyalar #

Hypothesis kirishlarni strategiya orqali yaratadi. Eng ko'p ishlatiladiganlari:

StrategiyaNima yaratadi
st.integers()Butun sonlar, jumladan juda katta va manfiy
st.integers(min_value=0, max_value=100)Chegaralangan sonlar
st.floats(allow_nan=False)Kasr sonlar (nan va inf siz)
st.text()Satrlar - Unicode ning har burchagidan
st.lists(st.integers())Ro'yxatlar, jumladan bo'shi
st.dictionaries(st.text(), st.integers())Lug'atlar
st.dates(), st.datetimes()Sana va vaqt
st.sampled_from(["A", "B", "F"])Berilgan ro'yxatdan tanlash
st.builds(Foydalanuvchi, ism=st.text())O'z obyektlaringiz
Xossaviy testlash misolli testlarni almashtirmaydi

Ikkalasi turli ishni bajaradi:

Misolli testXossaviy test
Aniq talabni ifodalaydiUmumiy qoidani ifodalaydi
Tez o'qiladi, hujjat vazifasini bajaradiO'qish qiyinroq
MillisoniyalarSoniyalar
Faqat siz o'ylagan holatlarSiz o'ylamagan holatlar

Amalda ular birga ishlatiladi: asosiy holatlar uchun misolli testlar, chuqur tekshirish uchun bir-ikkita xossaviy test.

Yana bir chegara: xossaviy test xossani topish talab qiladi. Ba'zi kod uchun (masalan interfeys yoki hisobot formati) foydali xossa umuman topilmaydi - va bu normal.

Amaliy topshiriq
  1. kodlash.py ni yozib, uchta misolli test bilan sinang.
  2. Hypothesis o'rnatib, aylanma yo'l xossasini yozing.
  3. Topilgan eng kichik misolni yozib oling.
  4. Nima uchun uchta misolli test bu xatoni sezmaganini tushuntiring.
  5. Bo'sh ro'yxatni maxsus holat sifatida tuzating va yana ishga tushiring.
  6. Endi qanday misol topildi? Nima uchun maxsus holat yetmadi?
  7. Tugatuvchi va qochirish bilan yechimni yozing.
  8. @example bilan to'rtta holatni doimiy qiling.
  9. tozala uchun takrorlash bezararligi xossasini yozing.
  10. Loyihangizdagi bitta funksiya uchun aylanma yo'l xossasini toping.

Xulosa #

  • Xossaviy testlash - misol emas, qoida yozish; kirishlarni kompyuter yaratadi.
  • @given(st.lists(st.text())) yuzlab turli ro'yxatlar bilan testni ishga tushiradi.
  • Hypothesis xato topgach, qisqartirish bosqichida uni eng kichik shaklga keltiradi.
  • Shuning uchun u nafaqat xato topadi, balki uni tushunarli qilib ko'rsatadi.
  • derandomize=True har ishga tushirishda bir xil kirishlar yaratilishini ta'minlaydi.
  • Qo'lda yozilgan misollar odatda "normal" ma'lumotdan iborat bo'ladi - shuning uchun ular chegara holatlarini sezmaydi.
  • Bizning misolimizda uchta haqiqiy xato bor edi: bo'sh ro'yxat, bo'sh element va ajratuvchi belgi.
  • Maxsus holat qo'shish birinchi xatoni yashirdi, lekin ikkinchisini emas - Hypothesis uni darhol topdi.
  • Ajratuvchi o'rniga tugatuvchi ishlatish uchala holatni ham hal qildi.
  • @example(...) topilgan muhim holatni testga doimiy qilib muhrlaydi.
  • To'rtta eng foydali xossa turi: aylanma yo'l, o'zgarmas qoida, takrorlash bezarar, ishonchli o'lchov.
  • Aylanma yo'l saqlash/o'qish, shifrlash/deshifrlash va JSON kabi juft amallar uchun tabiiy.
  • Ishonchli o'lchov - tez yechimni sodda, lekin aniq yechim bilan solishtirish.
  • --hypothesis-show-statistics har test uchun nechta misol sinalganini ko'rsatadi; sukut bo'yicha 100 ta.
  • Strategiyalar kirish turini belgilaydi: integers, text, lists, dates, sampled_from, builds.
  • Xossaviy test misolli testni almashtirmaydi - ular birga ishlatiladi.

Keyingi bo'limda boshqa savolga o'tamiz: kod to'g'ri ishlayapti, lekin yetarli tezmi?

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.