Ачааллж байна...
Бүлэг 7-д бидний бүх тест нэг файлд, кодтой нэг фолдерт байсан. prices.py ба test_prices.py зэрэгцэн оршиж, тест арван дөрөв хүрсэн. Жижиг туршилтад энэ хангалттай, гэхдээ төсөл томрох тусам асуудал үүсдэг. Модуль тав, арав болоход кодын файлууд болон тестийн файлууд нэг фолдерт холилдож, юу код, юу тест болохыг ялгахад хэцүү болно. Нэг тестийн файлд хэдэн зуун тест хуримтлагдаж, тодорхой нэгийг олоход цаг ордог. Мөн та зөвхөн нэг хэсэг дээр ажиллаж байхдаа бүх тестийг дахин дахин ажиллуулах шаардлагагүй.
Энэ хичээлд бид тестүүдээ мэргэжлийн төслүүдийн дагадаг бүтцээр зохион байгуулна. Тестүүдийг tests нэртэй тусдаа фолдерт шилжүүлж, кодын модуль бүрт тусдаа тестийн файл үүсгэнэ. Энэ шилжүүлгийн явцад pytest-ийн нэг түгээмэл асуудалтай тулгарч, түүнийг жижиг тохиргооны файлаар шийднэ. Дараа нь бүх тестийн оронд зөвхөн нэг файл, нэг тест, эсвэл нэрээр нь сонгосон тестүүдийг ажиллуулж сурна. Эцэст нь энэ бүхнийг боломжтой болгодог нэрлэх дадлын тухай ярина. Бид pytest-dadlaga фолдер дээрээ үргэлжлүүлнэ.
Жижиг гэрийн номын санд бүх ном нэг тавиур дээр байж болно. Та хайж буй номоо нүдээр хараад олно. Харин хотын номын сан мянга мянган номтой бөгөөд тэнд номуудыг сэдвээр нь тавиурт хуваадаг: түүх нэг тавиурт, шинжлэх ухаан нөгөөд. Тавиур бүр шошготой, ном бүр дугаартай. Ингэснээр номын санч "энэ тавиурын бүх ном", "энэ нэг ном", эсвэл "нэрэндээ 'Монгол' гэсэн үгтэй бүх ном" гэх мэт хүссэн бүлгээ хэдхэн секундэд олдог. Энэ бүх систем нэг зүйлд тулгуурладаг: нэрлэх, байрлуулах дүрмийг бүгд баримталдаг.
Тестүүдийг зохион байгуулах нь яг ийм. tests фолдер бол тестэд зориулсан тусдаа танхим. Кодын модуль бүрт харгалзах тестийн файл бол сэдэв бүрийн тавиур. Тестийн функцийн нэр бол номын нэр. Нэрлэх дүрэм тууштай байвал pytest-ээс "энэ файлын тестүүд", "энэ нэг тест", эсвэл "нэрэндээ total гэсэн үгтэй бүх тест" гэж асууж болно.
Алхам 1: Хоёр дахь модуль
Тестүүдийг файлаар хуваахын тулд нэгээс олон модуль хэрэгтэй. VS Code-д pytest-dadlaga фолдероо нээж, venv идэвхтэй эсэхийг шалгаад, receipt.py нэртэй шинэ файл үүсгэнэ үү. Энэ модуль дэлгүүрийн баримт тооцоолно.
python
from prices import add_vat, format_price
def receipt_total(items):
subtotal = sum(price * qty for name, price, qty in items)
return add_vat(subtotal)
def receipt_lines(items):
return [f"{name} x{qty}: {format_price(price * qty)}" for name, price, qty in items]Баримтын бараа бүр нэр, үнэ, тоо ширхэгээс бүрдсэн tuple. receipt_total бүх барааны үнийг тоогоор нь үржүүлж нэмээд НӨАТ нэмнэ. receipt_lines бараа бүрийн мөрийг "талх x2: 4,000₮" хэлбэрээр бэлтгэнэ. Энэ модуль prices модулийг ашигладаг: хоёр модуль хоорондоо холбоотой, яг жинхэнэ төсөл шиг.
Алхам 2: tests фолдер үүсгэж, тестээ шилжүүлэх
VS Code-ийн Explorer хэсэгт pytest-dadlaga-ийн үндсэнд tests нэртэй шинэ фолдер үүсгэнэ үү. Дараа нь test_prices.py файлыг хулганаар чирж tests фолдер руу оруулна уу. Хэрвээ VS Code import-уудыг шинэчлэх эсэхийг асуувал “No” эсвэл “Skip” сонгоорой, бид import-ыг өөрсдөө удирдана.
Одоо бүтэц ийм болсон.
pytest-dadlaga/
prices.py
receipt.py
tests/
test_prices.py
venv/Код үндсэн фолдерт, тестүүд tests фолдерт. Хоёр ертөнц тусдаа, нэг харцаар ялгагдана.
Алхам 3: Түгээмэл асуудал
Command:
pytestГаралт:
============================= test session starts =============================
platform win32 -- Python 3.13.7, pytest-9.1.1, pluggy-1.6.0
rootdir: C:\Users\Bat\pytest-dadlaga
collected 0 items / 1 error
=================================== ERRORS ====================================
____________________ ERROR collecting tests/test_prices.py ____________________
ImportError while importing test module 'C:\Users\Bat\pytest-dadlaga\tests\test_prices.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
...
tests\test_prices.py:3: in <module>
from prices import add_vat, add_vat_exact, format_price, is_free, prices_with_vat
E ModuleNotFoundError: No module named 'prices'
=========================== short test summary info ===========================
ERROR tests/test_prices.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
============================== 1 error in 0.08s ===============================Бүлэг 7-ийн уналтын тайлангаас өөр зүйл гарч ирлээ. 7.4 дүгээр хичээлийн аргаар доороос нь уншъя. Сүүлийн мөр “1 error” буюу “1 алдаа” гэж байна, “failed” биш. Түүний дээрх анхаарлын тэмдэгтэй мөр “Interrupted: 1 error during collection” нь "цуглуулах явцад 1 алдаа гарсан тул тасаллаа" гэсэн утгатай. pytest тестүүдийг ажиллуулах хүртэл ч хүрсэнгүй, файлыг нээж тестүүдийг цуглуулах шатанд зогссон. Энэ бол чухал ялгаа: FAILED нь тест ажиллаад унасан, ERROR нь тест ажиллаж ч эхлээгүй гэсэн утгатай. Дээд хэсгийн “collected 0 items / 1 error” ч мөн үүнийг баталж байна.
ERRORS хэсгийн блокт шалтгаан бий. “ImportError while importing test module ...” нь "тестийн модулийг import хийх үед ImportError гарлаа" гэсэн утгатай. “Hint” мөр нь ерөнхий зөвлөгөө бөгөөд манай тохиолдолд хамаагүй. “Traceback:” доор “...” гэсэн хэсэг Python-ий дотоод import-ын механизмын мөрүүд бөгөөд тэдгээрийг алгасаж болно. Чухал нь сүүлийн хоёр мөр: tests\test_prices.py-ийн 3 дугаар мөрөнд prices-ийг import хийх гэж оролдсон, “E ModuleNotFoundError: No module named ‘prices’” буюу “prices нэртэй модуль олдсонгүй”.
Яагаад? Файлууд нүүхээс өмнө ажиллаж байсан шүү дээ. Шалтгаан нь Python модулиудыг хайдаг газруудтай холбоотой. Тест файлыг import хийхэд pytest тухайн тестийн файл байгаа фолдерыг Python-ий хайх газруудын жагсаалтад нэмдэг. Өмнө нь тестийн файл prices.py-тэй нэг фолдерт байсан тул prices олдсон. Одоо тестийн файл tests фолдерт байгаа тул pytest tests фолдерыг жагсаалтад нэмсэн, харин prices.py дээд талын фолдерт байгаа тул Python тэнд хайхгүй. Энэ бол tests фолдерт шилжих үед бараг бүх хүнд тохиолддог асуудал.
Алхам 4: pytest.ini тохиргоо
Шийдэл бол pytest-д төслийн үндсэн фолдерыг модуль хайх газар гэж хэлэх. Үүнийг тохиргооны файлаар хийдэг. VS Code-д pytest-dadlaga-ийн үндсэнд, tests фолдерт биш, pytest.ini нэртэй шинэ файл үүсгэж, дараахыг бичнэ үү.
ini
[pytest]
pythonpath = .
testpaths = testsМөр бүрийг уншъя. “[pytest]” бол хэсгийн гарчиг: "доорх тохиргоонууд pytest-д хамаарна". Энэ мөр заавал байх ёстой, түүнгүйгээр pytest файлыг тоохгүй. “pythonpath = .” нь "энэ фолдерыг модуль хайх газруудад нэм" гэсэн утгатай: цэг нь 2.1 дүгээр хичээлээс таньж байгаагаар "одоогийн фолдер", энд pytest.ini байгаа фолдер. Одоо Python prices болон receipt-ийг үндсэн фолдерт олно. “testpaths = tests” нь "тестүүдийг зөвхөн tests фолдерт хай" гэсэн утгатай. Энэ нь pytest-ийг хурдасгаж, мөн бусад фолдерт санамсаргүй тестийн нэртэй файл байсан ч цуглуулахаас сэргийлнэ.
Command:
pytestГаралт:
============================= test session starts =============================
platform win32 -- Python 3.13.7, pytest-9.1.1, pluggy-1.6.0
rootdir: C:\Users\Bat\pytest-dadlaga
configfile: pytest.ini
testpaths: tests
collected 14 items
tests\test_prices.py .............. [100%]
============================= 14 passed in 0.03s ==============================Толгой хэсэгт хоёр шинэ мөр гарч ирлээ. “configfile: pytest.ini” нь pytest тохиргооны файлыг олж уншсаныг баталдаг. Хэрвээ энэ мөр гарахгүй бол pytest таны файлыг олоогүй гэсэн үг. “testpaths: tests” нь хаана хайсныг заана. Файлын замын өмнө “tests” нэмэгдсэн нь тест одоо шинэ байрандаа байгааг харуулна. Бүх 14 тест дахин ногоон.
Энэ асуудлыг шийдэх өөр аргууд ч бий. Жишээ нь pytest-ийн оронд python -m pytest гэж ажиллуулбал Python одоогийн фолдерыг автоматаар хайх газарт нэмдэг тул тохиргоогүйгээр ажилладаг. Гэхдээ энэ нь command-ыг санаж байхыг шаарддаг. pytest.ini-ийн давуу тал нь тохиргоо файлд бичигдсэн, төсөлтэй хамт Git-д орж, хэн ч, хаана ч ердийн pytest бичихэд ажилладаг явдал.
Алхам 5: Модуль бүрт тестийн файл
Одоо receipt модульд өөрийн тестийн файл үүсгэе. tests фолдерт test_receipt.py нэртэй файл үүсгэж, дараахыг бичнэ үү.
python
from receipt import receipt_lines, receipt_total
def test_receipt_total_single_item():
assert receipt_total([("талх", 2000, 1)]) == 2200
def test_receipt_total_quantity():
assert receipt_total([("сүү", 3000, 2)]) == 6600
def test_receipt_total_empty():
assert receipt_total([]) == 0
def test_receipt_lines():
assert receipt_lines([("талх", 2000, 2)]) == ["талх x2: 4,000₮"]Хүлээгдэж буй утгуудыг толгойдоо бодъё. Нэг талх 2000, татвартай 2200. Хоёр сүү 6000, татвартай 6600. Хоосон баримт тэг. Хоёр талх 4000, хэлбэржүүлбэл “4,000₮”. Бүгд кодыг ажиллуулалгүй бодогдсон.
Command:
pytestГаралт:
collected 18 items
tests\test_prices.py .............. [ 77%]
tests\test_receipt.py .... [100%]
============================= 18 passed in 0.03s ==============================Одоо тестийн файл бүр өөрийн мөртэй. Мөрийн баруун талын хувь хэмжээ ахицыг харуулна: эхний файл дуусахад нийт тестийн 77 хувь ажилласан. Хэрвээ аль нэг файлд тест унавал аль модуль асуудалтай нь шууд харагдана.
Эцсийн бүтэц:
pytest-dadlaga/
prices.py
receipt.py
pytest.ini
tests/
test_prices.py
test_receipt.py
venv/Энэ бүтцийн дүрэм энгийн: кодын файл бүр prices.py, receipt.py гэх мэт, tests фолдерт харгалзах test_prices.py, test_receipt.py файлтай. Хэн ч энэ төслийг нээгээд receipt-ийн тестүүд хаана байгааг таамаглах шаардлагагүй. Нэр нь хэлж байна.
Тест олон болоход та тэдгээрийн зөвхөн нэг хэсэгтэй ажиллах хэрэгтэй болно. Жишээ нь receipt.py дээр ажиллаж байхдаа зөвхөн түүний тестүүдийг ажиллуулбал хурдан, тайлан нь богино.
Нэг файл
Command:
pytest tests\test_receipt.pyГаралт:
collected 4 items
tests\test_receipt.py .... [100%]
============================== 4 passed in 0.02s ==============================pytest-д файлын замыг өгвөл зөвхөн тэр файлын тестүүдийг цуглуулна. “collected 4 items” нь зөвхөн receipt-ийн дөрвөн тест. PowerShell-д “” ба “/” хоёулаа ажилладаг.
Нэг тест
Command:
pytest tests\test_receipt.py::test_receipt_total_empty -vГаралт:
collected 1 item
tests/test_receipt.py::test_receipt_total_empty PASSED [100%]
============================== 1 passed in 0.01s ==============================“::” тэмдгийг 7.2 дугаар хичээлийн pytest -v гаралтаас таньж байгаа: энэ нь файл болон түүн доторх тестийг холбодог. pytest-ийн тайлан тест бүрийг яг ийм хэлбэрээр нэрлэдэг бөгөөд энэ нэрийг тестийн node id гэдэг. 7.4 дүгээр хичээлийн “short test summary info” хэсгийн “FAILED tests/test_prices.py::test_add_vat” мөрийг санаарай: тэнд байгаа нэрийг хуулж, pytest-ийн араас тавибал яг тэр унасан тестийг ганцаар нь ажиллуулна. Нэг тестийн алдааг засаж байхдаа бусад бүгдийг дахин дахин ажиллуулахгүйн тулд энэ нь маш тохиромжтой.
Нэрээр сонгох: -k
Command:
pytest -k totalГаралт:
collected 18 items / 15 deselected / 3 selected
tests\test_receipt.py ... [100%]
======================= 3 passed, 15 deselected in 0.02s ======================“-k” нь keyword буюу түлхүүр үг гэсэн утгатай: нэрэндээ “total” гэсэн үгтэй тестүүдийг л сонгоно. “collected 18 items / 15 deselected / 3 selected” нь “18 цуглуулсан, 15-ыг хассан, 3-ыг сонгосон” гэсэн утгатай. receipt_total-ийн гурван тест сонгогдсон. Сүүлийн мөрөнд “15 deselected” буюу “15 сонгогдоогүй” гэж ч дурдагдана. -k нь илүү нарийн нөхцөл ч ойлгодог: -k "vat and not exact" нь “vat” агуулсан боловч “exact” агуулаагүй тестүүдийг сонгоно.
-k бүхэлдээ нэрлэх дадалд тулгуурладаг гэдгийг анзаараарай. Хэрвээ receipt_total-ийн тестүүд test_1, test_2, test_3 гэж нэрлэгдсэн бол “total” гэж хайхад юу ч олдохгүй байх байсан.
Энэ хичээлийн бүх боломж, файл бүрийг харгалзах модулиар нь олох, node id-оор нэг тест ажиллуулах, -k-ээр сонгох, нэрлэх тууштай дадалд тулгуурладаг. Тиймээс энэ дадлыг тодорхой томъёолъё.
Тестийн файл: test_ дээр кодын модулийн нэрийг залгана. prices.py-ийн тест test_prices.py, receipt.py-ийнх test_receipt.py. Нэг модульд нэг тестийн файл. Тестийн функц: test_ дээр тестлэж буй функцийн нэр, дараа нь нөхцөл байдал. test_receipt_total_empty гэдэг нь “receipt_total, хоосон тохиолдол”. test_add_vat_rounds_down гэдэг нь “add_vat, доош тайрах зан төлөв”. Ийм нэр нь өгүүлбэр шиг уншигддаг бөгөөд тест унах үед тайлан дээрх нэр нь кодыг нээхгүйгээр юу эвдэрснийг хэлдэг.
Муу нэрсийн жишээ: test_1, test_receipt2, test_it_works, test_new. Эдгээр нь -k-ээр хайгдахгүй, тайланд юу ч хэлэхгүй, хэдэн сарын дараа юуг шалгадгийг санахад хэцүү. 2.2 дугаар хичээлийн commit message, 3.1 дүгээр хичээлийн салааны нэр, 6.1 дүгээр хичээлийн issue-ийн гарчгийн нэгэн адил тестийн нэр бол ирээдүйн өөртөө бичиж буй захидал. Урт байх нь асуудал биш: test_receipt_total_with_several_items_and_quantities нь урт ч ойлгомжтой.
Мөн pytest тестүүдийг ангиудад, өөрөөр хэлбэл class дотор бүлэглэх боломжтой бөгөөд зарим төсөл үүнийг ашигладаг. Энэ сургалтад бид функц хэлбэрийн тестийг ашиглана, учир нь энэ нь хамгийн энгийн бөгөөд pytest-ийн бүх боломжтой бүрэн ажилладаг. Хэрвээ хожим ангитай тест харвал нэрлэх дүрэм ижил гэдгийг санаарай: анги нь “Test”-ээр эхэлдэг.
Хүсвэл receipt.py-д receipt_count(items) гэсэн функц нэмж үзээрэй: баримтын нийт барааны тоо ширхэгийг буцаадаг, жишээ нь хоёр талх, гурван сүү бол 5. Дараа нь test_receipt.py-д нэрлэх дадлын дагуу хоёр гурван тест бичээрэй: ердийн тохиолдол, хоосон баримт, ганц бараа. Тэгээд pytest -k count-ээр зөвхөн шинэ тестүүдээ ажиллуулж, тэдгээр нь сонгогдож байгааг шалгаарай. Хэрвээ сонгогдохгүй бол нэрээ шалгаарай.
tests фолдерт шилжүүлсний дараа ModuleNotFoundError. Энэ бол Алхам 3-ын асуудал. Хэрвээ pytest.ini-ийг үүсгэсэн хэрнээ алдаа гарсаар байвал гурван зүйл шалгаарай. Файл төслийн үндсэн фолдерт, tests фолдерт биш байрласан уу? Файлын нэр яг “pytest.ini” юу, “pytest.ini.txt” биш үү: VS Code-ийн Explorer-оос шалгаарай. Эхний мөр нь “[pytest]” юу? Pytest тохиргооны файлыг олсон эсэхийг гаралтын толгой дахь “configfile: pytest.ini” мөрөөр шалгаарай. Энэ мөр байхгүй бол pytest файлыг уншаагүй.
Ижил нэртэй хоёр тестийн файл. Хэрвээ төсөлд хоёр өөр фолдерт ижил нэртэй тестийн файл байвал, жишээ нь tests\test_utils.py болон tests\old\test_utils.py, pytest дараах алдааг гаргаж магадгүй.
import file mismatch:
imported module 'test_utils' has this __file__ attribute:
C:\Users\Bat\pytest-dadlaga\tests\old\test_utils.py
which is not the same as the test file we want to collect:
C:\Users\Bat\pytest-dadlaga\tests\test_utils.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules“import file mismatch” нь “import хийсэн файл таарахгүй байна” гэсэн утгатай. Python модулиудыг нэрээр нь танидаг тул ижил нэртэй хоёр файлыг ялгаж чадахгүй. HINT мөр шийдлийг хэлж байна: "тестийн файлууддаа давтагдашгүй нэр өг". Энэ нь нэрлэх дадлын бас нэг шалтгаан: модуль бүрт нэг тестийн файл, модулийн нэрээр, давхардалгүй.
-k юу ч сонгохгүй байх.
collected 18 items / 18 deselected / 0 selected
============================ 18 deselected in 0.02s ===========================“18 цуглуулсан, 18-ыг хассан, 0 сонгосон”. Таны бичсэн үг ямар ч тестийн нэрэнд байхгүй. Үгийн үсгийн алдааг шалгаарай. -k нь том жижиг үсгийг ялгадаггүй ч үгийн хэсгийг яг таарах ёстой. Хэрвээ тест таны хүлээж буй нэртэй байх ёстой хэрнээ сонгогдохгүй байгаа бол энэ нь тестийн нэр таны санаж байгаагаас өөр гэсэн дохио: pytest --collect-only нь тестүүдийг ажиллуулалгүй, зөвхөн бүх тестийн жагсаалтыг харуулдаг тул жинхэнэ нэрсийг шалгахад тустай.
Тестийн файлыг tests фолдерт биш, өөр газар үүсгэх. pytest.ini-д testpaths = tests гэж бичсэн тул tests фолдероос гадуур, жишээ нь үндсэн фолдерт үлдсэн тестийн файлыг ердийн pytest ажиллуулахад цуглуулахгүй. Хэрвээ шинэ тест бичсэн хэрнээ “collected” тоо өсөхгүй байвал файл зөв фолдерт байгаа эсэхийг шалгаарай.
Node id-д буруу тусгаарлагч. Нэг тестийг ажиллуулахдаа файл ба функцийг хоёр цэгээр “::” холбох ёстой. Нэг цэг “:” эсвэл зай ашиглавал pytest файлыг олохгүй, “ERROR: file or directory not found” гэсэн мессеж гарна. Хамгийн найдвартай арга бол pytest-ийн тайлан эсвэл pytest --collect-only-ийн гаралтаас node id-г хуулж авах явдал.
Төсөл томрох тусам тестүүдийг tests фолдерт, кодын модуль бүрт харгалзах test_<модуль>.py файлд зохион байгуулдаг. Тестийг tests фолдерт шилжүүлэхэд ModuleNotFoundError гарах нь түгээмэл, учир нь pytest тестийн файлын фолдерыг модуль хайх газарт нэмдэг. Үүнийг төслийн үндсэнд байрлах pytest.ini файлаар шийднэ: “[pytest]” гарчиг, “pythonpath = .” үндсэн фолдерыг хайх газарт нэмдэг, “testpaths = tests” тестийг хаана хайхыг заадаг. Гаралтын толгой дахь “configfile” мөр тохиргоо уншигдсаныг баталдаг. Цуглуулах үеийн алдаа ERROR гэж харагддаг бөгөөд FAILED-ээс ялгаатай нь тест ажиллаж ч эхлээгүй гэсэн утгатай. pytest файл нэг файлыг, pytest файл::тест нэг тестийг (node id), pytest -k үг нэрээр сонгосон тестүүдийг ажиллуулна. pytest --collect-only тестүүдийг ажиллуулалгүй жагсаадаг. Энэ бүх боломж нэрлэх тууштай дадалд тулгуурладаг: test_<модуль>.py файл, test_<функц>_<нөхцөл> функц.
Дараагийн хичээлд олон тест ижил бэлтгэл шаарддаг үед юу хийхийг сурна. Тестүүд бүгд ижил жишээ баримтаар эхэлдэг бол тэр бэлтгэлийг нэг удаа бичиж, бүх тестэд хуваалцах хэрэгсэл бол fixture юм. Бэлтгэсэн гал тогооны зүйрлэлээр түүнийг ойлгож, pytest-ийн @pytest.fixture-ийг ашиглана.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.