Tarkibga o'tish

14 β€” BDD va spetsifikatsiya

🏠 README Β· ⬅️ Oldingi: 13 β€” Refactoring va testlar Β· Keyingi: 15 β€” Integratsiya testlari ➑️


Bu bobda: BDD (Behavior-Driven Development) β€” TDD'ning "test" so'zidan "xulq va spetsifikatsiya" so'ziga o'tgan evolyutsiyasi. Given-When-Then strukturasini (AAA bilan taqqoslab), Gherkin tilini, "specification by example" va "jonli hujjat" g'oyalarini, hamda umumiy til (ubiquitous language) orqali biznes-dasturchi-tester ko'prigini ko'ramiz. Amaliyotda β€” Given/When/Then'ni oddiy pytest bilan ifodalaymiz.

Halollik / Eslatma: Gherkin namunalari konseptual (ularni ishlatish shart emas β€” .feature matni qanday ko'rinishini ko'rsatamiz). Python qadam-funksiyalari esa haqiqatan python -m pytest (Python 3.14, pytest 9.0.3) bilan ishga tushirib tekshirilgan. pytest-bdd/behave ekotizimini faqat tanishtiramiz (sintaksis ko'rsatib, kodsiz). Oxirida halol aytamiz: BDD yomon qilinganda ortiqcha "tarjima qatlami"ga aylanadi.


"Test" so'zidan "xulq" so'ziga

Tasavvur qiling, oshpazga buyurtma berasiz. Ikki xil aytishingiz mumkin:

  • ❌ Texnik: "150 Β°C pechda 22 daqiqa, ichki harorat 74 Β°C ga yetsin."
  • βœ… Xulq: "Tovuq tashqaridan tilla rang, ichidan suvsiz, yumshoq bo'lsin."

Ikkinchisini mijoz ham, oshpaz ham tushunadi. Birinchisi β€” faqat oshpaz tili. Dasturda ham shunday: "calculate_discount() 20 qaytarsin" β€” dasturchi tili. "VIP mijozga 20% chegirma berilsin" β€” hamma tushunadigan til.

BDD (Behavior-Driven Development β€” "xulq orqali boshqariladigan ishlab chiqish") aynan shu o'tish haqida. Uni 2006-yil atrofida Dan North kiritgan. Uning kuzatuvi sodda edi: yangi dasturchilarga TDD'ni o'rgatganda, ular doim bir savolga qoqilardi β€” "qaysi testni birinchi yozay? Testni nima deb nomlay?". North payqadiki, agar "test" so'zini "should" ("...bo'lishi kerak", ya'ni xulq) bilan almashtirsang, savol o'z-o'zidan yo'qoladi: test nomi tizim nima qilishi kerakligini tasvirlaydi.

Markaziy g'oya: BDD β€” yangi texnologiya emas, TDD'ning evolyutsiyasi (11-bob). Bir xil "avval kutilgan natijani, keyin kodni" sikli β€” lekin diqqat texnik "test"dan biznes "xulqqa" ko'chadi. Maqsad: kod nima qilishini mijoz ham o'qiy oladigan tilda yozish, shunda spetsifikatsiya, test va hujjat bitta narsaga aylanadi.

Nega bu muhim? Chunki dasturdagi eng qimmat xato β€” noto'g'ri narsani to'g'ri qurish. Dasturchi talabni noto'g'ri tushunsa, eng chiroyli kod ham befoyda. BDD bu bo'shliqni til birligi bilan yopadi: biznes, dasturchi va tester bir tilda gaplashadi.


Given-When-Then β€” xulqning uch bo'lagi

BDD xulqni doim uch qismga ajratadi:

  • Given (berilgan) β€” boshlang'ich kontekst, "dunyo qanday holatda".
  • When (qachon) β€” bitta hodisa yoki amal yuz beradi.
  • Then (u holda) β€” kutilgan natija.

Misol uchun, "voyaga yetmagan spirtli ichimlik sotib ololmaydi" xulqi:

Given 16 yoshli foydalanuvchi va bo'sh savat
When  u spirtli ichimlikni savatga qo'shmoqchi bo'ladi
Then  savdo rad etiladi

Tanish ko'rinmayaptimi? Bu β€” 02-bobdagi AAA (Arrange-Act-Assert)ning aynan o'zi, faqat boshqa nuqtai nazardan:

AAA (02-bob) Given-When-Then (BDD) Mohiyati
Arrange β€” tayyorla Given β€” boshlang'ich kontekst Holatni o'rnatamiz
Act β€” bajar When β€” hodisa / amal Bitta narsa yuz beradi
Assert β€” tekshir Then β€” kutilgan natija Natijani tasdiqlaymiz

Given-When-Then va AAA yonma-yon: bir xil uch bosqichli skelet, ikki xil nuqtai nazar

Skelet bir xil, farq β€” kim uchun yozilgani. AAA dasturchi tilida ("Savat obyektini yarat"); Given-When-Then biznes tilida ("foydalanuvchining bo'sh savati bor"). Shuning uchun BDD'ni o'rgansangiz, yangi narsa emas, balki bilganingizga yangi nom va yangi auditoriya olasiz.

Eslatma: "When" bitta hodisa bo'lishi kerak (AAA'dagi "bitta Act" qoidasi). Agar bir senariyda ikkita "When" yozsangiz β€” bu odatda ikkita alohida senariy bo'lishi kerakligining belgisi (04-bobdagi "bitta yiqilish sababi" tamoyili bilan bir xil ruh).


Gherkin tili β€” xulqni oddiy matnda yozish

BDD g'oyasini yozma formatga aylantirish uchun Gherkin degan til ishlatiladi (Cucumber asbobidan kelib chiqqan). Gherkin β€” bu deyarli oddiy ingliz (yoki o'zbek) tili, lekin bir nechta kalit so'z bilan tuzilgan, shuning uchun uni mashina ham o'qiy oladi.

Asosiy kalit so'zlar:

  • Feature β€” imkoniyat (bir foydalanuvchi qadr-qiymati, masalan "Pul yechish").
  • Scenario β€” bitta aniq holat (bitta misol).
  • Given / When / Then β€” yuqoridagi uch bosqich.
  • And / But β€” oldingi qadamni davom ettiradi (takrorlamaslik uchun).

Mana bankomatdan pul yechish uchun to'liq .feature matni (bu β€” konseptual namuna, uni biz ishlatmaymiz, faqat qanday ko'rinishini ko'rsatamiz):

# bankomat.feature
Feature: Bankomatdan pul yechish
  Mijoz sifatida men hisobimdan naqd pul yechmoqchiman,
  shunda kassa navbatida turishim shart bo'lmaydi.

  Scenario: Mablag' yetarli bo'lganda yechish
    Given hisobimda 100000 so'm bor
    When men 30000 so'm yechaman
    Then bankomat 30000 so'm beradi
    And hisobimda 70000 so'm qoladi

  Scenario: Mablag' yetmaganda rad etish
    Given hisobimda 20000 so'm bor
    When men 50000 so'm yechmoqchi bo'laman
    Then bankomat "Mablag' yetarli emas" deb xabar beradi
    And hisobimda 20000 so'm qoladi

Gherkin tuzilishi: Feature ichida Scenario, Scenario ichida Given/When/Then/And qadamlari

E'tibor bering: bu matnda bironta dasturlash atamasi yo'q β€” funksiya nomi, klass, status kod hech narsa. Buni mahsulot egasi (product owner) ham o'qiy va to'g'rilay oladi. Ayni vaqtda, har bir Given/When/Then qatori ortiga (keyinroq ko'ramiz) bir bo'lak kod ulanadi, shuning uchun bu matn bajariladigan testga aylanadi.

Bitta xulq, ko'p misol: Scenario Outline

Ko'pincha bitta xulqni bir nechta misol bilan tekshirish kerak. Gherkin buni Scenario Outline + Examples jadvali bilan beradi:

  Scenario Outline: Turli summalarda yechish
    Given hisobimda <balans> so'm bor
    When men <yechiladigan> so'm yechaman
    Then hisobimda <qolgan> so'm qoladi

    Examples:
      | balans | yechiladigan | qolgan |
      | 100000 |        30000 |  70000 |
      |  50000 |        50000 |      0 |
      | 100000 |            1 |  99999 |

Bu jadvalning har qatori β€” bitta misol. Pythonda bu @pytest.mark.parametrizening aynan ekvivalenti (06-bob) β€” quyida ko'ramiz.


Specification by example β€” misol = spetsifikatsiya = test

Klassik talabnoma (requirements document) shunday yoziladi: "Tizim mablag' yetarli bo'lganda pul berishi kerak." Ammo "yetarli" nima? Aniq emas. Specification by example ("misol orqali spetsifikatsiya") boshqacha yo'l tutadi: mavhum qoida o'rniga aniq misollar beradi.

Markaziy g'oya: Yaxshi tanlangan misollar β€” eng aniq spetsifikatsiya. "100000 dan 30000 yechsa, 70000 qoladi" β€” buni hech kim noto'g'ri tushunmaydi. BDD'da bu misollar ayni vaqtning o'zida uch narsa bo'ladi: (1) talab (spetsifikatsiya), (2) test (avtomat tekshiriladi), (3) hujjat.

Bu uchlikning ulanishi β€” jonli hujjat (living documentation) deb ataladi. Oddiy hujjat eskiradi: kod o'zgaradi, hujjat eski qoladi. Gherkin senariysi esa bajariladigan test bo'lgani uchun, agar kod xulqni buzsa, test yiqiladi β€” demak hujjat hech qachon "jimgina noto'g'ri" bo'lib qolmaydi. Yiqilgan test β€” eskirgan hujjatning ovozli signali.

Trade-off: jonli hujjat tirik bo'lib qolishi uchun doim ishga tushirilishi kerak (CI'da β€” 27-bob). Agar .feature fayllar yozilib, lekin pipeline'da bajarilmasa, ular oddiy (eskiradigan) hujjatdan farq qilmaydi. "Jonli"lik β€” bepul emas, intizom talab qiladi.


Umumiy til (ubiquitous language) β€” DDD bilan ko'prik

BDD'ning eng katta foydasi kod emas β€” so'zlar. Senariyni biznes bilan birga yozsangiz, hammangiz bir xil atamalardan foydalanasiz: mijoz "savat" desa, kodda ham savat, testda ham "savat". Bu g'oyaning nomi bor β€” ubiquitous language ("hamma joyda bir xil til"), uni Eric Evans Domain-Driven Design (DDD)da ommalashtirgan.

Umumiy til ko'prigi: biznes, Gherkin spetsifikatsiyasi va kod bir xil tilda gaplashadi

Tasavvur qiling, biznes "buyurtmani bekor qilish", dasturchi "order'ni soft-delete qilish", tester "o'chirib tashlash" deb atasa β€” uchovi har xil narsani tushunadi, mayda farqlar yo'qoladi va xatolar tug'iladi. Umumiy til bu "tarjima yo'qolishini" kamaytiradi. Gherkin senariysi β€” shu umumiy tilning yozma kelishuvi: barchasi bir matnga qaraydi va bir tilda gaplashadi.

Eslatma: Bu yerda DDD bilan to'g'ridan-to'g'ri ko'prik bor β€” chuqurroq: arxitektura kitobi, DDD asoslari. BDD'ni "DDD'ning testdagi aks-sadosi" deb o'ylash mumkin: ikkalasi ham domen tilini kodga olib kirishni xohlaydi.


Amaliy Python: Given-When-Then'ni oddiy pytest bilan

Endi eng muhim qism: pytest-bdd yoki Gherkin'siz ham BDD ruhida test yozish mumkin. Buning uchun ikki narsa kifoya: (1) test nomi xulqni tasvirlasin, (2) tana Given/When/Then izohlari bilan ajratilsin.

Avval sinaladigan domen kodi:

# magazin.py β€” sinaladigan kod (domen mantig'i)
class YoshChegarasiXatosi(Exception):
    pass

class Magazin:
    def __init__(self):
        self.savat = []

    def mahsulot_qosh(self, foydalanuvchi_yoshi, mahsulot, spirtli):
        if spirtli and foydalanuvchi_yoshi < 18:
            raise YoshChegarasiXatosi("18 yoshdan kichik spirtli ichimlik sotib ololmaydi")
        self.savat.append(mahsulot)
        return self.savat

Endi xulqni tasvirlaydigan testlar. Nomga e'tibor bering β€” u jumla: kodni o'qimasdan ham nima tekshirilayotgani tushunarli.

# test_magazin.py
import pytest
from magazin import Magazin, YoshChegarasiXatosi

def test_18_dan_kichik_foydalanuvchi_ichkilik_sotib_ololmaydi():
    # Given: 16 yoshli foydalanuvchi va bo'sh savat
    magazin = Magazin()
    yosh = 16

    # When: spirtli ichimlikni savatga qo'shmoqchi bo'ladi
    # Then: yosh chegarasi xatosi ko'tariladi
    with pytest.raises(YoshChegarasiXatosi):
        magazin.mahsulot_qosh(yosh, "pivo", spirtli=True)

def test_kattalar_ichkilik_sotib_olishi_mumkin():
    # Given: 25 yoshli foydalanuvchi
    magazin = Magazin()
    yosh = 25

    # When: spirtli ichimlikni savatga qo'shadi
    savat = magazin.mahsulot_qosh(yosh, "pivo", spirtli=True)

    # Then: mahsulot savatga tushadi
    assert savat == ["pivo"]
# -> 2 passed

Given/When/Then izohlari AAA bloklarining biznes-tilidagi nomi, xolos. Bu β€” eng yengil, eng amaliy "BDD" β€” hech qanday yangi kutubxonasiz, faqat fikrlash tarzi va nom berish intizomi.

Specification by example β€” parametrize bilan

Yuqoridagi Gherkin Scenario Outline/Examples jadvali Pythonda to'g'ridan-to'g'ri @pytest.mark.parametrizega aylanadi β€” har qator bitta misol-spetsifikatsiya:

# test_spek.py
import pytest
from magazin import bankomatdan_yech

# Har qator = bitta misol = bitta xulq spetsifikatsiyasi (Examples jadvalining ekvivalenti)
@pytest.mark.parametrize("balans, yechiladigan, kutilgan_qolgan", [
    (100000, 30000, 70000),   # oddiy yechish
    (50000, 50000, 0),        # hammasini yechish
    (100000, 1, 99999),       # eng kichik summa
])
def test_pul_yechish_misollari(balans, yechiladigan, kutilgan_qolgan):
    # Given balans, When summa yechiladi, Then qolgan kutilganday
    assert bankomatdan_yech(balans, yechiladigan) == kutilgan_qolgan
# -> 3 passed

Haqiqiy pytest chiqishi har misolni alohida sanaydi:

test_spek.py::test_pul_yechish_misollari[100000-30000-70000] PASSED
test_spek.py::test_pul_yechish_misollari[50000-50000-0] PASSED
test_spek.py::test_pul_yechish_misollari[100000-1-99999] PASSED
======================== 3 passed in 0.53s ========================

Qadamlarni qayta ishlatish β€” pytest-bdd ruhi, kutubxonasiz

pytest-bdd/behavening asosiy g'oyasi β€” Given/When/Then qadamlari qayta ishlatiladigan funksiyalar bo'lishi. Buni oddiy funksiyalar bilan ham taqlid qilish mumkin:

# test_qadamlar.py
from magazin import Magazin, YoshChegarasiXatosi

def given_yoshli_foydalanuvchi(yosh):
    return {"magazin": Magazin(), "yosh": yosh, "xato": None}

def when_spirtli_qoshadi(kontekst, mahsulot):
    try:
        kontekst["magazin"].mahsulot_qosh(kontekst["yosh"], mahsulot, spirtli=True)
    except YoshChegarasiXatosi as e:
        kontekst["xato"] = e
    return kontekst

def then_rad_etiladi(kontekst):
    assert isinstance(kontekst["xato"], YoshChegarasiXatosi)
    assert kontekst["magazin"].savat == []

def test_voyaga_yetmagan_rad_etiladi():
    kontekst = given_yoshli_foydalanuvchi(15)     # Given
    kontekst = when_spirtli_qoshadi(kontekst, "vino")  # When
    then_rad_etiladi(kontekst)                    # Then
# -> 1 passed

Bu yondashuv qadamlarni ko'p senariyda qayta ishlatish imkonini beradi β€” aynan pytest-bdd .feature faylga ulaydigan narsa, faqat bizda Gherkin matni o'rniga to'g'ridan-to'g'ri Python.


pytest-bdd va behave ekotizimi (qisqacha tanishuv)

Agar .feature fayllarni haqiqatan bajarmoqchi bo'lsangiz, Pythonda ikki mashhur asbob bor (bizda o'rnatilmagan, shuning uchun faqat sintaksisni ko'rsatamiz):

pytest-bdd (pip install pytest-bdd) β€” .feature faylni o'qiydi va har Given/When/Thenni dekorator orqali Python funksiyasiga bog'laydi:

# (konseptual β€” ishlatish uchun pytest-bdd kerak)
from pytest_bdd import scenario, given, when, then

@scenario("bankomat.feature", "Mablag' yetarli bo'lganda yechish")
def test_yechish():
    pass

@given("hisobimda 100000 so'm bor", target_fixture="hisob")
def _():
    return {"balans": 100000}

@when("men 30000 so'm yechaman")
def _(hisob):
    hisob["balans"] -= 30000

@then("hisobimda 70000 so'm qoladi")
def _(hisob):
    assert hisob["balans"] == 70000

behave (pip install behave) β€” alohida asbob (pytest'siz), .feature fayllar va steps/ papkasidagi qadam-funksiyalar bilan ishlaydi. G'oya bir xil.

Ikkalasida ham siz Gherkin matni (biznes uchun) va qadam kodi (dasturchi uchun) o'rtasida ko'prik qurasiz.


BDD foydasi va xavfi β€” halol gap

BDD β€” kumush o'q emas. U qachon arziydi, qachon yo'q β€” buni ochiq aytish kerak.

βœ… Yaxshi qilinganda:

  • Biznes-texnik ko'prik: mijoz/PO senariyni o'qiy va to'g'rilay oladi.
  • Murakkab biznes qoidalari (ko'p shart, ko'p misol) β€” Gherkin jadvali ularni aniq ushlaydi.
  • Jonli hujjat: spetsifikatsiya hech qachon "jimgina eskirmaydi".

❌ Yomon qilinganda (eng tez-tez uchraydigan tuzoq):

  • Faqat dasturchilar yozsa, mijoz o'qimasa β€” Gherkin shunchaki ortiqcha "tarjima qatlami"ga aylanadi: bir xil mantiqni ikki marta (.feature + qadam kodi) yozasiz, foyda esa nol.
  • Texnik senariy: "Given users jadvalida 1 qator bor, When GET /api/u/1..." β€” bu Gherkin emas, oddiy testning niqobli ko'rinishi. Biznes buni o'qiy olmaydi.
  • Har bir unit testni Gherkinga o'rash β€” ortiqcha boilerplate (oddiy AAA test 3 qator, Gherkin ekvivalenti 15 qator).

Trade-off / Diqqat: BDD'ning qiymati hamkorlikda (biznes + dasturchi + tester birga senariy yozadi). Agar bu hamkorlik yo'q bo'lsa β€” Gherkin sof yo'qotishdir. Qoida: agar mijoz senariyni o'qimaydigan bo'lsa, oddiy pytest AAA testi yozing, Gherkin'ni majburlamang. Eng ko'p foyda β€” yuqori darajadagi, biznes-muhim xulqlarda; mayda texnik birliklarda β€” kamdan-kam.


Til-mustaqillik

Gherkin β€” tildan mustaqil standart. Bir xil Feature/Scenario/Given/When/Then sintaksisi har yerda ishlaydi, faqat ortidagi "qadam" kodi tilga qarab o'zgaradi:

Til / ekotizim BDD asbobi
Java, Ruby, JavaScript Cucumber
.NET (C#) SpecFlow / Reqnroll
Python behave, pytest-bdd
PHP Behat

Demak BDD'ni o'rgansangiz β€” .feature fayllaringiz va fikrlash tarzingiz ko'chma bo'ladi; faqat qadam-implementatsiya tilga moslanadi. G'oya β€” Given-When-Then, specification by example va umumiy til β€” hamma joyda bir xil.


Asosiy g'oyalar (bobni qisqacha)

  • BDD β€” TDD evolyutsiyasi (Dan North): diqqat "test"dan "xulq/spetsifikatsiya"ga ko'chadi. Maqsad β€” biznes ham tushunadigan til.
  • Given-When-Then = AAAning biznes-tilidagi ko'rinishi: bir xil uch bosqichli skelet, boshqa auditoriya.
  • Gherkin β€” Feature/Scenario/Given/When/Then/And kalit so'zlari bilan tuzilgan, mashina o'qiy oladigan oddiy matn.
  • Specification by example: aniq misollar mavhum qoidadan tiniqroq spetsifikatsiya; misol = test = hujjat.
  • Jonli hujjat: bajariladigan senariy eskirmaydi β€” kod xulqni buzsa, test yiqiladi (lekin CI'da doim ishga tushirilishi shart).
  • Umumiy til (ubiquitous language) biznes-dasturchi-tester orasidagi "tarjima yo'qolishini" kamaytiradi; DDD bilan ko'prik.
  • Amaliyot: BDD'ni Gherkin'siz ham qilish mumkin β€” xulqni tasvirlovchi test nomi + Given/When/Then izohlari + parametrize.
  • Xavf: mijoz o'qimasa, Gherkin ortiqcha tarjima qatlami; mayda unit testni Gherkinga o'rash β€” boilerplate. Hamkorlik bo'lganda arziydi.

Mashqlar

Oson

1-mashq. Given-When-Then'ning uch qismini AAA'ning qaysi qismiga mos kelishini ayting va har biriga bir og'iz tushuntiring.

2-mashq. "Foydalanuvchi noto'g'ri parol kiritsa, tizimga kira olmaydi" xulqini Given-When-Then formatida (oddiy matn, kod emas) yozing.

3-mashq. BDD'da "test" so'zi o'rniga qaysi so'zga urg'u beriladi va nega bu yangi dasturchilarga "qaysi testni yozay?" savolini yengillashtiradi β€” o'z so'zingiz bilan tushuntiring.

O'rta

4-mashq. Quyidagi Magazin.mahsulot_qosh uchun "voyaga yetmagan oddiy (spirtsiz) mahsulot sotib olishi mumkin" xulqini Given/When/Then izohli pytest testi sifatida yozing va nomini xulqni tasvirlaydigan jumla qiling.

5-mashq. Quyidagi Gherkin Scenario Outlineni Pythondagi @pytest.mark.parametrize testiga aylantiring (sinaladigan funksiya: narx_chegirma(narx, foiz) natijani qaytaradi):

  Scenario Outline: Chegirma hisoblash
    Given narxi <narx> bo'lgan mahsulot
    When <foiz>% chegirma qo'llaniladi
    Then yakuniy narx <yakuniy> bo'ladi

    Examples:
      | narx  | foiz | yakuniy |
      | 10000 |   10 |    9000 |
      | 20000 |   50 |   10000 |

6-mashq. Bitta jamoa har bir unit testini (3 qatorli AAA testlarini ham) majburan Gherkinga o'rab yozadi. Bu yerda qaysi tuzoqqa tushishgan? Qachon Gherkin arzimaydi β€” ikki sabab keltiring.

Qiyin

7-mashq. 5-mashqdagi narx_chegirma uchun "qadamlarni qayta ishlatish" uslubida (given_/when_/then_ funksiyalar + kontekst lug'ati) test yozing. Keyin ikkinchi senariy qo'shing (boshqa narx/foiz) va when_/then_ qadamlarini qayta ishlatganingizni ko'rsating.

8-mashq. "Jonli hujjat" g'oyasini tushuntiring: oddiy (matnli) hujjat va Gherkin senariysi o'rtasidagi farqni ayting. Keyin halol savol: jonli hujjat qachon oddiy hujjatdan farq qilmay qoladi (ya'ni "jonli"ligini yo'qotadi)?

Yechimlar

1-mashq yechimi

  • Given = Arrange β€” boshlang'ich holatni/kontekstni tayyorlaymiz.
  • When = Act β€” bitta hodisa yoki amalni ishga tushiramiz.
  • Then = Assert β€” kutilgan natijani tasdiqlaymiz.

Skelet bir xil; farq β€” Given-When-Then biznes tilida, AAA dasturchi tilida yoziladi.

2-mashq yechimi

Given ro'yxatdan o'tgan foydalanuvchi va to'g'ri login
When  u noto'g'ri parol bilan kirishga urinadi
Then  tizim uni kiritmaydi (kirish rad etiladi)

(Ixtiyoriy And menga "Parol noto'g'ri" xabari ko'rsatiladi.)

3-mashq yechimi

BDD'da "test" o'rniga "xulq" / "...bo'lishi kerak" (should) ga urg'u beriladi. Yangi dasturchiga "qaysi testni yoz?" qiyin, chunki "test" mavhum. Lekin "tizim nima qilishi kerak?" deb so'rasangiz, javob tabiiy keladi: "VIP mijozga chegirma berishi kerak". Shu jumla to'g'ridan-to'g'ri test nomiga aylanadi (test_vip_mijozga_chegirma_beriladi). Demak xulqqa urg'u β€” testni nomlash va tanlash muammosini yo'qotadi.

4-mashq yechimi

from magazin import Magazin

def test_voyaga_yetmagan_oddiy_mahsulot_sotib_olishi_mumkin():
    # Given: 15 yoshli foydalanuvchi
    magazin = Magazin()
    yosh = 15

    # When: spirtsiz mahsulotni savatga qo'shadi
    savat = magazin.mahsulot_qosh(yosh, "non", spirtli=False)

    # Then: mahsulot savatga tushadi (yosh chegarasi yo'q)
    assert savat == ["non"]
# -> 1 passed

5-mashq yechimi

import pytest

def narx_chegirma(narx, foiz):
    return narx - narx * foiz // 100

@pytest.mark.parametrize("narx, foiz, yakuniy", [
    (10000, 10, 9000),
    (20000, 50, 10000),
])
def test_chegirma_misollari(narx, foiz, yakuniy):
    # Given narx, When foiz qo'llaniladi, Then yakuniy narx kutilganday
    assert narx_chegirma(narx, foiz) == yakuniy
# -> 2 passed

Gherkin Examples jadvalining har qatori β€” parametrize ro'yxatining bitta korteji.

6-mashq yechimi

Tuzoq: ular Gherkin'ni majburlab, har bir mayda texnik testni ham (oddiy 3 qatorli AAA test) 15 qatorli .feature + qadam kodiga o'rashgan β€” bu boilerplate va ikki marta yozish (mantiq .featureda ham, qadamda ham). Gherkin arzimaydigan ikki holat:

  1. Mijoz/PO senariyni o'qimasa β€” biznes-texnik ko'prik yo'q, faqat dasturchilar yozadi va o'qiydi; demak qo'shimcha qatlamning foydasi nol.
  2. Mayda, sof texnik birlikni testlashda (masalan, bir funksiyaning chegara qiymati) β€” oddiy pytest AAA testi qisqaroq, tezroq va aniqroq.

7-mashq yechimi

def narx_chegirma(narx, foiz):
    return narx - narx * foiz // 100

def given_mahsulot(narx):
    return {"narx": narx, "yakuniy": None}

def when_chegirma_qollaniladi(kontekst, foiz):
    kontekst["yakuniy"] = narx_chegirma(kontekst["narx"], foiz)
    return kontekst

def then_yakuniy_narx(kontekst, kutilgan):
    assert kontekst["yakuniy"] == kutilgan

def test_10foiz_chegirma():
    k = given_mahsulot(10000)
    when_chegirma_qollaniladi(k, 10)
    then_yakuniy_narx(k, 9000)

def test_50foiz_chegirma():           # bir xil when_/then_ qadamlari qayta ishlatildi
    k = given_mahsulot(20000)
    when_chegirma_qollaniladi(k, 50)
    then_yakuniy_narx(k, 10000)
# -> 2 passed

when_chegirma_qollaniladi va then_yakuniy_narx ikkala senariyda ham o'zgartirilmasdan ishlatildi β€” bu aynan pytest-bdd/behave qadamlarni qayta ishlatishining oddiy ko'rinishi.

8-mashq yechimi

Oddiy (matnli) hujjat β€” alohida yashaydi: kod o'zgaradi, hujjat yangilanmaydi va asta-sekin "jimgina noto'g'ri" bo'lib qoladi (hech kim sezmaydi). Gherkin senariysi esa qadam kodiga ulangan bajariladigan test: agar kod xulqni buzsa, test yiqiladi β€” yiqilish ovozli signaldir, demak hujjat noto'g'ri ekanini darhol bilamiz.

Halol savol β€” jonli hujjat "jonli"ligini yo'qotadi, agar u muntazam ishga tushirilmasa. Agar .feature fayllar yozilib, lekin CI'da (yoki umuman) bajarilmasa, ular ham xuddi oddiy hujjat kabi jimgina eskiradi. "Jonli"lik kelib chiqishi β€” doimiy bajarilishdan; bajarilmagan senariy β€” shunchaki chiroyli, eskiradigan matn.


🏠 README Β· ⬅️ Oldingi: 13 β€” Refactoring va testlar Β· Keyingi: 15 β€” Integratsiya testlari ➑️