17-bo‘lim
Xossaviy testlash
Misollar o'rniga qoidalarni tekshirish, Hypothesis bilan kirishlarni avtomatik yaratish, qisqartirish (shrinking) va qaysi xossalarni izlash kerak.
Ushbu bo‘lim mundarijasi
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:
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:
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"]
pytest -q -p no:randomly test_misol.py
... [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.
pip install hypothesis
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
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
pytest -q -p no:randomly --tb=line test_xossa.py
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.
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:
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(";")
pytest -q -p no:randomly --tb=line test_xossa.py
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):
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'yxat | Kodlangani |
|---|---|
[] | bo'sh satr |
[""] | ; |
["a", "b"] | a;b; |
["a;b"] | a\;b; |
pytest -q -p no:randomly test_xossa.py test_misol.py
.... [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:
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
pytest -q -p no:randomly test_xossa.py
. [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 #
Qolgan uchtasini ham amalda yozamiz:
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
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)
pytest -q -p no:randomly test_xossalar.py
.... [100%]
4 passed in 0.84s
To'rtta test, lekin ularning har biri yuzlab kirishda sinaldi. Nechta ekanini ko'rish mumkin:
pytest -q -p no:randomly --hypothesis-show-statistics test_xossalar.py
.... [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:
| Strategiya | Nima 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 |
Ikkalasi turli ishni bajaradi:
| Misolli test | Xossaviy test |
|---|---|
| Aniq talabni ifodalaydi | Umumiy qoidani ifodalaydi |
| Tez o'qiladi, hujjat vazifasini bajaradi | O'qish qiyinroq |
| Millisoniyalar | Soniyalar |
| Faqat siz o'ylagan holatlar | Siz 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.
kodlash.pyni yozib, uchta misolli test bilan sinang.- Hypothesis o'rnatib, aylanma yo'l xossasini yozing.
- Topilgan eng kichik misolni yozib oling.
- Nima uchun uchta misolli test bu xatoni sezmaganini tushuntiring.
- Bo'sh ro'yxatni maxsus holat sifatida tuzating va yana ishga tushiring.
- Endi qanday misol topildi? Nima uchun maxsus holat yetmadi?
- Tugatuvchi va qochirish bilan yechimni yozing.
@examplebilan to'rtta holatni doimiy qiling.tozalauchun takrorlash bezararligi xossasini yozing.- 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=Truehar 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-statisticshar 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?
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.