Ачааллж байна...
Өмнөх хичээлд бид тестүүдээ tests фолдерт, модуль бүрт тусдаа файлд зохион байгуулсан. Одоо тестийн файлын дотор харцгаая. Олон тест ижил бэлтгэлээр эхэлдэг: ижил жишээ баримт, ижил жагсаалт, ижил өгөгдөл. Тест бүрт тэр бэлтгэлийг дахин дахин бичвэл файл урт, давтагдсан болж, жишээ өгөгдлийг өөрчлөх шаардлага гарвал бүх тестийг нэг нэгээр нь засах хэрэгтэй болно. Энэ хичээлд бид энэ асуудлыг pytest-ийн хамгийн хүчирхэг боломжуудын нэг болох fixture-ээр шийднэ.
Fixture бол тестэд хэрэгтэй бэлтгэлийг нэг удаа тодорхойлж, түүнийг хүссэн тест бүрт автоматаар хүргэдэг хэрэгсэл юм. Бид эхлээд давтагдсан бэлтгэлтэй тестүүдийн асуудлыг харж, дараа нь @pytest.fixture-ээр жишээ баримтын fixture бичнэ. Fixture тест бүрт шинэ, цэвэр хуулбар өгдгийг туршилтаар баталгаажуулна. Эцэст нь fixture-ийг олон тестийн файлд хуваалцдаг conftest.py файлыг сурч, fixture-ийн хүрээний тухай нэг шударга тайлбар өгнө. Бид pytest-dadlaga фолдерын receipt модуль дээр ажиллана.
Рестораны гал тогоонд үйлчилгээ эхлэхээс өмнө тогооч нар нэг чухал ажил хийдэг. Тэд сонгино хэрчиж, мах зүсэж, соус бэлдэж, бүгдийг нь жижиг саванд хийж, ажлын байрандаа эмх цэгцтэй тавьдаг. Захиалга ирэхэд тогооч сонгино хэрчиж эхэлдэггүй, бэлэн савнаас шууд авдаг. Ингэснээр хоол бүр хурдан, ижил чанартай болдог. Мөн нэгэн чухал дүрэм бий: захиалга бүр өөрийн цэвэр орцоор хийгддэг. Хэрвээ нэг үйлчлүүлэгч хоолондоо давс нэмүүлбэл тэр нь дараагийн үйлчлүүлэгчийн хоолонд нөлөөлдөггүй.
Fixture бол яг энэ бэлтгэл. Та тестэд хэрэгтэй жишээ өгөгдлийг fixture функцэд нэг удаа бэлдэнэ. Тест "надад жишээ баримт хэрэгтэй" гэж хэлэхэд pytest бэлэн савнаас түүнд өгнө. Мөн гал тогооны дүрмийн нэгэн адил тест бүр өөрийн шинэ хуулбарыг авдаг: нэг тест өгөгдлийг өөрчилсөн ч дараагийн тестэд нөлөөлөхгүй. Энэ хоёр шинж, нэг удаа бэлдэх ба тест бүрт цэвэр хуулбар, fixture-ийн бүх үнэ цэнийг тодорхойлдог.
Fixture-гүйгээр бичвэл ямар харагдахыг эхлээд харцгаая. receipt модульд ердийн баримтаар хэд хэдэн зүйлийг шалгахыг хүсэж байна гэж бодъё: нийт дүн, мөрийн тоо, эхний мөрийн хэлбэр. Доорх кодыг зөвхөн уншаарай, файлд бичих шаардлагагүй.
python
def test_receipt_total_sample():
items = [("талх", 2000, 2), ("сүү", 3000, 1), ("өндөг", 500, 10)]
assert receipt_total(items) == 13200
def test_receipt_lines_count():
items = [("талх", 2000, 2), ("сүү", 3000, 1), ("өндөг", 500, 10)]
assert len(receipt_lines(items)) == 3
def test_receipt_lines_first():
items = [("талх", 2000, 2), ("сүү", 3000, 1), ("өндөг", 500, 10)]
assert receipt_lines(items)[0] == "талх x2: 4,000₮"Гурван тест, гурван удаа ижил жагсаалт. Энэ нь хэд хэдэн асуудал үүсгэдэг. Нэгдүгээрт, жагсаалтыг гурван газар бичсэн тул хэрэв жишээ баримтад бараа нэмэх шаардлага гарвал гурван газар засах хэрэгтэй, нэгийг нь мартах эрсдэлтэй. Хоёрдугаарт, давтагдсан мөрүүд тест бүрийн жинхэнэ санааг далдалдаг: уншигч "энэ тест юуг шалгаж байна вэ" гэдгийг олохын тулд урт бэлтгэлийг гүйлгэж харах хэрэгтэй. Гуравдугаарт, жагсаалт бүр бага зэрэг өөр болж эхлэх эрсдэлтэй: нэгэнд нь хэн нэгэн санамсаргүй өөр үнэ бичвэл тестүүд өөр өөр баримт шалгаж эхэлнэ.
Алхам 1: Fixture тодорхойлох
VS Code-д pytest-dadlaga фолдероо нээж, venv идэвхтэй эсэхийг шалгаад, tests\test_receipt.py-г нээнэ үү. Файлын хамгийн дээд хэсгийг дараах байдлаар өөрчилнө үү.
python
import pytest
from receipt import receipt_lines, receipt_total
@pytest.fixture
def sample_items():
return [
("талх", 2000, 2),
("сүү", 3000, 1),
("өндөг", 500, 10),
]Мөр бүрийг уншъя. “import pytest” нь 7.3 дугаар хичээлийн pytest.approx-ийн нэгэн адил pytest-ийн хэрэгслийг ашиглахад шаардлагатай. “@pytest.fixture” бол decorator: функцийн дээр “@” тэмдгээр бичигдэж, тухайн функцэд онцгой үүрэг өгдөг Python-ий хэлбэр. Decorator-ийн тухай та өмнөх сургалтаас санаж магадгүй: бэлгийг боох цаас шиг функцийг өөрчлөлгүйгээр түүнд шинэ шинж нэмдэг. Энд тэр нь pytest-д "энэ бол ердийн функц биш, fixture" гэж хэлж байна. sample_items функц өөрөө маш энгийн: жишээ баримтын жагсаалтыг буцаана. Fixture-ийн нэр нь функцийн нэр, энд sample_items.
Хүлээгдэж буй утгуудыг одоо толгойдоо бодъё, тестүүдэд хэрэг болно. Хоёр талх 4000, нэг сүү 3000, арван өндөг 5000. Нийлбэр 12000, арван хувийн татвартай 13200. Гурван бараа, гурван мөр. Эхний мөр "талх x2: 4,000₮".
Алхам 2: Fixture-ийг тестэд ашиглах
Файлын төгсгөлд гурван тест нэмнэ үү.
python
def test_receipt_total_sample(sample_items):
assert receipt_total(sample_items) == 13200
def test_receipt_lines_count(sample_items):
assert len(receipt_lines(sample_items)) == 3
def test_receipt_lines_first(sample_items):
assert receipt_lines(sample_items)[0] == "талх x2: 4,000₮"Шинэ зүйлийг анзаараарай: тест функцууд одоо параметртэй, sample_items. Энэ бол fixture-ийн хамгийн гайхалтай хэсэг. Та sample_items()-ийг хаана ч дуудаагүй, import ч хийгээгүй. Харин pytest тест ажиллуулахаасаа өмнө түүний параметрүүдийн нэрийг хардаг. “sample_items нэртэй параметр байна, ижил нэртэй fixture бий юу?” гэж шалгаад, олдвол fixture функцийг дуудаж, үр дүнг нь тестэд параметр болгон өгдөг. Тест өөрөө юуг ч бэлдэхгүй, зөвхөн "надад sample_items хэрэгтэй" гэж нэрээрээ хүсдэг. Энэ бол рестораны тогоочийн бэлэн савнаас орц авах үйлдэл.
Одоо тест бүр нэг мөр болсон бөгөөд тэр мөр зөвхөн тестийн жинхэнэ санааг агуулна. Жишээ баримтыг өөрчлөх шаардлага гарвал зөвхөн fixture-ийг засна.
Command:
pytest tests\test_receipt.py -vГаралт:
collected 7 items
tests/test_receipt.py::test_receipt_total_single_item PASSED [ 14%]
tests/test_receipt.py::test_receipt_total_quantity PASSED [ 28%]
tests/test_receipt.py::test_receipt_total_empty PASSED [ 42%]
tests/test_receipt.py::test_receipt_lines PASSED [ 57%]
tests/test_receipt.py::test_receipt_total_sample PASSED [ 71%]
tests/test_receipt.py::test_receipt_lines_count PASSED [ 85%]
tests/test_receipt.py::test_receipt_lines_first PASSED [100%]
============================== 7 passed in 0.02s ==============================Өмнөх хичээлийн дөрвөн тест болон гурван шинэ тест бүгд давлаа. Хэрвээ та 8.1 дүгээр хичээлийн "туршаад үзээрэй" хэсэгт receipt_count-ийн тестүүд нэмсэн бол тоо илүү байна. Fixture-ийг ашиглаагүй тестүүд, жишээ нь test_receipt_total_empty, ердийнхөөрөө ажилласаар байна: fixture зөвхөн түүнийг нэрээр нь хүссэн тестэд л хүргэгдэнэ.
Туршилт: fixture-ийн өгөгдлийг өөрчлөх
Гал тогооны хоёр дахь дүрмийг, захиалга бүр өөрийн цэвэр орцтой, баталгаажуулъя. Энэ нь яагаад чухалыг ойлгохын тулд өгөгдлийг өөрчилдөг тест бичнэ. Энэ тестийг fixture-ийн шууд доор, бусад sample_items тестүүдийн өмнө нэмнэ үү.
python
def test_adding_item_changes_total(sample_items):
sample_items.append(("ус", 1000, 1))
assert receipt_total(sample_items) == 14300Энэ тест жишээ баримтад нэг ус нэмээд нийт дүнг шалгана: 12000 дээр 1000 нэмбэл 13000, татвартай 14300. Энд нэг эрсдэл бий: тест fixture-ийн жагсаалтыг өөрчилсөн. Хэрвээ бүх тест нэг жагсаалтыг хуваалцдаг байсан бол энэ тестийн дараа ажиллах test_receipt_total_sample дөрвөн бараатай жагсаалт авч, 13200-ийн оронд 14300 гаргаж унах байсан. Мөн test_receipt_lines_count гурвын оронд дөрвөн мөр олох байсан. Тестүүд бие биедээ нөлөөлж, нэгийн өөрчлөлт нөгөөг нь уналгах байсан.
Command:
pytest tests\test_receipt.pyГаралт:
collected 8 items
tests\test_receipt.py ........ [100%]
============================== 8 passed in 0.02s ==============================Бүгд давлаа. Шалтгаан нь pytest тест бүрийн өмнө sample_items функцийг шинээр дууддаг явдал: fixture функц шинэ жагсаалт үүсгэж буцаадаг тул тест бүр өөрийн, бусдаас тусдаа хуулбартай. test_adding_item_changes_total өөрийн хуулбарт ус нэмсэн ч тэр хуулбар тест дуусахад алга болно. Дараагийн тест цэвэр, гурван бараатай шинэ хуулбар авна.
Энэ бол fixture-ийн ердийн хувьсагчаас давуу тал. Хэрвээ та файлын дээд хэсэгт SAMPLE = [...] гэж ердийн хувьсагч тодорхойлж, бүх тестэд ашигласан бол тэр нь бүх тестэд нэг жагсаалт байх бөгөөд дээрх туршилт бусад тестийг уналгах байсан. Тестүүдийн дараалал өөрчлөгдөхөд өөр өөр тест унаж, "өчигдөр ажиллаж байсан тест өнөөдөр яагаад унаж байна" гэсэн хамгийн хэцүү асуудлын нэг үүсэх байсан. Тестүүд бие биеэсээ бүрэн тусгаар байх ёстой: тест бүр ганцаараа, ямар ч дараалалд ажиллаж чадах ёстой.
Fixture-ийг олон файлд хуваалцах: conftest.py
Одоогоор sample_items зөвхөн test_receipt.py дотор байна, тиймээс зөвхөн тэр файлын тестүүд ашиглаж чадна. Хэрвээ өөр тестийн файлд ч ижил жишээ баримт хэрэгтэй бол яах вэ? Fixture-ийг хуулж тавих нь давхардал руу буцаж байгаа хэрэг. pytest-д үүнд зориулсан тусгай файл бий: conftest.py.
tests фолдерт conftest.py нэртэй шинэ файл үүсгэж, fixture-ийг test_receipt.py-ээс тийш нь шилжүүлнэ үү.
python
import pytest
@pytest.fixture
def sample_items():
return [
("талх", 2000, 2),
("сүү", 3000, 1),
("өндөг", 500, 10),
]Дараа нь test_receipt.py-ээс fixture-ийг устгана уу. Тестүүдийн sample_items параметрүүдийг хэвээр үлдээнэ. test_receipt.py-д fixture ашигладаг өөр pytest хэрэгсэл байхгүй бол “import pytest” мөрийг ч устгаж болно, үлдээсэн ч хор хөнөөлгүй.
Command:
pytestГаралт:
collected 22 items
tests\test_prices.py .............. [ 63%]
tests\test_receipt.py ........ [100%]
============================= 22 passed in 0.03s ==============================Бүх тест давсан, хэдийгээр test_receipt.py-д sample_items-ийн тодорхойлолт ч, import ч байхгүй. Учир нь pytest conftest.py нэртэй файлыг автоматаар олж уншдаг бөгөөд түүн доторх fixture-ууд тэр фолдер болон түүнээс доорх бүх фолдерын тестэд хүртээмжтэй болдог. conftest.py-г хэзээ ч import хийдэггүй, pytest өөрөө ачаалдаг. Нэр нь “configuration test” буюу "тестийн тохиргоо" гэдгээс гаралтай.
Хэзээ fixture-ийг тестийн файлд, хэзээ conftest.py-д байрлуулах вэ? Энгийн дүрэм: fixture-ийг ашигладаг цорын ганц файлд нь байрлуул. Хоёр ба түүнээс олон тестийн файл ашиглах үед л conftest.py руу шилжүүл. Ингэснээр fixture-ийн тодорхойлолт түүнийг ашигладаг тесттэй аль болох ойр байна.
Fixture-ийн хүрээний тухай нэг шударга тайлбар
Fixture анхдагчаар тест бүрийн өмнө шинээр дуудагддаг гэдгийг бид харсан. Үүнийг fixture-ийн scope буюу хүрээ гэдэг бөгөөд анхдагч хүрээ нь “function”, өөрөөр хэлбэл тест функц бүрт нэг удаа. pytest өөр хүрээнүүдийг ч дэмждэг: @pytest.fixture(scope="module") нь нэг тестийн файлд нэг удаа, scope="session" нь бүх тестийн хуралдаанд нэг удаа fixture-ийг дуудна. Эдгээр нь бэлтгэл нь маш үнэтэй үед, жишээ нь өгөгдлийн сантай холбогдох эсвэл том файл ачаалахад хэдэн секунд шаардагдах үед хэрэгтэй. Гэхдээ хүрээ томрох тусам тестүүд нэг объектыг хуваалцаж эхэлдэг бөгөөд дээрх туршилтын яг тэр аюул буцаж ирнэ: нэг тест өгөгдлийг өөрчилбөл бусдад нөлөөлнө. Энэ сургалтын бүх fixture-ийн бэлтгэл хурдан тул бид үргэлж анхдагч хүрээг ашиглана. Хүрээг томруулах шаардлага гарсан үед тестүүдийн тусгаар байдлыг алдах эрсдэлийг ухамсартайгаар жигнэх ёстой гэдгийг санаарай.
Fixture-ийн бусад боломжууд ч бий: fixture өөр fixture-ийг параметрээр хүсэж болно, мөн тестийн дараа цэвэрлэгээ хийх шаардлагатай үед return-ийн оронд yield ашиглаж болно. Энэ бүхнийг энэ сургалтад гүнзгийрүүлэхгүй. Харин 9.1 дүгээр хичээлд pytest-ийн өөрийн бэлэн fixture болох tmp_path-тай танилцана: тэр нь тест бүрт түр фолдер бэлдэж, тестийн дараа цэвэрлэдэг.
Хүсвэл conftest.py-д хоёр дахь fixture нэмж үзээрэй: empty_items нэртэй, хоосон жагсаалт буцаадаг. Дараа нь test_receipt.py-д түүнийг ашигладаг тестүүд бичээрэй: хоосон баримтын нийт дүн тэг, мөр хоосон жагсаалт. Мөн 8.1 дүгээр хичээлийн receipt_count-ийг бичсэн бол sample_items-ээр түүний тест нэмээрэй: хоёр талх, нэг сүү, арван өндөг нийт хэдэн ширхэг вэ? Хариуг кодыг ажиллуулалгүй толгойдоо бодоод, тестэд бичээрэй.
Fixture-ийн нэрийг буруу бичих. Хэрвээ тестийн параметрийн нэр fixture-ийн нэртэй яг таарахгүй бол, жишээ нь sample_items-ийн оронд sample_item, pytest дараах алдааг гаргана.
_________________ ERROR at setup of test_receipt_total_sample _________________
file C:\Users\Bat\pytest-dadlaga\tests\test_receipt.py, line 22
def test_receipt_total_sample(sample_item):
E fixture 'sample_item' not found
> available fixtures: cache, capfd, capsys, ..., monkeypatch, ..., sample_items, ..., tmp_path, tmp_path_factory, ...
> use 'pytest --fixtures [testpath]' for help on them.“ERROR at setup of ...” нь “... тестийн бэлтгэлийн үед алдаа гарлаа” гэсэн утгатай. 8.1 дүгээр хичээлийн FAILED ба ERROR-ийн ялгааг санаарай: тест ажиллаж ч эхлээгүй, бэлтгэлийн шатанд зогссон. “fixture ‘sample_item’ not found” нь “sample_item нэртэй fixture олдсонгүй”. Хамгийн тустай нь дараагийн мөр: “available fixtures” буюу "боломжтой fixture-ууд" гэж pytest өөрийн бэлэн fixture-ууд болон таны тодорхойлсон fixture-уудын бүрэн жагсаалтыг харуулдаг. Таны жагсаалт энд товчилсоноос урт байна. Тэр жагсаалтаас sample_items-ийг олж, үсгийн алдааг харж болно. Хураангуйд энэ тест “ERROR tests/test_receipt.py::test_receipt_total_sample” гэж харагдана.
@pytest.fixture-ийг мартах. Хэрвээ fixture функцийн дээрх decorator мартагдвал pytest түүнийг ердийн функц гэж үзэж, дээрх “fixture not found” алдааг гаргана. Функц байгаа хэрнээ pytest олохгүй байвал decorator байгаа эсэхийг эхлээд шалгаарай.
Fixture-ийг шууд дуудах. Хэрвээ тестийн дотор items = sample_items() гэж fixture-ийг ердийн функц шиг дуудвал pytest дараах мессежтэй алдаа гаргана.
Fixture "sample_items" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.“sample_items fixture-ийг шууд дуудлаа. Fixture-уудыг шууд дуудах ёсгүй, харин тест функцууд тэдгээрийг параметрээр хүсэхэд автоматаар үүсдэг” гэсэн утгатай. Мессеж өөрөө шийдлийг хэлж байна: fixture-ийг тестийн параметрийн жагсаалтад нэрээр нь бич, pytest бусдыг нь хийнэ.
return-ийг мартах. Хэрвээ fixture функц жагсаалтыг үүсгэсэн боловч return хийхээ мартвал Python автоматаар None буцаана. Тест sample_items-ийн оронд None авч, ихэвчлэн код дотор “TypeError: ‘NoneType’ object is not iterable” гэх мэт алдаагаар унана. “NoneType” гэсэн үг fixture-ийн уналтын тайланд гарвал fixture функцийн return мөрийг шалгаарай.
conftest.py-ийн нэр эсвэл байрлал буруу. pytest зөвхөн яг “conftest.py” нэртэй файлыг автоматаар уншдаг: “conftests.py” эсвэл “conf_test.py” ажиллахгүй. Мөн conftest.py-ийн fixture-ууд зөвхөн тэр файл байгаа фолдер болон түүнээс доорх фолдеруудын тестэд хүрнэ. Хэрвээ conftest.py-г tests фолдерт биш, өөр газар үүсгэвэл tests доторх тестүүд түүний fixture-уудыг олохгүй байж магадгүй.
Fixture бол олон тестэд хэрэгтэй бэлтгэлийг нэг удаа тодорхойлж, тест бүрт хүргэдэг pytest-ийн хэрэгсэл бөгөөд урьдчилан бэлдсэн гал тогоотой адил. Fixture-ийг @pytest.fixture decorator бүхий функцээр тодорхойлж, бэлтгэсэн утгыг return хийнэ. Тест fixture-ийг параметрийн нэрээр хүсдэг бөгөөд pytest нэрийг таарсан fixture-тэй холбож, үр дүнг тестэд автоматаар өгнө: fixture-ийг хэзээ ч шууд дууддаггүй. Fixture анхдагч “function” хүрээтэй тул тест бүр шинэ, цэвэр хуулбар авдаг бөгөөд нэг тестийн өөрчлөлт нөгөөд нөлөөлдөггүй. Энэ нь ердийн хувьсагчаас давуу тал юм. Олон тестийн файлд хуваалцах fixture-ийг tests фолдерын conftest.py-д байрлуулдаг бөгөөд pytest түүнийг import-гүйгээр автоматаар уншдаг. “module” ба “session” хүрээ үнэтэй бэлтгэлд хэрэгтэй ч тестүүдийн тусгаар байдлыг эрсдэлд оруулдаг тул энэ сургалтад анхдагчийг ашиглана. Fixture-ийн нэр буруу бол “ERROR at setup” ба “fixture not found” гарч, боломжтой fixture-уудын жагсаалт харагдана.
Дараагийн хичээлд өөр төрлийн давхардлыг шийднэ. Нэг функцийг олон өөр оролтоор шалгах үед, жишээ нь add_vat-ийг арван өөр үнээр, ихэнх тест бүтэц нь ижил, зөвхөн тоо нь өөр байдаг. pytest-ийн parametrize нь ийм тестийг нэг удаа бичиж, хүснэгт шиг олон тохиолдлоор ажиллуулах боломж олгодог.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.