Tarkibga o'tish

02 β€” Birinchi testingiz: AAA va test anatomiyasi

🏠 README Β· ⬅️ Oldingi: 01 β€” Nega test yozamiz? Β· Keyingi: 03 β€” Test turlari va test piramidasi ➑️


Bu bobda: terminalingizda birinchi avtomat testni yozasiz va ishga tushirasiz. Test runner (pytest) nima ekanini, assert qanday ishlashini, va har bir testni o'qishga oson qiladigan AAA naqshi (Arrange-Act-Assert) bilan tanishasiz. Yaxshi test nomi qanday bo'lishini ko'rasiz va pytest test fayllaringizni qanday avtomat topishini bilib olasiz.

Halollik / Eslatma: misollar Python + pytest ustida, lekin AAA, test nomlash va "bitta test = bitta xulq" tamoyillari har qanday tilda bir xil. Bu bobda eng oddiy testlardan boshlaymiz; fixture, parametrize va mock kabi kuchli vositalar keyingi boblarga qoldiriladi. Bu bobdagi barcha kod va pytest chiqishlari haqiqatan ishga tushirib tekshirilgan.


Test runner nima va nega kerak

Tasavvur qiling, siz oshxonada ovqat pishirdingiz va har gal mehmon kelganda uni qo'lda tatib ko'rasiz: tuz yetarlimi, issiqmi. Bu β€” qo'lda testlash. Endi tasavvur qiling, sizning o'rningizga bir yordamchi bor: u har gal ovqat tayyor bo'lishi bilan o'zi tatib, "tuz joyida, harorat joyida" deb hisobot beradi β€” bir soniyada, charchamasdan, har safar aynan bir xil. Mana shu yordamchi β€” test runner.

Test runner β€” bu sizning testlaringizni topib, ishga tushirib, natijani hisobot qiladigan dastur. Python dunyosida eng mashhuri β€” pytest. Siz faqat "nimani tekshirish kerak"ni yozasiz; topish, chaqirish, natijani yashil yoki qizil qilib ko'rsatish β€” runner'ning ishi.

Nega runner kerak? Chunki bitta funksiyani bir marta qo'lda tekshirish oson, ammo loyiha o'sgani sayin yuzlab tekshiruv paydo bo'ladi. Ularning hammasini har o'zgarishdan keyin qo'lda qaytarish mumkin emas. Runner buni bir buyruq bilan, soniyalarda bajaradi.

Til-ko'prik: JavaScript'da runner β€” Jest yoki Vitest; PHP'da β€” PHPUnit yoki Pest; Java'da β€” JUnit; Go'da β€” o'rnatilgan testing paketi. Nomi boshqacha, vazifasi bir xil: testlarni topadi, ishga tushiradi, hisobot beradi.

pytest'ni o'rnatish va ishga tushirish

pytest oddiy paket. Terminalda:

pip install pytest

O'rnatilganini tekshirish:

pytest --version
# -> pytest 9.0.3

Testlarni ishga tushirish β€” joriy papkada shunchaki:

pytest

Batafsilroq, har test nomini ko'rsatib ishga tushirish:

pytest -v

-v (verbose, ya'ni "gapdor") har bir testni alohida qatorda PASSED yoki FAILED bilan ko'rsatadi. Tezda ko'rasiz, bu farqning ahamiyati katta.


Eng birinchi testingiz

Kichik bir funksiyadan boshlaylik: ikki sonni qo'shadigan qoshish. Avval sinaladigan kodni alohida faylga yozamiz.

Fayl: kalkulyator.py (bu β€” biz testlayotgan haqiqiy kod):

def qoshish(a, b):
    return a + b

Endi test kodini alohida faylga yozamiz. Bu yerda eng muhim konvensiya bor: pytest test fayllarini nomidan tanib oladi β€” fayl nomi test_ bilan boshlanishi (yoki _test bilan tugashi) kerak, va ichidagi test funksiyalari ham test_ bilan boshlanishi kerak.

Fayl: test_kalkulyator.py (bu β€” testlar):

from kalkulyator import qoshish


def test_ikki_musbat_son_qoshilganda_yigindi_qaytadi():
    natija = qoshish(2, 3)
    assert natija == 5

Ikki fayl bir papkada yonma-yon turadi:

proj/
β”œβ”€β”€ kalkulyator.py          # sinaladigan kod
└── test_kalkulyator.py     # test kod

Endi terminalda papkada turib pytest deymiz:

pytest
# ->
# ======================== test session starts ========================
# collected 1 item
#
# test_kalkulyator.py .                                          [100%]
#
# ========================= 1 passed in 0.53s =========================

Tabriklaymiz β€” siz birinchi avtomat testingizni yozib, ishga tushirdingiz. O'sha . (nuqta) "bitta test o'tdi" degani. 1 passed β€” hammasi yashil.

pytest -v bilan ko'rsa, test nomi to'liq ko'rinadi:

pytest -v
# ->
# test_kalkulyator.py::test_ikki_musbat_son_qoshilganda_yigindi_qaytadi PASSED [100%]
# ========================= 1 passed in 0.52s =========================

Eslatma: kodni va testni nega ikki faylga ajratamiz? Chunki kalkulyator.py β€” ishlab chiqarishga (production) ketadigan haqiqiy kod, test_kalkulyator.py esa faqat tekshirish uchun. Ularni ajratish kodingizni toza saqlaydi va testlarni alohida ishga tushirish, alohida tashlab yuborishni osonlashtiradi.


assert qanday ishlaydi

assert β€” Python'ning o'rnatilgan kalit so'zi. U juda oddiy: assert SHART deyilsa, agar SHART rost bo'lsa β€” hech narsa bo'lmaydi, kod davom etadi. Agar yolg'on bo'lsa β€” AssertionError istisnosi (exception) tashlanadi va test yiqiladi.

assert 5 == 5     # rost -> hech narsa bo'lmaydi
assert 4 == 5     # yolg'on -> AssertionError, test yiqiladi

Test runner mana shu mexanizmga tayanadi: test funksiyasi oxirigacha istisno tashlamasdan yetib borsa β€” PASSED; biror assert yiqilsa (yoki kutilmagan istisno chiqsa) β€” FAILED.

pytest'ning aqlli xabari (assertion introspection)

Oddiy assert yiqilganda Python faqat "AssertionError" deydi β€” qaysi qiymatlar mos kelmaganini aytmaydi. pytest esa assertion introspection (tekshiruvni o'rganib chiqish) qiladi: yiqilgan assertning ikki tomonidagi haqiqiy qiymatlarni o'zi hisoblab, sizga ko'rsatadi. Bu β€” xatoni topishni tezlashtiradigan eng yoqimli xususiyatlardan biri.

Ataylab yiqiladigan test yozamiz: qoshish(2, 2) natijasini xato qilib 5 deb kutamiz.

from kalkulyator import qoshish


def test_qoshish_xato_kutilsa_yiqiladi():
    natija = qoshish(2, 2)
    assert natija == 5

Ishga tushiramiz:

pytest test_yiqiladi.py
# ->
# =============================== FAILURES ===============================
# ___________________ test_qoshish_xato_kutilsa_yiqiladi _________________
#
#     def test_qoshish_xato_kutilsa_yiqiladi():
#         natija = qoshish(2, 2)
# >       assert natija == 5
# E       assert 4 == 5
#
# test_yiqiladi.py:6: AssertionError
# ========================== 1 failed in 0.68s ==========================

Diqqat qiling: pytest aynan assert 4 == 5 deb yozdi. Ya'ni natija aslida 4 bo'lgan, biz esa 5 kutgan ekanmiz β€” bu yiqilishning sababi darrov ko'rinib turibdi. Kodga kirib qidirishga hojat yo'q.

Endi tuzatamiz β€” kutilgan qiymatni to'g'rilaymiz (2 + 2 = 4):

def test_qoshish_tuzatildi():
    natija = qoshish(2, 2)
    assert natija == 4
# ->
# ========================== 1 passed in 0.53s ==========================

Yashil. Mana shu sikl β€” yozish, ishga tushirish, qizilni ko'rish, tuzatish, yashilni ko'rish β€” testlash bilan ishlashning kundalik ritmi.

Test runner oqimi: pytest fayllarni topadi, ishga tushiradi va yashil yoki qizil hisobot beradi


AAA naqsh: Arrange β€” Act β€” Assert

Eng yaxshi testlar bir xil shaklga ega bo'ladi. Bu shakl β€” AAA naqsh: har test uchta aniq bosqichdan iborat:

  1. Arrange (tayyorla) β€” testga kerakli obyekt, ma'lumot va boshlang'ich holatni tayyorlaymiz.
  2. Act (bajar) β€” tekshirilayotgan bitta amalni ishga tushiramiz.
  3. Assert (tekshir) β€” natija kutilgan qiymatga mosligini tasdiqlaymiz.

AAA naqsh: Arrange tayyorlaydi, Act bajaradi, Assert tekshiradi

Buni real misolda ko'raylik. Bizda kichik savat (shopping cart) klassi bor.

Fayl: savat.py (sinaladigan kod):

class Savat:
    def __init__(self):
        self.mahsulotlar = []

    def qoshish(self, nom, narx):
        self.mahsulotlar.append((nom, narx))

    def jami(self):
        return sum(narx for _, narx in self.mahsulotlar)

Fayl: test_savat.py β€” har bosqichni izoh bilan ajratamiz:

from savat import Savat


def test_ikki_mahsulot_qoshilganda_jami_narxlar_yigindisi_boladi():
    # Arrange β€” savatni tayyorlaymiz
    savat = Savat()
    savat.qoshish("non", 5000)
    savat.qoshish("sut", 12000)

    # Act β€” tekshirilayotgan amalni bajaramiz
    jami = savat.jami()

    # Assert β€” natijani kutilgan qiymat bilan solishtiramiz
    assert jami == 17000

Ishga tushiramiz:

pytest test_savat.py -v
# ->
# test_savat.py::test_ikki_mahsulot_qoshilganda_jami_narxlar_yigindisi_boladi PASSED [ 50%]
# test_savat.py::test_bosh_savatning_jamisi_nol PASSED                              [100%]
# ========================== 2 passed in 0.55s ==========================

Nega AAA o'qishni osonlashtiradi

Ko'zingiz testni yuqoridan pastga o'qiganda darrov javob topadi: "Nima holatda? (Arrange) Nima qildik? (Act) Nimani kutamiz? (Assert)". Uch bosqich bir-biriga aralashmasa, test bir nigohda tushunarli bo'ladi.

Quyidagi anti-misolga qarang β€” bu test ham ishlaydi, lekin bosqichlar aralashib ketgan, o'qish qiyin:

# ❌ Yomon: tayyorlash, amal va tekshiruv aralash
def test_savat():
    savat = Savat()
    savat.qoshish("non", 5000)
    assert savat.jami() == 5000   # tekshiruv o'rtada
    savat.qoshish("sut", 12000)
    assert savat.jami() == 17000  # yana tekshiruv
# βœ… Yaxshi: bir holat, bir amal, bir tekshiruv (AAA aniq)
def test_ikki_mahsulot_qoshilganda_jami_narxlar_yigindisi_boladi():
    savat = Savat()                  # Arrange
    savat.qoshish("non", 5000)
    savat.qoshish("sut", 12000)
    jami = savat.jami()              # Act
    assert jami == 17000             # Assert

Trade-off: har testga # Arrange / # Act / # Assert izohini yozish shartmi? Yo'q β€” kichik testlarda bo'sh qator bilan ajratish ham yetadi. Muhimi β€” shakl, izoh emas. Lekin boshlang'ich paytda izohlar fikrni tartibga solishga juda yordam beradi.

Eslatma: ba'zan to'rtinchi bosqich qo'shiladi β€” Cleanup (tozalash), masalan ochilgan fayl yoki ulanishni yopish. pytest'da buni odatda fixture bajaradi (06-bobda). Hozircha uch bosqich yetarli.


Test nomlash: nom o'zi hikoya aytsin

Test yiqilganda siz birinchi ko'radigan narsa β€” uning nomi. Yaxshi nom shu zahoti "nima buzilgani"ni aytib beradi; yomon nom esa sizni kodga qarashga majbur qiladi.

Yaxshi nom uch qismdan iborat: nima testlanadi + qaysi sharoitda + qanday natija kutiladi.

Yaxshi test nomi: nima testlanadi, qaysi sharoitda, qanday natija kutiladi

Misollar:

❌ Yomon nom βœ… Yaxshi nom
test_qoshish test_manfiy_sonlar_qoshilganda_togri_yigindi
test_1 test_bosh_savatning_jamisi_nol
test_ishlaydimi test_mavjud_bolmagan_mahsulot_ochirilsa_xato_chiqadi
test_savat test_ikki_mahsulot_qoshilganda_jami_yigindi_boladi

test_qoshish yiqilsa, siz "qo'shishning qaysi holatida?" deb so'raysiz β€” manfiy sonlarmi, nolmi, juda katta sonmi? Nom buni aytmaydi. test_manfiy_sonlar_qoshilganda_togri_yigindi esa darrov aytadi: muammo manfiy sonlarni qo'shishda.

Diqqat: uzun nomdan qo'rqmang. Test nomini siz qo'lda yozmaysiz β€” runner uni faqat hisobotda ko'rsatadi. "Uzun, lekin aniq" har doim "qisqa, lekin sirli"dan afzal.


Bitta test = bitta xulq

Yaxshi testning yana bir xossasi: u bitta xulqni (one logical assertion / bitta yiqilish sababi) tekshiradi. Ya'ni test yiqilsa, faqat bitta sabab bo'lishi kerak.

# ❌ Bir testda uch xil xulq aralash β€” yiqilsa, qaysi biri buzildi?
def test_savat_hammasi():
    savat = Savat()
    savat.qoshish("non", 5000)
    assert savat.jami() == 5000
    assert len(savat.mahsulotlar) == 1
    assert savat.mahsulotlar[0][0] == "non"

Bu testda uchta turli da'vo bor. Birinchisi yiqilsa, pytest qolganlarini tekshirmaydi ham β€” demak siz to'liq manzarani ko'rmaysiz. Har xulqni alohida testga ajratish yaxshiroq:

# βœ… Har test bitta xulq β€” yiqilsa, sabab aniq
def test_mahsulot_qoshilganda_jami_oshadi():
    savat = Savat()
    savat.qoshish("non", 5000)
    assert savat.jami() == 5000


def test_mahsulot_qoshilganda_soni_birga_oshadi():
    savat = Savat()
    savat.qoshish("non", 5000)
    assert len(savat.mahsulotlar) == 1

Eslatma: "bitta xulq" "bitta assert qatori" degani emas. Ba'zan bitta xulqni tasdiqlash uchun bir nechta assert kerak bo'ladi (masalan, bir obyektning bir nechta maydoni). Muhimi β€” barcha assertlar bitta g'oyani tekshirsin. Bunga 04-bobda chuqurroq qaytamiz.


Test fayllari tashkili va pytest qanday topadi (discovery)

pytest testlarni avtomat topadi β€” buni "discovery" (kashf qilish) deyiladi. U qat'iy, oddiy qoidalarga amal qiladi:

Element Konvensiya Misol
Test fayli test_*.py yoki *_test.py test_savat.py
Test funksiyasi test_ bilan boshlanadi def test_jami_togri(): ...
Test klassi (ixtiyoriy) Test bilan boshlanadi, __init__ siz class TestSavat: ...
Test papkasi (odatda) tests/ tests/test_savat.py

Mana shu nomlash konvensiyasiga rioya qilsangiz β€” pytest fayllaringizni avtomat topadi, ro'yxatdan o'tkazadi va ishga tushiradi. Hech qaysi faylni qo'lda ro'yxatga olish kerak emas.

Kichik loyihada kod va testlar bir papkada yonma-yon tursa bo'ladi. Loyiha o'sganda esa testlarni alohida tests/ papkasiga ajratish odatga aylangan:

proj/
β”œβ”€β”€ kalkulyator.py
β”œβ”€β”€ savat.py
└── tests/
    β”œβ”€β”€ test_kalkulyator.py
    └── test_savat.py

Endi loyiha ildizida shunchaki pytest desangiz, u butun daraxtni aylanib chiqib, barcha test_*.py fayllarni topadi. Bizning sinov loyihamizda uchta test fayli bor edi, va bir pytest buyrug'i hammasini topib ishga tushirdi:

pytest
# ->
# collected 5 items
#
# test_kalkulyator.py ..                                         [ 40%]
# test_savat.py ..                                               [ 80%]
# test_yiqiladi.py F                                             [100%]
#
# ===================== 1 failed, 4 passed in 0.68s =====================

. β€” o'tgan test, F β€” yiqilgan test. Bir nigohda: 4 ta yashil, 1 ta qizil.

Eslatma: test_ prefiksi shunchaki kelishuv emas β€” pytest aynan shunga qarab qaror qiladi. Agar test funksiyangizni tekshir_jami deb nomlasangiz, pytest uni umuman ko'rmaydi va jimgina o'tkazib yuboradi. "Testim ishlamayapti" muammosining eng keng tarqalgan sababi β€” noto'g'ri nom.


Til-ko'prik: AAA universaldir

AAA naqshi Python'ga xos emas β€” u barcha tillarda bir xil. JavaScript'da (Jest/Vitest) xuddi shu test shunday ko'rinadi:

// JavaScript β€” Jest/Vitest
test('ikki mahsulot qoshilganda jami yigindi boladi', () => {
  const savat = new Savat();          // Arrange
  savat.qoshish('non', 5000);
  savat.qoshish('sut', 12000);

  const jami = savat.jami();          // Act

  expect(jami).toBe(17000);           // Assert
});

Farq faqat sintaksisda: Python'da assert jami == 17000, JS'da expect(jami).toBe(17000), PHP'da (PHPUnit) $this->assertEquals(17000, $jami). Fikr β€” tayyorla, bajar, tekshir β€” bir xil. Bir tilda AAA'ni o'rgansangiz, boshqasiga o'tish β€” faqat sintaksisni almashtirish.


Asosiy g'oyalar (bobni qisqacha)

  • Test runner (pytest) testlarni topadi, ishga tushiradi va hisobot beradi β€” qo'lda tekshirishni avtomatlashtiradi.
  • assert SHART rost bo'lsa o'tadi, yolg'on bo'lsa testni yiqitadi. Test runner shu mexanizmga tayanadi: istisnosiz tugasa PASSED, yiqilsa FAILED.
  • pytest assertion introspection yiqilgan tekshiruvning haqiqiy qiymatlarini ko'rsatadi (assert 4 == 5) β€” xatoni topishni tezlashtiradi.
  • AAA naqsh β€” Arrange (tayyorla) β†’ Act (bajar) β†’ Assert (tekshir). Har testni bir nigohda o'qishga oson qiladi.
  • Yaxshi test nomi = nima + sharoit + kutilgan natija. Yiqilganda nomning o'zi sababni aytadi.
  • Bitta test = bitta xulq. Yiqilsa, sabab bitta bo'lsin; bir testga ko'p xil g'oyani tiqmang.
  • Discovery: pytest test_*.py fayllar va test_ funksiyalarni avtomat topadi. Noto'g'ri nom = test ko'rinmaydi.
  • AAA va nomlash til-mustaqil β€” JS, PHP, Java'da ham aynan shu tamoyillar.

Mashqlar

Oson

1-mashq. kopaytir(a, b) funksiyasini yozing va unga bitta test_ funksiyasi yarating. Test nomi "nima + sharoit + natija" qoidasiga mos bo'lsin. pytest bilan ishga tushiring.

2-mashq. Quyidagi testning AAA bosqichlarini izoh bilan ajrating:

def test_xx():
    savat = Savat()
    savat.qoshish("olma", 3000)
    jami = savat.jami()
    assert jami == 3000

3-mashq. Ataylab yiqiladigan test yozing (assert qoshish(2, 3) == 6). pytest chiqishida assert ... == ... qatorini toping va u nimani ko'rsatishini tushuntiring.

O'rta

4-mashq. Quyidagi nomlarni "yaxshi"ga aylantiring: test_login, test_2, test_narx. Har biri uchun nima testlanayotgani + sharoit + kutilgan natijani o'ylab toping.

5-mashq. Savatga ochirish(nom) metodini qo'shing (mahsulotni nomi bo'yicha o'chiradi). Keyin AAA bo'yicha test yozing: "mavjud mahsulot o'chirilsa, jami kamayadi".

6-mashq. Quyidagi "hammasini birga" testni uchta alohida, "bitta xulq" testiga bo'ling:

def test_savat():
    s = Savat()
    s.qoshish("non", 5000)
    assert s.jami() == 5000
    assert len(s.mahsulotlar) == 1

Qiyin

7-mashq. Bitta test fayl yozing, lekin bitta test funksiyasini test_ siz nomlang (masalan tekshir_jami). pytest -v ishga tushiring va u necha testni topganini kuzating. Nima uchun u funksiya hisobotda yo'qligini tushuntiring.

8-mashq. tests/ papka tuzilmasini yarating: ildizda savat.py, ichida tests/test_savat.py. Ildizdan pytest ishga tushiring. Agar ModuleNotFoundError: savat chiqsa, nega yuzaga kelishini va uni qanday hal qilish mumkinligini (masalan, ildizdan ishga tushirish yoki __init__.py / conftest.py) muhokama qiling.

Yechimlar

1-mashq yechimi

amallar.py:

def kopaytir(a, b):
    return a * b
test_amallar.py:
from amallar import kopaytir


def test_ikki_son_kopaytirilganda_kopaytma_qaytadi():
    natija = kopaytir(4, 5)
    assert natija == 20
# -> 1 passed in 0.5s

2-mashq yechimi

def test_xx():
    # Arrange
    savat = Savat()
    savat.qoshish("olma", 3000)
    # Act
    jami = savat.jami()
    # Assert
    assert jami == 3000
Tayyorlash (savat va mahsulot), amal (jami() chaqiruvi) va tekshiruv (assert) aniq ajraldi.

3-mashq yechimi

def test_qoshish_xato_yiqiladi():
    assert qoshish(2, 3) == 6
# ->
# >       assert qoshish(2, 3) == 6
# E       assert 5 == 6
pytest assert 5 == 6 deb ko'rsatadi: qoshish(2, 3) aslida 5 qaytargan, biz 6 kutgan edik. Introspection ikki tomonning haqiqiy qiymatini hisoblab beradi, shuning uchun sababni darrov ko'ramiz.

4-mashq yechimi

Yomon Yaxshi
test_login test_togri_parol_bilan_kirilganda_sessiya_ochiladi
test_2 test_bosh_royxatda_element_topilmaganda_none_qaytadi
test_narx test_chegirma_qollanilganda_narx_kamayadi

Har nom: nima (login/qidiruv/narx) + sharoit (to'g'ri parol / bo'sh ro'yxat / chegirma) + natija (sessiya ochiladi / None / narx kamayadi).

5-mashq yechimi

savat.pyga qo'shish:

    def ochirish(self, nom):
        self.mahsulotlar = [m for m in self.mahsulotlar if m[0] != nom]
Test:
def test_mavjud_mahsulot_ochirilganda_jami_kamayadi():
    # Arrange
    savat = Savat()
    savat.qoshish("non", 5000)
    savat.qoshish("sut", 12000)
    # Act
    savat.ochirish("sut")
    # Assert
    assert savat.jami() == 5000
# -> 1 passed

6-mashq yechimi

def test_mahsulot_qoshilganda_jami_narxga_teng():
    s = Savat()
    s.qoshish("non", 5000)
    assert s.jami() == 5000


def test_mahsulot_qoshilganda_soni_bittaga_oshadi():
    s = Savat()
    s.qoshish("non", 5000)
    assert len(s.mahsulotlar) == 1
(Uchinchi xulq sifatida mahsulot nomini tekshiruvchi alohida test ham qo'shsa bo'ladi.) Endi har test yiqilsa, qaysi xulq buzilgani aniq.

7-mashq yechimi

def tekshir_jami():       # 'test_' siz nom
    assert Savat().jami() == 0
pytest -v chiqishi: collected 0 items β€” pytest bu funksiyani umuman ko'rmaydi, chunki nomi test_ bilan boshlanmaydi. Discovery qoidasi qat'iy: faqat test_ prefiksli funksiyalar test deb hisoblanadi. Bu β€” "testim ishlamayapti, lekin xato ham yo'q" muammosining eng tez-tez uchraydigan sababi.

8-mashq yechimi

Tuzilma:

proj/
β”œβ”€β”€ savat.py
└── tests/
    └── test_savat.py
test_savat.py ichida from savat import Savat deyilsa, pytestni proj/ ildizidan ishga tushirsangiz odatda ishlaydi, chunki pytest ildiz papkasini import yo'liga qo'shadi (rootdir mexanizmi). Agar ModuleNotFoundError: savat chiqsa, sababi β€” Python savat.pyni qidirayotgan yo'lda topa olmayapti. Yechimlar: (1) pytestni aynan loyiha ildizidan ishga tushirish; (2) ildizga bo'sh conftest.py qo'yish (pytest uni ildiz sifatida tan oladi va yo'lga qo'shadi); (3) loyihani paket sifatida o'rnatish (pip install -e .). Eng oddiy boshlang'ich yechim β€” ildizdan ishga tushirish yoki ildizga conftest.py qo'yish.


🏠 README Β· ⬅️ Oldingi: 01 β€” Nega test yozamiz? Β· Keyingi: 03 β€” Test turlari va test piramidasi ➑️