Ачааллж байна...
Өмнөх хичээлд бид анхны тестүүдээ бичсэн бөгөөд тэд бүгд нэг төрлийн асуулт асуусан: "энэ үр дүн тэр тоотой тэнцүү юу?" Тэнцүүгийн шалгалт хамгийн түгээмэл нь үнэн ч ганцхан хэрэгсэл биш. Заримдаа бид яг утгыг мэдэхгүй ч "үр дүн анхны дүнгээс их байх ёстой" гэдгийг мэддэг. Заримдаа бүхэл жагсаалт биш, "энэ утга жагсаалтад байх ёстой" гэдгийг шалгахыг хүсдэг. Заримдаа үр дүн үнэн эсвэл худал байх ёстой. Энэ хичээлд бид assert-ийн эдгээр бүх хэлбэрийг сурна: тэнцүү эсэх, агуулж буй эсэх, их бага эсэх, үнэн худал эсэх.
Энэ хичээлийн хоёр дахь хагас нь компьютерийн бутархай тоонуудын тухай нэг сонирхолтой, анх удаа сонсоход гайхмаар үнэнд зориулагдсан. Тэр үнэн нь тэнцүүгийн энгийн шалгалтыг заримдаа эвдэж, зөв код ч тестийг уналгадаг. Бид энэ асуудлыг нууж, тойрохгүй, харин түүний шалтгааныг ойлгож, pytest-ийн тусгай хэрэгсэл болох pytest.approx-оор шударгаар шийднэ. Өмнөх хичээлийн pytest-dadlaga фолдер дээр үргэлжлүүлэн ажиллана.
Харуул шөнийн эргүүл хийхдээ ганцхан төрлийн асуулт асуудаггүй. Хаалганы хувьд "түгжээтэй юу?" гэж асууна: энэ бол тийм үгүй асуулт. Зочдын жагсаалтын хувьд "энэ хүний нэр жагсаалтад байна уу?" гэж асууна: энэ бол агуулж буй эсэхийн асуулт. Хөргөгчтэй агуулахын хувьд "температур хасах таван хэмээс доош байна уу?" гэж асууна: энэ бол харьцуулалтын асуулт, яг хэдэн хэм гэдэг нь хамаагүй, хязгаараас доош л байвал болно. Тоолуурын хувьд "яг 1500 гэж харуулж байна уу?" гэж асууна: энэ бол тэнцүүгийн асуулт.
Сайн харуул нөхцөл байдал бүрт тохирох асуултыг сонгодог. Хэрвээ тэр хөргөгчийн температурыг "яг хасах 7.3 хэм байна уу?" гэж асуувал хасах 7.2 хэм байхад ч дохио өгнө, тэгсэн ч агуулах хэвийн ажиллаж байгаа. Тест бичихэд ч яг ийм: хэт нарийн асуулт зөв кодыг уналгадаг, хэт ерөнхий асуулт алдааг алгасдаг. Энэ хичээлийн гол ур чадвар бол асуултаа зөв сонгох явдал.
Тестлэх функцууд
Эхлээд prices.py-д хэд хэдэн шинэ функц нэмье. VS Code-д pytest-dadlaga фолдероо нээж, prices.py-ийн төгсгөлд нэмнэ үү.
python
def format_price(amount):
return f"{amount:,}₮"
def prices_with_vat(prices):
return [add_vat(p) for p in prices]
def is_free(amount):
return amount == 0format_price нь 6.3 дугаар хичээлийн format_amount-тай ижил: мянгатын таслалтай, төгрөгийн тэмдэгттэй текст буцаана. prices_with_vat нь үнийн жагсаалтын үнэ бүрт НӨАТ нэмсэн шинэ жагсаалт буцаана. is_free нь дүн тэг эсэхийг True эсвэл False-оор хэлнэ. Эдгээр нь гурван өөр төрлийн утга буцаадаг: текст, жагсаалт, үнэн худал.
Terminal-д “(venv)” байгаа эсэхийг шалгаарай. Хэрвээ шинэ terminal нээсэн бол .\venv\Scripts\Activate.ps1-ээр venv-ээ дахин идэвхжүүлнэ.
Текстийн тэнцүү байдал
test_prices.py-ийн эхний мөрийн import-ийг шинэ функцуудыг багтаахаар өөрчилнө үү.
python
from prices import add_vat, format_price, is_free, prices_with_vatФайлын төгсгөлд тест нэмнэ үү.
python
def test_format_price():
assert format_price(20500) == "20,500₮"“==” тоонд ажилладагтай яг адил текстэд ч ажилладаг: хоёр текст тэмдэгт бүрээрээ ижил байх ёстой. Энэ нь хатуу шаардлага: хэрвээ функц “20,500 ₮” гэж зайтай буцаавал тест унана. Энэ хатуу байдал заримдаа хэрэгтэй, заримдаа хэтэрхий нарийн байдаг. Удахгүй үүнийг илүү уян хатан шалгах аргыг үзнэ.
Жагсаалтын тэнцүү байдал
python
def test_prices_with_vat():
assert prices_with_vat([1000, 2000]) == [1100, 2200]
def test_prices_with_vat_empty():
assert prices_with_vat([]) == []Жагсаалтад “==” нь хоёр жагсаалт ижил урттай, ижил дараалалтай, байрлал бүрт ижил утгатай эсэхийг шалгана. [1100, 2200] болон [2200, 1100] хоорондоо тэнцүү биш, учир нь дараалал өөр. Хоёр дахь тест хил дээрх тохиолдол: хоосон жагсаалт өгөхөд хоосон жагсаалт буцах ёстой. 6.1 дүгээр хичээлийн хоосон жагсаалтын алдааг санаарай: хил дээрх тохиолдлыг тест болгож бичих нь яг тийм алдаанаас хамгаалдаг. Dictionary-д ч “==” ижил зарчмаар ажилладаг: ижил түлхүүрүүд, тус бүрт ижил утга, харин түлхүүрийн дараалал хамаагүй.
Command:
pytestГаралт:
collected 6 items
test_prices.py ...... [100%]
============================== 6 passed in 0.01s ==============================Гаралтын толгой хэсэг өмнөх хичээлтэй адил тул энд товчилсон. “collected 6 items” бол өмнөх гурав ба шинэ гурав. Хэрвээ өмнөх хичээлийн "туршаад үзээрэй" хэсэгт remove_vat-ийн тест нэмсэн бол таны тоо илүү байна, энэ нь хэвийн.
Агуулж буй эсэх: in ба not in
Заримдаа бүхэл үр дүнг биш, түүний нэг хэсгийг шалгах нь илүү тохиромжтой. Python-ий in түлхүүр үгийг та жагсаалт, текст, dictionary-тай ажиллахдаа хэрэглэж байсан. Тестэд ч яг ижил ажилладаг.
python
def test_format_price_has_symbol():
assert "₮" in format_price(5)
def test_vat_price_in_list():
assert 1100 in prices_with_vat([1000, 500])Эхний тест format_price-ийн гаралтад төгрөгийн тэмдэгт байгаа эсэхийг л шалгана, тоо ямар хэлбэрээр бичигдсэн нь хамаагүй. Энэ нь зүйрлэлийн "уян хатан асуулт": хэрвээ хожим тоог өөр хэлбэрээр бичдэг болгосон ч тэмдэгт хэвээр бол энэ тест давсаар байна. Хоёр дахь тест жагсаалтад 1100 байгаа эсэхийг шалгана, бусад элемент болон дараалал хамаагүй.
Эсрэг шалгалт not in бөгөөд "агуулаагүй байх ёстой" гэсэн утгатай. Жишээ нь assert "$" not in format_price(5) нь үнэд долларын тэмдэг санамсаргүй орж ирээгүйг шалгана. Агуулж буй эсэхийн шалгалт тэнцүүгийн шалгалтаас сул: үр дүнгийн ганц хэсгийг л баталгаажуулдаг. Тиймээс чухал зан төлөвийг тэнцүүгээр, харин тодорхой нэг шинжийг in-ээр шалгах нь зөв хослол.
Харьцуулалт: >, <, >=, <=
Заримдаа яг утга биш, чиглэл чухал байдаг.
python
def test_vat_increases_price():
assert add_vat(5000) > 5000
def test_vat_never_decreases():
assert add_vat(1) >= 1Эхний тест "татвар нэмэхэд үнэ өсөх ёстой" гэсэн үндсэн дүрмийг шалгана. Яг хэд болох нь хамаагүй, гагцхүү их байвал болно. Хоёр дахь тест сонирхолтой хил дээрх тохиолдол: 1 төгрөгийн арван хувь нь 0.1, доош тайрвал 0, тиймээс add_vat(1) нь 1 буцаана, өсөхгүй. Тиймээс энд “>” биш “>=” буюу "их буюу тэнцүү" ашигласан. Хэрвээ “>” бичсэн бол энэ тест унах байсан бөгөөд энэ нь кодын алдаа биш, харин бидний асуултын алдаа. Харьцуулалтын тэмдгийг сонгохдоо хил дээрх утгад юу болох ёстойг нарийн бодох хэрэгтэй.
Үнэн ба худал
Функц True эсвэл False буцаадаг бол assert-д шууд өгч болно.
python
def test_zero_is_free():
assert is_free(0)
def test_positive_is_not_free():
assert not is_free(100)assert is_free(0) нь “is_free(0) үнэн байх ёстой” гэсэн утгатай. assert not is_free(100) нь "худал байх ёстой" гэсэн утгатай. assert is_free(0) == True гэж бичиж болох ч илүүц: assert аль хэдийн үнэн эсэхийг шалгадаг. Python-ий заншил энгийн хэлбэрийг илүүд үздэг бөгөөд Бүлэг 11-д сурах ruff хэрэгсэл ч == True-ийг анзаарч сануулдаг.
Нэг холбоотой хэлбэр: утга None эсэхийг шалгахдаа “==” биш “is” ашигладаг, жишээ нь assert result is None. Энэ нь Python-ий None-той ажиллах ерөнхий дүрэм бөгөөд тестэд ч хамаатай.
Бүгдийг ажиллуулъя.
Command:
pytestГаралт:
collected 12 items
test_prices.py ............ [100%]
============================= 12 passed in 0.02s ==============================Арван хоёр цэг, арван хоёр тест, бүгд давсан.
Нэг тест нэг санаа
Дээрх тест бүр нэг assert агуулж байгааг анзаараарай. Нэг тестэд хэд хэдэн assert бичих нь хориотой биш, заримдаа зүй ёсны. Гэхдээ сайн дадал бол нэг тест нэг санааг шалгах явдал. Шалтгаан нь тест уналтын үед тодорхой болдог: test_vat_increases_price унавал юу эвдэрсэн нь нэрнээс нь шууд мэдэгдэнэ. Харин арван өөр зүйлийг шалгасан test_everything унавал аль нь эвдэрснийг хайх хэрэгтэй, мөн эхний assert унахад pytest үлдсэн есийг шалгахгүй тул бусад алдаа нуугдана. Тиймээс нэг санаа, нэг тест, тодорхой нэр.
Гайхмаар туршилт
Одоо хичээлийн хоёр дахь хагас руу шилжье. Terminal-д дараах command-ыг бичнэ үү.
Command:
python -c "print(0.1 + 0.2)"Гаралт:
0.30000000000000004Энэ бол таны компьютерийн алдаа биш, Python-ий алдаа ч биш. Ихэнх програмчлалын хэл, бараг бүх компьютер яг ийм хариу өгдөг. Шалтгаан нь компьютер тоог хоёртын системд хадгалдагт оршино. Аравтын системд 1/3-ыг бүрэн бичих боломжгүй гэдгийг та мэднэ: 0.3333... гэж үргэлжилсээр байна, хаа нэгтээ зогсоход л бага зэрэг алдаа гарна. Хоёртын системд 0.1 нь яг ийм "төгсгөлгүй" тоо. Компьютер түүнийг хамгийн ойрын тоогоор хадгалдаг бөгөөд тэр ойролцоо утгуудыг нэмэхэд жижиг алдаа хуримтлагдаж, 0.30000000000000004 болдог. Алдаа нь маш жижиг, арван зургаа дахь орон дээр, гэхдээ “==” маш жижиг ялгааг ч уучлахгүй.
Тест дээр ямар харагддаг вэ
prices.py-ийн төгсгөлд дахин нэг функц нэмье: бүхэл тоогоор тайралгүй, яг тооцоолдог НӨАТ.
python
def add_vat_exact(amount):
return amount * 1.1test_prices.py-ийн import-д add_vat_exact нэмээд, төгсгөлд тест бичнэ үү. 100-ийн арван хувийг нэмэхэд 110 болох нь толгойд ойлгомжтой.
python
def test_add_vat_exact():
assert add_vat_exact(100) == 110Command:
pytestГаралт:
collected 13 items
test_prices.py ............F [ 92%]
================================== FAILURES ===================================
_____________________________ test_add_vat_exact ______________________________
def test_add_vat_exact():
> assert add_vat_exact(100) == 110
E assert 110.00000000000001 == 110
E + where 110.00000000000001 = add_vat_exact(100)
test_prices.py:53: AssertionError
=========================== short test summary info ===========================
FAILED test_prices.py::test_add_vat_exact - assert 110.00000000000001 == 110
========================= 1 failed, 12 passed in 0.03s ========================Таны анхны амжилтгүй тест. Үр дүнгийн мөрөнд арван хоёр цэгийн араас “F” үсэг гарсан: F нь failed буюу "унасан" гэсэн утгатай, ихэнх terminal-д улаан. Сүүлийн мөр улаан болж “1 failed, 12 passed” гэж байна. Энэ гаралтын бүх хэсгийг дараагийн хичээлд мөр мөрөөр нь бүрэн уншина. Одоохондоо зөвхөн нэг мөрийг харцгаая: “E assert 110.00000000000001 == 110”. pytest add_vat_exact(100)-ийн жинхэнэ үр дүнг харуулж байна: 110 биш, 110.00000000000001. Энэ нь яг 0.1 + 0.2-ийн туршилттай ижил үзэгдэл. Таны мөрийн дугаар 53-аас өөр байж болно.
Энд маш чухал зүйлийг тэмдэглэх хэрэгтэй: код буруу биш. 110.00000000000001 бол бодит амьдралд 110 төгрөг. Харин манай тест хэт нарийн асуулт асуусан: зүйрлэлийн "яг хасах 7.3 хэм үү" гэсэн асуулт. Бутархай тоог “==” ашиглаж шалгах нь бараг үргэлж ийм асуудал үүсгэх эрсдэлтэй.
Шийдэл: pytest.approx
pytest үүнд зориулсан хэрэгсэлтэй. test_prices.py-ийн хамгийн дээд хэсэгт import мөр нэмж, тестийг өөрчилнө үү.
python
import pytest
from prices import add_vat, add_vat_exact, format_price, is_free, prices_with_vatpython
def test_add_vat_exact():
assert add_vat_exact(100) == pytest.approx(110)“approx” нь approximately буюу "ойролцоогоор" гэсэн утгатай. pytest.approx(110) нь “110-тай маш ойрхон ямар ч тоо” гэсэн утгатай объект үүсгэдэг. “==” түүнтэй харьцуулахад зөрүү маш жижиг бол тэнцүү гэж үзнэ. Анхдагчаар "маш жижиг" гэдэг нь утгын саяны нэгээс бага зөрүү. 110-ын хувьд энэ нь ойролцоогоор 0.0001, харин манай зөрүү 0.00000000000001, хэдэн тэрбум дахин бага.
Command:
pytestГаралт:
collected 13 items
test_prices.py ............. [100%]
============================= 13 passed in 0.02s ==============================Бүгд дахин ногоон боллоо. Тест одоо зөв асуулт асууж байна: "үр дүн 110-тай практикт ижил үү?"
pytest.approx жагсаалттай ч ажилладаг: assert results == pytest.approx([1.1, 2.2, 3.3]) нь жагсаалтын элемент бүрийг ойролцоогоор харьцуулна. Хэрвээ илүү нарийн эсвэл илүү сул хязгаар хэрэгтэй бол pytest.approx(110, abs=0.01) гэж хэрэглэж болох бөгөөд “abs” нь абсолют зөрүү, энд нэг сотоос бага зөрүү гэсэн утгатай.
Мөнгө ба бутархай тооны тухай нэг шударга тэмдэглэл
Энэ туршлага бидний өмнөх нэг шийдвэрийн учрыг тайлбарлаж байна. Өмнөх хичээлд add_vat-ийг “//” бүхэл хуваалтаар бичсэн: үр дүн үргэлж бүхэл төгрөг. Тэр нь санамсаргүй сонголт биш. Мөнгөн дүнг бүхэл тоогоор хадгалах нь программд бутархай тооны алдаа орохоос сэргийлдэг. Бүхэл тоонууд дээр нэмэх, хасах, үржүүлэх нь үргэлж яг зөв байдаг тул тэдгээрийг “==” ашиглан тестлэхэд ямар ч асуудалгүй. Хэрвээ та төгрөгийн бутархай, жишээ нь мөнгө хэрэглэх шаардлагатай бол ч дүнг мөнгөөр, бүхэл тоогоор хадгалдаг арга түгээмэл. Python-д мөн яг аравтын тооцоолол хийдэг decimal модуль бий. Энэ сургалтад бид тэр сэдвийг гүнзгийрүүлэхгүй, гагцхүү нэг дүрмийг санаарай: бутархай тоог “==”-ээр биш, pytest.approx-оор тестлэ, мөнгийг боломжтой бол бүхэл тоогоор хадгал.
Хүсвэл prices.py-д apply_discount(price, percent) гэсэн функц нэмж үзээрэй: үнээс тодорхой хувийн хөнгөлөлт хасаад үр дүнг буцаана, жишээ нь price * (100 - percent) / 100. Дараа нь энэ хичээлийн бүх төрлийн assert-ийг ашиглан тестүүд бичээрэй: хөнгөлөлттэй үнэ pytest.approx-оор тодорхой утгатай тэнцүү эсэх, хөнгөлөлт эерэг бол үнэ буурах эсэх (“<”), тэг хувийн хөнгөлөлт үнийг өөрчлөхгүй эсэх. Хүлээгдэж буй утгуудыг толгойдоо бодоорой. Энэ функцийг бид Бүлэг 8-д ашиглана, тиймээс хүсвэл хадгалаад үлдээгээрэй.
Бутархай тоог “==”-ээр харьцуулах. Дээр харсанчлан энэ нь зөв кодыг уналгадаг. Хэрвээ тест “assert 0.30000000000000004 == 0.3” гэх мэт, хоёр утга бараг ижил боловч сүүлийн оронд нь ялгаатай мессежээр унавал энэ бол бутархай тооны асуудал, кодын алдаа биш. Хүлээгдэж буй утгыг pytest.approx(...)-оор боож, import pytest-ийг файлын эхэнд нэмнэ үү.
import pytest-ийг мартах.
def test_add_vat_exact():
> assert add_vat_exact(100) == pytest.approx(110)
^^^^^^
E NameError: name 'pytest' is not defined“NameError: name ‘pytest’ is not defined” нь “pytest гэдэг нэр тодорхойлогдоогүй” гэсэн утгатай. pytest-ийн өөрийн командыг ажиллуулж байгаа ч таны тестийн файл түүнийг import хийгээгүй тул доторх кодонд pytest гэдэг нэр байхгүй. “^^^^^^” тэмдэг нэрийг яг онож заажээ. Файлын хамгийн дээд хэсэгт import pytest нэмнэ үү.
Харьцуулалтын тэмдгийг буруу сонгох. Хэрвээ хил дээрх утгад тест унаад, гэтэл код зөв ажиллаж байгаа гэдэгт итгэлтэй байвал асуултаа шалгаарай. “>” ба “>=”, “<” ба “<=” хоёрын ялгаа яг хил дээр л илэрдэг. Дээрх add_vat(1)-ийн жишээ шиг өөрөөсөө асуугаарай: хил дээрх утгад юу болох ёстой вэ? Тэнцүү байж болох уу? Хариултаас нь тэмдгийг сонгоно.
Тестийн нэр давхцах. Хэрвээ нэг файлд хоёр тест ижил нэртэй бол, жишээ нь хоёулаа test_format_price, Python хоёр дахийг нь эхнийх дээр дарж бичнэ. pytest зөвхөн сүүлийнхийг олж, “collected” тоо таны бичсэнээс нэгээр бага байна. Энэ нь ямар ч алдааны мессеж гаргадаггүй тул анзаарагдахгүй байж болно. Тест бүрт давтагдашгүй, тодорхой нэр өгч, “collected” тоог тоолсон тестүүдтэйгээ үе үе харьцуулаарай. Бүлэг 11-д сурах ruff ийм давхардлыг ч анзаардаг.
Хэт ерөнхий шалгалт. assert format_price(20500) гэж бичвэл тест үргэлж давна, учир нь хоосон биш ямар ч текст үнэн гэж тооцогддог. Энэ тест функц ямар нэг юм буцааж байгааг л шалгаж, буруу үр дүнг огт барихгүй. Assert бичихдээ өөрөөсөө асуугаарай: хэрвээ функц буруу үр дүн буцаавал энэ assert унах уу? Хэрвээ үгүй бол асуулт хэт ерөнхий байна.
assert зөвхөн “==”-ээр хязгаарлагдахгүй. “==” тоо, текст, жагсаалт (дараалал чухал), dictionary (түлхүүрийн дараалал хамаагүй)-ийг бүрэн харьцуулдаг. in ба not in үр дүнгийн нэг хэсэг байгаа эсэхийг уян хатан шалгадаг. >, <, >=, <= чиглэлийг шалгадаг бөгөөд хил дээрх утгад тохирох тэмдгийг нарийн сонгох хэрэгтэй. True эсвэл False буцаадаг функцийг assert f() ба assert not f()-ээр, None-ийг is None-оор шалгана. Нэг тест нэг санааг тодорхой нэртэйгээр шалгах нь уналтыг ойлгомжтой болгодог. Компьютер бутархай тоог хоёртын системд ойролцоогоор хадгалдаг тул 0.1 + 0.2 нь 0.30000000000000004 болдог, иймээс бутархай тоог “==”-ээр биш, pytest.approx(...)-оор тестлэх ёстой, түүнд import pytest хэрэгтэй. Мөнгийг боломжтой бол бүхэл тоогоор хадгалах нь бутархай тооны асуудлаас бүрэн сэргийлдэг. Assert бүр функц буруу ажиллахад унах ёстой.
Дараагийн хичээлд энэ хичээлд товч харсан уналтын гаралтыг бүрэн, мөр мөрөөр нь уншина. Зориуд унах тест бичиж, FAILURES хэсэг, “>” ба “E” тэмдэглэгээ, жагсаалт болон текстийн ялгааг pytest хэрхэн харуулдгийг монголоор тайлбарлана. Энэ бол энэ сургалтын алдааны мессеж унших уламжлалын pytest дахь үргэлжлэл юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.