IlmHamroh
Python kursi/Testlash10/10-dars21 daqiqa
Mundarija (23)

17.10-dars: Property-based test (hypothesis)

17-QISM — TESTLASH · 10-dars · 17-qism yakuni


1. Kirish va motivatsiya

Shu paytgacha yozgan barcha testlar misolga asoslangan edi: siz aniq kirish beradingiz (qoshish(2, 3)) va aniq natijani tekshirdingiz (== 5). Bu yaxshi, lekin bitta cheklovi bor: siz faqat o'zingiz o'ylagan holatlarni sinaysiz. Kodingizni buzadigan chegara holati (bo'sh ro'yxat, juda katta son, maxsus belgi) sizning xayolingizga kelmasa, u test qilinmaydi. Property-based testing (xususiyatga asoslangan testlash) buni hal qiladi: siz misol emas, umumiy qoida (xususiyat) yozasiz, kutubxona esa yuzlab tasodifiy kirishni o'zi yaratib, qoidani buzadigan holatni topadi.

hypothesis — Python'ning property-based testing kutubxonasi. U nafaqat xatoni topadi, balki uni eng kichik ko'rinishga keltirib (shrinking) beradi — hatoni tushunishni osonlashtiradi.

Real vaziyat. Bir jamoa summani formatlovchi funksiya yozdi va uni ko'p misol bilan test qildi — hammasi o'tdi. Ular hypothesis qo'shishdi: "har qanday son uchun, formatlangan summani qayta o'qisak, asl songa teng bo'lishi kerak" degan xususiyat yozishdi. hypothesis bu qoidani minglab tasodifiy son bilan sinadi va darhol yiqildi: manfiy nol (-0.0), juda katta son va aniqlik chegarasidagi qiymatlar funksiyani buzardi. Bu holatlar hech kimning xayoliga kelmagan edi. Undan ham yaxshisi — hypothesis xatoni eng kichik ko'rinishga (0.0 va -0.0) qisqartirib berdi, muammoni bir zumda tushunarli qildi.

Bu darsda property-based testing ni — misol emas, xususiyat asosida test yozishni o'rganamiz va 17-qismni yakunlaymiz.

Bu darsda:

  • Misolga vs xususiyatga asoslangan test
  • @given va strategiyalar (strategies)
  • hypothesis yuzlab kirishni o'zi yaratadi
  • Shrinking — eng kichik qarshi misol
  • Umumiy xususiyat naqshlari (roundtrip, invariant, kommutativlik, idempotentlik)
  • @example — muhim holatni kafolatlash
  • Qachon property-based, qachon misol
  • Amaliy: funksiyani xususiyat bilan sinash
  • 17-qism yakuni

ℹ Bu darsda hypothesis — tashqi kutubxona (pip install hypothesis) ishlatiladi.


2. Nazariya — chuqur tushuntirish

2.1. Misol vs xususiyat

Misolga asoslangan Xususiyatga asoslangan
Aniq kirish → aniq natija Umumiy qoida (har kirish uchun)
qoshish(2, 3) == 5 qoshish(a, b) == qoshish(b, a)
Siz o'ylagan holatlar Kutubxona yuzlab holat yaratadi
Chegara holatlarini unutish mumkin Kutubxona ularni topadi

Ikkalasi bir-birini to'ldiradi: misol testlar aniq holatlarni hujjatlaydi, property testlar kutilmagan chegara holatlarini topadi.

2.2. @given va strategiyalar

@given dekoratori test funksiyasiga tasodifiy kirish beradi; strategiya qanday ma'lumot yaratilishini belgilaydi:

python
from hypothesis import given, strategies as st

@given(st.integers(), st.integers())
def test_kommutativ(a, b):          # a va b — tasodifiy butun sonlar
    assert a + b == b + a

hypothesis bu testni ko'p marta (sukut 100) turli a, b bilan ishga tushiradi.

Strategiya Yaratadi
st.integers() Butun sonlar (istalgan kattalikda)
st.floats() Float (NaN, inf ham)
st.text() Satrlar (Unicode)
st.lists(st.integers()) Butun sonlar ro'yxati
st.booleans() True/False
st.dictionaries(...) Lug'atlar
st.dates(), st.datetimes() Sana/vaqt

2.3. Strategiyalarni cheklash

python
st.integers(min_value=1, max_value=100)      # 1-100 oralig'ida
st.floats(allow_nan=False, allow_infinity=False)   # NaN/inf siz
st.lists(st.integers(), min_size=1)          # bo'sh bo'lmagan
st.text(min_size=1, max_size=10)             # 1-10 belgili

Strategiyalar birlashtiriladi: st.lists(st.tuples(st.text(), st.integers())) — (satr, son) juftliklar ro'yxati.

2.4. Shrinking — eng kichik qarshi misol

hypothesis xatoni topganda, uni eng kichik ko'rinishga qisqartiradi (shrinking):

python
@given(st.lists(st.integers()))
def test_ortacha(sonlar):
    assert ortacha(sonlar) >= 0      # bo'sh ro'yxatda ZeroDivisionError

hypothesis [] (bo'sh ro'yxat) ni topadi va ko'rsatadi: Falsifying example: sonlar=[]. Katta tasodifiy ro'yxat emas, aynan minimal buzuvchi holat — muammoni tushunish oson.

Shrinking — property-based testing ning eng qimmatli imkoniyati: "1000 elementli tasodifiy ro'yxat yiqildi" emas, "bo'sh ro'yxat yiqildi" deydi.

2.5. Umumiy xususiyat naqshlari

Property yozish uchun "har kirish uchun to'g'ri bo'ladigan qoida" ni topish kerak. Keng tarqalgan naqshlar:

Naqsh Misol
Roundtrip decode(encode(x)) == x
Invariant len(sorted(lst)) == len(lst)
Kommutativlik f(a, b) == f(b, a)
Idempotentlik f(f(x)) == f(x) (masalan sorted)
Oracle Sekin, to'g'ri versiya bilan solishtirish
Chegaralar min(lst) <= ortacha(lst) <= max(lst)

Roundtrip (aylanib qaytish) — eng ko'p ishlatiladigan: seriyalash/deseriyalash (16-qism), kodlash/dekodlash, parse/format.

2.6. @example — muhim holatni kafolatlash

Tasodifiy kirishga qo'shimcha, muayyan holatni har doim sinash uchun:

python
from hypothesis import given, example, strategies as st

@given(st.integers())
@example(0)              # 0 ni har doim sinash (muhim chegara)
@example(-1)
def test_x(n): ...

@example — ma'lum muhim/xatoli holatni kafolatli sinaydi (regressiya kabi).

2.7. Qachon property-based

Property-based mos Misol afzal
Aniq xususiyat bor (roundtrip, invariant) Aniq biznes qoidasi (soliq(1000) == 120)
Ko'p chegara holati Bitta muayyan holat
Algoritmlar, ma'lumot tuzilmalari Oddiy mantiq
Seriyalash, parsing Konfiguratsiya

Property-based har joyga mos kelmaydi: ba'zan "to'g'ri natija" ni faqat aniq misol bilan ifodalash mumkin. Ikkalasini birga ishlating.

2.8. @settings va nazorat

python
from hypothesis import settings

@settings(max_examples=500)          # ko'proq kirish sinash
@settings(deadline=None)             # vaqt chegarasini olib tashlash
@settings(derandomize=True)          # takrorlanuvchi (bir xil kirishlar)

derandomize=True — CI va reproduksiya uchun testni deterministik qiladi.


3. Tez ma'lumotnoma

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

@given(st.integers(), st.integers())
def test_kommutativ(a, b):
    assert qoshish(a, b) == qoshish(b, a)

@given(st.text())
def test_roundtrip(s):
    assert dekod(kod(s)) == s              # aylanib qaytish

@given(st.lists(st.integers(), min_size=1))
@example([0])                             # muhim holat kafolati
def test_invariant(lst):
    assert min(lst) <= max(lst)

Qoidalar

misol emas, xususiyat (umumiy qoida) yozing
@given + strategies bilan tasodifiy kirish
roundtrip: decode(encode(x)) == x — eng ko'p ishlatiladi
hypothesis chegara holatlarini o'zi topadi
shrinking — eng kichik qarshi misol
@example — muhim holatni kafolatlash
misol va property testlarni birga ishlating

4. Batafsil misollar

Misollarda pytest (hypothesis bilan) ni deterministik ishga tushiruvchi pytest_ishga yordamchisi ishlatiladi (derandomize=True).

Misol 1 — Property-based asoslari

python
"""@given + strategiyalar; hypothesis yuzlab kirishni sinaydi; kommutativlik va roundtrip xususiyatlari; strategiyalarni cheklash."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


TEST = '''
from hypothesis import given, settings, strategies as st

@settings(derandomize=True)
@given(st.integers(), st.integers())
def test_qoshish_kommutativ(a, b):
    assert a + b == b + a

@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_reverse_roundtrip(lst):
    # ikki marta teskari — asl ro'yxat (roundtrip)
    assert list(reversed(list(reversed(lst)))) == lst

@settings(derandomize=True)
@given(st.text())
def test_encode_decode_roundtrip(s):
    # utf-8 ga o'girib qaytarsak — asl satr
    assert s.encode("utf-8").decode("utf-8") == s

@settings(derandomize=True)
@given(st.lists(st.integers(), min_size=1))
def test_sorted_invariant(lst):
    saralangan = sorted(lst)
    assert len(saralangan) == len(lst)              # uzunlik saqlanadi
    assert min(lst) == saralangan[0]                # eng kichik birinchi
    assert max(lst) == saralangan[-1]               # eng katta oxirgi

@settings(derandomize=True)
@given(st.integers(min_value=1, max_value=100))
def test_cheklangan_oralik(n):
    assert 1 <= n <= 100                            # strategiya cheklovi
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "test_prop.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Property-based testlar ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ har test yuzlab tasodifiy kirish bilan sinaldi va o'tdi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Property-based testlar ===
  test_prop.py::test_qoshish_kommutativ PASSED
  test_prop.py::test_reverse_roundtrip PASSED
  test_prop.py::test_encode_decode_roundtrip PASSED
  test_prop.py::test_sorted_invariant PASSED
  test_prop.py::test_cheklangan_oralik PASSED
  chiqish kodi: 0
  ⭐ har test yuzlab tasodifiy kirish bilan sinaldi va o'tdi

Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.5-bo'limlar.

Misol 2 — Xatoni topish va shrinking

python
"""hypothesis o'ylanmagan chegara holatini topadi (bo'sh ro'yxat, maxsus belgi); shrinking bilan eng kichik qarshi misolga qisqartiradi."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    natija = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile|Traceback|^\s+File |^\s+\^|^\s+[a-z_]+\.py:)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        l = l.replace("E   ", "  ").rstrip()
        natija.append(l)
    return r.returncode, natija


KOD = '''
def ortacha(sonlar):
    return sum(sonlar) / len(sonlar)         # bo'sh ro'yxatda XATO

def birinchi_harf_katta(matn):
    return matn[0].upper() + matn[1:]        # bo'sh satrda XATO
'''

TEST = '''
from hypothesis import given, settings, strategies as st
from kod import ortacha, birinchi_harf_katta

@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_ortacha_xato(sonlar):
    # xususiyat: natija son bo'lishi kerak — lekin bo'sh ro'yxatda yiqiladi
    natija = ortacha(sonlar)
    assert isinstance(natija, float)

@settings(derandomize=True)
@given(st.text())
def test_bosh_satr_xato(matn):
    # xususiyat: har satr uchun ishlashi kerak — lekin bo'sh satrda yiqiladi
    natija = birinchi_harf_katta(matn)
    assert len(natija) == len(matn)
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "kod.py").write_text(KOD, encoding="utf-8")
        (r / "test_xato.py").write_text(TEST, encoding="utf-8")

        print("=== 1. hypothesis chegara holatini topadi ===")
        kod, satrlar = pytest_ishga(r, "--tb=short", "-q")
        for satr in satrlar:
            s = satr.strip()
            if s.startswith("FAILED") or "Falsifying" in s or "Failing test case" in s \
                    or "sonlar=" in s or "matn=" in s or "passed" in s or "failed" in s \
                    or "ZeroDivision" in s or "IndexError" in s:
                print(f"  {s}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ shrinking: eng kichik qarshi misol — bo'sh ro'yxat [] va bo'sh satr ''")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. hypothesis chegara holatini topadi ===
  ZeroDivisionError: division by zero
  Failing test case: test_ortacha_xato(
  sonlar=[],
  IndexError: string index out of range
  Failing test case: test_bosh_satr_xato(
  matn='',
  FAILED test_xato.py::test_ortacha_xato - ZeroDivisionError: division by zero
  FAILED test_xato.py::test_bosh_satr_xato - IndexError: string index out of range
  2 failed
  chiqish kodi: 1
  ⭐ shrinking: eng kichik qarshi misol — bo'sh ro'yxat [] va bo'sh satr ''

Nima ko'rsatdi: 2.4-bo'lim.

Misol 3 — Xususiyat naqshlari va @example

python
"""umumiy naqshlar: roundtrip (JSON), idempotentlik (sorted), oracle (sekin/tez versiya); @example bilan muhim holatni kafolatlash."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


TEST = '''
import json
from hypothesis import given, example, settings, strategies as st

# 1. Roundtrip — JSON seriyalash 16.2-bob
@settings(derandomize=True)
@given(st.dictionaries(st.text(), st.integers()))
def test_json_roundtrip(d):
    assert json.loads(json.dumps(d)) == d

# 2. Idempotentlik — ikki marta saralash bir marta bilan bir xil
@settings(derandomize=True)
@given(st.lists(st.integers()))
def test_sorted_idempotent(lst):
    assert sorted(sorted(lst)) == sorted(lst)

# 3. Oracle — o'z implementatsiya vs to'g'ri versiya
def mening_maks(lst):
    natija = lst[0]
    for x in lst[1:]:
        if x > natija:
            natija = x
    return natija

@settings(derandomize=True)
@given(st.lists(st.integers(), min_size=1))
def test_maks_oracle(lst):
    assert mening_maks(lst) == max(lst)          # built-in max bilan solishtirish

# 4. @example bilan muhim holatlar
@settings(derandomize=True)
@given(st.integers())
@example(0)
@example(-1)
@example(2**63)
def test_kvadrat_manfiy_emas(n):
    assert n * n >= 0
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "test_naqsh.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Xususiyat naqshlari ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ roundtrip, idempotentlik, oracle, @example — barchasi o'tdi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Xususiyat naqshlari ===
  test_naqsh.py::test_json_roundtrip PASSED
  test_naqsh.py::test_sorted_idempotent PASSED
  test_naqsh.py::test_maks_oracle PASSED
  test_naqsh.py::test_kvadrat_manfiy_emas PASSED
  chiqish kodi: 0
  ⭐ roundtrip, idempotentlik, oracle, @example — barchasi o'tdi

Nima ko'rsatdi: 2.5, 2.6-bo'limlar.

Misol 4 — Amaliy: pul yaxlitlash funksiyasini sinash

Pul summasini tiyingacha yaxlitlovchi funksiyani (16.2, Decimal) property-based va misolga asoslangan test bilan birga sinaymiz. Property testlar chegara holatlarini (manfiy, nol, juda katta, aniqlik chegarasi) topadi; misol testlar muayyan biznes qoidalarini hujjatlaydi. Bu — ikki yondashuvni birlashtirishning namunasi.

python
"""pul yaxlitlash: property testlar (roundtrip, invariant, chegaralar) + misol testlar (aniq biznes qoidalari); ikki yondashuv birga."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path

KOD = '''
from decimal import Decimal, ROUND_HALF_UP

def tiyinga_yaxlitla(summa: Decimal) -> Decimal:
    """Summani 2 xona (tiyin) aniqligigacha yaxlitlaydi."""
    return summa.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

def foizni_qolla(summa: Decimal, foiz: Decimal) -> Decimal:
    """Foiz qo'llab, tiyinga yaxlitlaydi."""
    return tiyinga_yaxlitla(summa * (Decimal(1) + foiz / Decimal(100)))
'''

TEST = '''
from decimal import Decimal
from hypothesis import given, example, settings, strategies as st
from pul import tiyinga_yaxlitla, foizni_qolla

# Strategiya: mantiqli pul summalari
pul = st.decimals(min_value=Decimal("0"), max_value=Decimal("1000000"),
                  allow_nan=False, allow_infinity=False, places=4)

# --- Property testlar ---
@settings(derandomize=True)
@given(pul)
def test_yaxlitlash_idempotent(summa):
    # ikki marta yaxlitlash — bir marta bilan bir xil
    bir = tiyinga_yaxlitla(summa)
    assert tiyinga_yaxlitla(bir) == bir

@settings(derandomize=True)
@given(pul)
def test_natija_ikki_xona(summa):
    # natija har doim 2 xonali (tiyin)
    natija = tiyinga_yaxlitla(summa)
    assert -natija.as_tuple().exponent == 2

@settings(derandomize=True)
@given(pul)
def test_yaxlitlash_yaqin(summa):
    # yaxlitlangan summa asldan 0.005 dan uzoq bo'lmasin
    natija = tiyinga_yaxlitla(summa)
    assert abs(natija - summa) <= Decimal("0.005")

@settings(derandomize=True)
@given(pul)
def test_nol_foiz_ozgarmas(summa):
    # 0% foiz summani o'zgartirmaydi (yaxlitlangan holda)
    assert foizni_qolla(summa, Decimal(0)) == tiyinga_yaxlitla(summa)

# --- Misol testlar (aniq biznes qoidalari) ---
def test_yaxlitlash_yuqoriga():
    assert tiyinga_yaxlitla(Decimal("10.125")) == Decimal("10.13")

def test_yaxlitlash_pastga():
    assert tiyinga_yaxlitla(Decimal("10.124")) == Decimal("10.12")

def test_foiz_qollash():
    assert foizni_qolla(Decimal("100"), Decimal("12")) == Decimal("112.00")

def test_soliq_hisobi():
    # 12% QQS: 1250.50 → 1400.56
    assert foizni_qolla(Decimal("1250.50"), Decimal("12")) == Decimal("1400.56")
'''


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "pul.py").write_text(KOD, encoding="utf-8")
        (r / "test_pul.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Property + misol testlar birga ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")
        print(f"\n  chiqish kodi: {kod}")
        print("  ⭐ property — chegara holatlari; misol — aniq biznes qoidalari")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Property + misol testlar birga ===
  test_pul.py::test_yaxlitlash_idempotent PASSED
  test_pul.py::test_natija_ikki_xona PASSED
  test_pul.py::test_yaxlitlash_yaqin PASSED
  test_pul.py::test_nol_foiz_ozgarmas PASSED
  test_pul.py::test_yaxlitlash_yuqoriga PASSED
  test_pul.py::test_yaxlitlash_pastga PASSED
  test_pul.py::test_foiz_qollash PASSED
  test_pul.py::test_soliq_hisobi PASSED

  chiqish kodi: 0
  ⭐ property — chegara holatlari; misol — aniq biznes qoidalari

Nima ko'rsatdi: 2.5, 2.7-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Property test misol testni almashtiradi" Bir-birini to'ldiradi
"Property test — bir kirish bilan" Yuzlab tasodifiy kirish
"Yiqilsa, katta tasodifiy kirish ko'rinadi" Shrinking — eng kichik qarshi misol
"Har kod uchun property yozish oson" Ba'zan misol tabiiyroq
"hypothesis sekin" Sukut 100 kirish — tez
"@example keraksiz" Muhim holatlarni kafolatlaydi
"Property test — faqat matematik kod uchun" Roundtrip, invariant — hamma joyda
"hypothesis tasodifiy — takrorlanmaydi" derandomize=True bilan deterministik

6. Keng tarqalgan xatolar va yechimlari

1. Faqat misol testlar (chegara holatlari o'tkaziladi)

python
def test_ortacha(): assert ortacha([1, 2, 3]) == 2   # ❌ bo'sh ro'yxat?
@given(st.lists(st.integers()))                       # ✅ hypothesis topadi
def test_ortacha_prop(lst): ...

2. Xususiyat o'rniga aniq natijani kutish

python
@given(st.integers())
def test_x(n): assert kvadrat(n) == 25                # ❌ har n uchun 25 emas
def test_x(n): assert kvadrat(n) == n * n             # ✅ umumiy qoida

3. NaN/inf ni hisobga olmaslik

python
@given(st.floats())                                   # ❌ NaN test yiqiladi
def test_x(f): assert f == f
@given(st.floats(allow_nan=False))                    # ✅
def test_x(f): assert f == f

4. Juda keng strategiya

python
@given(st.integers())                                 # ❌ juda katta sonlar
def test_massiv(n): arr = [0] * n                     # xotira portlashi
@given(st.integers(min_value=0, max_value=1000))      # ✅ cheklangan
def test_massiv(n): ...

5. Property da yon ta'sir

python
@given(st.integers())
def test_x(n): global hisob; hisob += n               # ❌ holat to'planadi
# ✅ property sof bo'lsin (yon ta'sirsiz)

6. Muhim holatni kafolatlamaslik

python
@given(st.integers())
def test_x(n): ...                                    # 0 sinalmasligi mumkin
@given(st.integers())
@example(0)                                           # ✅ 0 kafolatlanadi
def test_x(n): ...

7. Deterministik emas (CI da beqaror)

python
@given(st.integers())                                 # ⚠️ har run boshqa kirish
@settings(derandomize=True)                           # ✅ takrorlanuvchi
@given(st.integers())
def test_x(n): ...

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 16.2-dars (o'tilgan): JSON — roundtrip xususiyati (loads(dumps(x)) == x)
  • 17.2–17.4-darslar (o'tilgan): pytest, parametrize — hypothesis ular bilan ishlaydi
  • 17.7-dars (o'tilgan): coverage — property testlar qamrovni oshiradi
  • 24-qism: ma'lumot tahlili — funksiyalarni property bilan sinash
  • 27-qism: arxitektura — invariantlarni tekshirish

8. Eng yaxshi amaliyotlar

  1. Misol emas, xususiyat (umumiy qoida) yozing.

  2. Roundtrip (decode(encode(x)) == x) — eng ko'p ishlatiladigan naqsh.

  3. Strategiyalarni mantiqli cheklang (min_value, allow_nan=False).

  4. Shrinking yordamida eng kichik qarshi misolni o'rganing.

  5. Muhim/chegara holatlarni @example bilan kafolatlang.

  6. Property va misol testlarni birga ishlating.

  7. CI uchun derandomize=True bilan takrorlanuvchi qiling.

  8. Property sof (yon ta'sirsiz) bo'lsin.


9. Amaliy topshiriq

Vazifa 1: Xususiyatni aniqlang

Har funksiya uchun property (umumiy qoida) yozing:

python
1.  qoshish(a, b)        # kommutativlik?
2.  reversed(reversed(lst))   # roundtrip?
3.  sorted(lst)          # idempotentlik? invariant?
4.  abs(x)               # har doim?
5.  json.dumps/loads     # roundtrip?
6.  len(lst + [x])       # invariant?
7.  max(lst)             # chegara?
8.  str(int(s))          # qachon roundtrip?
Javoblar
  1. qoshish(a, b) == qoshish(b, a) (kommutativlik)
  2. list(reversed(list(reversed(lst)))) == lst (roundtrip)
  3. sorted(sorted(lst)) == sorted(lst) (idempotentlik); len(sorted(lst)) == len(lst) (invariant)
  4. abs(x) >= 0 (har doim manfiy emas)
  5. json.loads(json.dumps(x)) == x (roundtrip, oddiy turlarda)
  6. len(lst + [x]) == len(lst) + 1 (invariant)
  7. max(lst) in lst va max(lst) >= min(lst) (chegara)
  8. str(int(s)) == s faqat s normallashgan butun son satri bo'lsa (masalan "007" emas)

Vazifa 2: Xatolarni tuzating

python
1.  @given(st.integers())
    def test_kvadrat(n):
        assert kvadrat(n) == 4          # har n uchun 4 emas

2.  @given(st.floats())
    def test_teng(f):
        assert f == f                   # NaN != NaN

3.  @given(st.integers())
    def test_massiv(n):
        arr = list(range(n))            # katta n — xotira

4.  @given(st.text())
    def test_birinchi(s):
        assert s[0].isupper() or True   # bo'sh satr — IndexError

5.  @given(st.integers())
    def test_hisob(n):
        global jami
        jami += n                       # holat to'planadi
Javoblar
python
1.  @given(st.integers())
    def test_kvadrat(n):
        assert kvadrat(n) == n * n      # umumiy qoida

2.  @given(st.floats(allow_nan=False))  # NaN siz
    def test_teng(f):
        assert f == f

3.  @given(st.integers(min_value=0, max_value=1000))   # cheklangan
    def test_massiv(n):
        arr = list(range(n))

4.  @given(st.text(min_size=1))         # bo'sh bo'lmagan
    def test_birinchi(s):
        assert isinstance(s[0], str)

5.  @given(st.integers())
    def test_hisob(n):
        assert hisob([n]) == n          # sof — global holatsiz

Vazifa 3: Roundtrip to'plami

16-qismdagi formatlar uchun roundtrip property testlari:

  1. JSON: loads(dumps(x)) == x (oddiy turlar strategiyasi)
  2. CSV: bir qatorli yozib-o'qish teng
  3. Base64: decode(encode(x)) == x
  4. Har biriga qaysi turlar ishlamasligini toping (hypothesis bilan)

Vazifa 4: Saralash algoritmini sinash

O'z saralash funksiyangizni (masalan bubble sort):

  1. Invariant: uzunlik saqlanadi, elementlar bir xil (Counter)
  2. Oracle: built-in sorted bilan solishtiring
  3. Idempotentlik: ikki marta saralash
  4. hypothesis topgan xatolarni tuzating

Vazifa 5: Holatlar mashinasi

hypothesis ning RuleBasedStateMachine bilan (Stack yoki Savat):

  1. Amallar (push/pop) ketma-ketligini tasodifiy yarating
  2. Invariantlarni har amaldan keyin tekshiring
  3. hypothesis buzuvchi ketma-ketlikni topsin
  4. Oddiy property test bilan solishtiring

Vazifa 6: Shrinking ni kuzatish

  1. Ataylab xatoli funksiya yozing (masalan n > 100 da yiqiladi)
  2. hypothesis topgan qarshi misolni ko'ring
  3. Shrinking qanday minimal qiymatga (101) qisqartirganini kuzating
  4. @example bilan uni regressiya testiga aylantiring

Vazifa 7: O'ylash

Property-based testing 1999-yilda Haskell'ning QuickCheck kutubxonasidan boshlangan va endi ko'p tilda (Python hypothesis, JS fast-check) mavjud. Shunga qaramay, u misolga asoslangan testdan kamroq tarqalgan. Nima uchun kuchli g'oya bo'lsa ham property-based testing sekin qabul qilinmoqda, va u qanday muammolarni misol testlar hal qila olmaydigan darajada yaxshi hal qiladi?

Javob

Qisqa javob: Property-based testing sekin tarqalyapti, chunki u boshqacha fikrlashni talab qiladi: aniq misol ("2+3=5") o'rniga umumiy xususiyat ("qo'shish kommutativ") topish kerak, bu esa qiyinroq va o'rgatish talab qiladi. Ko'p dasturchi misol testlarni tabiiy deb biladi. Lekin property testing misol testlar printsipial hal qila olmaydigan narsani hal qiladi: siz o'ylamagan chegara holatlarini avtomatik topish. Inson faqat o'zi tasavvur qilgan holatlarni sinaydi; hypothesis minglab holatni yaratib, tasavvurni kengaytiradi.

1. Nega sekin tarqalyapti

Sabab Tafsilot
Fikrlash o'zgarishi Misol emas, xususiyat topish
O'rganish egri chizig'i Strategiyalar, naqshlar
"Xususiyat topib bo'lmaydi" tuyg'usi Ba'zi kod uchun qiyin
Misol testlar tabiiyroq "2+3=5" — aniq va tushunarli
Vaqt bosimi Boshda ko'proq o'ylash

2. Property testing hal qiladigan noyob muammolar

Muammo Nega misol test yeta olmaydi
O'ylanmagan chegara holatlari Inson faqat tasavvurini sinaydi
Kombinatsiyalar portlashi Qo'lda yozib bo'lmaydi
Minimal qarshi misol (shrinking) Qo'lda topish qiyin
Regressiya (avtomatik) Yangi holatlar o'zi topiladi
Invariantlarni ifodalash Umumiy qoida — kuchli hujjat

3. Ikkalasining o'rni

Misol test Property test
Aniq biznes qoidasi Umumiy xususiyat
Hujjat/spetsifikatsiya Chegara holatlarini topish
Tushunarli, aniq Kutilmaganni ochadi
Har kod uchun Xususiyat aniq bo'lganda

4. Nega baribir qimmatli

Property testing "test yozuvchi sifatida siz o'ylamagan narsani" topadi — bu uning eng katta qiymati. 0.1 + 0.2, bo'sh ro'yxat, -0.0, maxsus Unicode belgilar — bularni inson ko'pincha unutadi, hypothesis esa topadi.

5. Amaliy tavsiya

  1. Aniq xususiyat bor bo'lsa (roundtrip, invariant) — property test
  2. Aniq biznes qoidasi — misol test
  3. Ikkalasini birga: property chegaralarni, misol qoidalarni
  4. Kritik kod (pul, xavfsizlik, algoritm) — property test ayniqsa qimmatli

6. Xulosa

  1. Sekin tarqalishi — fikrlash o'zgarishi va o'rganish talabidan
  2. O'ylanmagan chegara holatlarini topish — noyob qiymati
  3. Shrinking — minimal qarshi misol
  4. Misol va property testlar birga eng kuchli

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda property-based testing ni — misol emas, xususiyat asosida test yozishni o'rgandik.

Eng muhim uch fikr:

  1. Misol emas, xususiyat. Misolga asoslangan test siz o'ylagan holatlarni sinaydi; property-based test umumiy qoida (qoshish(a, b) == qoshish(b, a)) yozadi va hypothesis yuzlab tasodifiy kirishni o'zi yaratib, qoidani buzadigan holatni topadi. @given va strategiyalar (st.integers(), st.text(), st.lists(...)) kirishni belgilaydi. Bu misol testlar o'tkazib yuboradigan chegara holatlarini (bo'sh ro'yxat, -0.0, maxsus belgi) topadi.

  2. Shrinking va xususiyat naqshlari. hypothesis xatoni topganda uni eng kichik qarshi misolga qisqartiradi (Falsifying example: sonlar=[]) — "1000 elementli tasodifiy ro'yxat" emas, aynan minimal buzuvchi holat, muammoni tushunish oson. Umumiy naqshlar: roundtrip (decode(encode(x)) == x — eng ko'p ishlatiladigan), invariant (len(sorted(lst)) == len(lst)), idempotentlik (sorted(sorted(x)) == sorted(x)), oracle (to'g'ri versiya bilan solishtirish). @example muhim holatni kafolatlaydi.

  3. Misol va property testlarni birga. Property-based testing misol testni almashtirmaydi — to'ldiradi: property testlar chegara holatlarini avtomatik topadi, misol testlar aniq biznes qoidalarini (soliq(1250.50, 12) == 1400.56) hujjatlaydi. Aniq xususiyat bor bo'lganda (algoritmlar, seriyalash, pul hisobi) property test ayniqsa kuchli; derandomize=True bilan CI uchun takrorlanuvchi qiling.

Bu bilan 17-qism — Testlash yakunlandi. Keyingi 18-qismda kod sifati va vositalari ga o'tamiz: virtual muhit (venv), bog'liqliklarni boshqarish (pip, poetry, uv), lint (ruff), formatlash (black), pre-commit, profiling va debugging.


17-qism yakuni

Testlash qismida kodning to'g'ri ishlashini kafolatlaydigan barcha asosiy vositalarni o'rgandik — misoldan xususiyatgacha, unit'dan integratsiongacha.

Dars Mavzu Asosiy g'oya
17.1 Nega test yozamiz Qo'lda emas, avtomatik; regressiya
17.2 pytest asoslari Oddiy assert, raises, approx, belgilar
17.3 Fixture Tayyorlash/tozalash, yield, conftest.py
17.4 parametrize Sinov holatlari — ma'lumot sifatida
17.5 Mock — tushuncha Tashqi bog'liqlikdan izolyatsiya
17.6 Mock — amaliyot patch, "qayerda ishlatilsa"
17.7 Coverage Ko'r nuqtalar; 100% ≠ xatosiz
17.8 TDD Test avval; qizil-yashil-refaktoring
17.9 Integratsion Qismlar birga; test piramidasi
17.10 Property-based Xususiyat; shrinking; hypothesis

Umumiy tamoyillar:

  1. Test — investitsiya: boshda vaqt, keyin ishonch va tezlik. Har tuzatilgan xato uchun regressiya testi.
  2. Piramida: ko'p tez unit, o'rta integratsion, kam E2E.
  3. Ma'noli testlar: coverage foizini emas, muhim yo'llarni va xato holatlarini qamrang.
  4. To'g'ri vosita: tashqi bog'liqlik — mock; o'z baza — haqiqiy; umumiy xususiyat — property test.

Keyingi bosqich — kod sifati va vositalari (18): muhit, bog'liqlik, lint, format, profiling — professional dasturchining kundalik asboblar to'plami.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.10-dars: Property-based test (hypothesis) — IlmHamroh