Ачааллж байна...
Бүлэг 7, 8-д бидний тестлэсэн бүх функц нэг онцлогтой байсан: тоо, жагсаалт авч, тоо, текст буцаадаг, өөр юунд ч хүрдэггүй. Ийм функцийг тестлэх нь хялбар. Харин бодит программууд үүгээр хязгаарлагддаггүй. Таны зарлагын бүртгэл өгөгдлөө JSON файлаас уншиж, файлд хадгалдаг. Файлтай ажилладаг кодыг хэрхэн тестлэх вэ? Тест файл үүсгэх ёстой юу? Хаана? Таны жинхэнэ өгөгдлийн файлыг тест дарж бичвэл яах вэ? Тест дууссаны дараа үлдсэн файлуудыг хэн цэвэрлэх вэ?
Энэ хичээлд бид эдгээр асуултад pytest-ийн бэлэн fixture болох tmp_path-аар хариулна. Эхлээд жинхэнэ файлуудаар тестлэх нь яагаад аюултайг ойлгоно. Дараа нь tmp_path гэж юу болохыг, хаана байдгийг харна. Дадлагын фолдерт бараа хадгалах, ачаалах жижиг модуль бичиж, "хадгалаад буцааж ачаалахад ижил зүйл гарах ёстой" гэсэн тестээр нэгэн нууцлаг алдааг олно. 8.2 дугаар хичээлийн sample_items fixture-ийг tmp_path-тай хамт нэг тестэд ашиглана. Эцэст нь Windows дээрх кирилл үсэгтэй файлын нэгэн чухал асуудлыг тестээр хамгаална.
Химийн лабораторид туршилт хийхдээ эрдэмтэн хэзээ ч бодит үйлдвэрийн том саванд шууд туршдаггүй. Тэр цэвэр, тусдаа туршилтын шил авч, түүнд бага хэмжээний бодис хийж туршдаг. Туршилт бүр өөрийн шинэ шилтэй, учир нь өмнөх туршилтын үлдэгдэл үр дүнг гажуудуулж болно. Туршилт дууссаны дараа шилийг угааж, дараагийн туршилтад бэлдэнэ. Хэрвээ туршилт буруу явж, шил тэсэрсэн ч үйлдвэр эсэн мэнд үлдэнэ.
tmp_path бол тестийн туршилтын шил. Тест бүр өөрийн шинэ, хоосон, тусдаа түр фолдерыг авдаг. Тест тэнд хүссэн файлаа үүсгэж, бичиж, уншиж болно. Таны төслийн фолдер, таны жинхэнэ өгөгдөл бүрэн хөндөгдөхгүй. Нэг тестийн үүсгэсэн файл нөгөө тестэд харагдахгүй. Мөн pytest тэдгээр түр фолдеруудыг өөрөө цэвэрлэдэг тул таны компьютер тестийн хогоор дүүрэхгүй.
Эхлээд tmp_path-гүйгээр тестлэвэл юу болохыг бодъё. Доорх кодыг зөвхөн уншаарай, бичих шаардлагагүй.
python
def test_save_items():
save_items("data/items.json", [("талх", 2000, 2)])
assert load_items("data/items.json") == [("талх", 2000, 2)]Энэ тест ажиллах ч дор хаяж дөрвөн асуудал үүсгэнэ. Нэгдүгээрт, хэрвээ data/items.json таны жинхэнэ өгөгдлийн файл бол тест түүнийг дарж бичнэ: таны бүх бодит бүртгэл нэг талхаар солигдоно. Тест хэзээ ч таны өгөгдөлд хүрэх ёсгүй. Хоёрдугаарт, тест дууссаны дараа файл үлдэнэ. 5.2 дугаар хичээлийн цэвэр repository-ийн шалгах жагсаалтыг санаарай: тест бүр ажиллах бүрт хог үүсгэвэл таны фолдер, git status дүүрэх болно. Гуравдугаарт, тестүүд бие биедээ нөлөөлнө: хоёр тест нэг файл руу бичвэл тэдгээрийн үр дүн ажиллах дарааллаас хамаарна, 8.2 дугаар хичээлд ярьсан хамгийн хэцүү асуудлын нэг. Дөрөвдүгээрт, data фолдер байхгүй компьютер дээр, жишээ нь таны repository-г clone хийсэн хүний компьютер дээр, тест огт ажиллахгүй.
Энэ бүх асуудлыг нэг хэрэгсэл шийднэ.
Алхам 1: Тестлэх модуль
VS Code-д pytest-dadlaga фолдероо нээж, venv идэвхтэй эсэхийг шалгаад, үндсэн фолдерт storage.py нэртэй шинэ файл үүсгэнэ үү.
python
import json
def save_items(path, items):
with open(path, "w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, indent=2)
def load_items(path):
with open(path, encoding="utf-8") as f:
return json.load(f)Энэ кодыг та 4.4 дүгээр хичээлийн save_expenses-ээс, мөн өмнөх сургалтаас таньж байгаа. save_items баримтын бараануудыг JSON файлд бичнэ, load_items уншиж буцаана. “encoding” болон “ensure_ascii” хоёрын үүргийг хичээлийн дунд хэсэгт тестээр баталгаажуулна.
Алхам 2: tmp_path-тай танилцах
tmp_path бол pytest-ийн бэлэн fixture. 8.2 дугаар хичээлд fixture-ийг параметрийн нэрээр хүсдэг гэж сурсан: tmp_path-ийг ч яг ингэж хүснэ, тодорхойлох шаардлагагүй, pytest өөрөө бэлдэнэ. Түүнийг юу болохыг харахын тулд түр судалгааны тест бичье. tests фолдерт test_storage.py нэртэй файл үүсгэнэ үү.
python
def test_show_tmp_path(tmp_path):
print(tmp_path)
print(type(tmp_path))Энэ бол жинхэнэ тест биш, зөвхөн харахад зориулсан. Ажиллуулахдаа “-s” нэмэх хэрэгтэй: pytest анхдагчаар тестийн print-ийг нуудаг, “-s” нь тэр нуултыг унтраана.
Command:
pytest tests\test_storage.py -sГаралт (гол хэсэг):
collected 1 item
tests\test_storage.py C:\Users\Bat\AppData\Local\Temp\pytest-of-Bat\pytest-3\test_show_tmp_path0
<class 'pathlib.WindowsPath'>
.
============================== 1 passed in 0.02s ==============================print-ийн хоёр мөр файлын нэрийн араас, цэгийн өмнө гарч ирлээ. Эхний мөр бол tmp_path-ийн байршил. Замыг хэсэг хэсгээр уншъя. “C:\Users\Bat\AppData\Local\Temp” бол Windows-ийн түр файлуудын фолдер, программууд түр зүйлсээ хадгалдаг газар. “pytest-of-Bat” бол pytest-ийн таны хэрэглэгчид зориулсан фолдер. “pytest-3” бол pytest-ийн гурав дахь удаагийн хуралдаан, ажиллуулах бүрт тоо нэмэгдэнэ. “test_show_tmp_path0” бол яг энэ тестэд зориулсан фолдер, тестийн нэрээр нэрлэгдсэн. Таны хэрэглэгчийн нэр, тоо өөр байна.
Энэ байршил таны төслийн фолдероос бүрэн гадуур байгааг анзаараарай: тест тэнд юу ч хийсэн таны pytest-dadlaga хөндөгдөхгүй. pytest хамгийн сүүлийн гурван хуралдааны фолдерыг хадгалж, хуучныг нь автоматаар устгадаг. Ингэснээр тест унасан үед түүний үүсгэсэн файлуудыг нээж үзэх боломжтой, гэхдээ хог хязгааргүй хуримтлагдахгүй.
Хоёр дахь мөр tmp_path-ийн төрлийг харуулж байна: “pathlib.WindowsPath”. tmp_path бол энгийн текст биш, Path объект. Python-ий pathlib модулийн Path бол файлын замыг илэрхийлдэг объект бөгөөд замтай ажиллах олон тохиромжтой арга агуулдаг. Үүний хамгийн чухал хэрэглээг одоо харна. Энэ судалгааны тестийг устгана уу.
Алхам 3: Анхны файлын тест
test_storage.py-г дараах агуулгаар солино уу.
python
from storage import load_items, save_items
def test_save_creates_file(tmp_path, sample_items):
path = tmp_path / "items.json"
save_items(path, sample_items)
assert path.exists()Мөр бүрийг уншъя. Тест хоёр fixture хүсэж байна: tmp_path ба 8.2 дугаар хичээлд conftest.py-д тодорхойлсон sample_items. Нэг тест хэдэн ч fixture хүсэж болох бөгөөд pytest тус бүрийг бэлдэж өгнө. conftest.py-д байгаа тул энэ шинэ файлд sample_items-ийг тодорхойлох шаардлагагүй, 8.2 дугаар хичээлд яг энэ зорилгоор түүнийг тийш шилжүүлсэн.
“tmp_path / “items.json”” мөр шинэ. Path объектод “/” тэмдэг хуваах биш, замыг залгах үйлдэл хийдэг: түр фолдерын замд “items.json” файлын нэрийг нэмж, шинэ Path буцаана. Энэ нь Windows-ийн “” болон бусад системийн “/” тусгаарлагчийг автоматаар зөв хэрэглэдэг тул замыг текстээр залгахаас найдвартай. Энэ мөрийн дараа path нь “...\test_save_creates_file0\items.json” гэсэн, хараахан байхгүй файлыг заана.
save_items-ийг тэр замаар дуудна. open() функц Path объектыг текст замтай адил хүлээн авдаг тул storage.py-д юуг ч өөрчлөх шаардлагагүй. Эцэст нь “path.exists()” нь Path-ийн арга бөгөөд тэр замд файл байгаа эсэхийг True, False-оор хэлнэ.
Command:
pytest tests\test_storage.py -vГаралт:
collected 1 item
tests/test_storage.py::test_save_creates_file PASSED [100%]
============================== 1 passed in 0.02s ==============================Файл түр фолдерт үүссэн, тест давсан. pytest-dadlaga фолдероо Explorer-оос хараарай: items.json гэсэн файл байхгүй. Туршилт шилэнд болсон.
Алхам 4: Хадгалаад буцааж ачаалах
Файл үүссэн нь хангалтгүй, зөв агуулгатай байх ёстой. Хадгалах ба ачаалах функцуудын хамгийн чухал гэрээ бол: хадгалсан зүйлийг ачаалахад яг тэр зүйл буцаж ирэх ёстой. Үүнийг round-trip буюу эргэх аялал гэж нэрлэдэг. Файлын төгсгөлд нэмнэ үү.
python
def test_save_and_load_round_trip(tmp_path, sample_items):
path = tmp_path / "items.json"
save_items(path, sample_items)
assert load_items(path) == sample_itemsCommand:
pytest tests\test_storage.pyГаралт (уналтын блок):
________________________ test_save_and_load_round_trip _________________________
tmp_path = WindowsPath('C:/Users/Bat/AppData/Local/Temp/pytest-of-Bat/pytest-5/test_save_and_load_round_trip0')
sample_items = [('талх', 2000, 2), ('сүү', 3000, 1), ('өндөг', 500, 10)]
def test_save_and_load_round_trip(tmp_path, sample_items):
path = tmp_path / "items.json"
save_items(path, sample_items)
> assert load_items(path) == sample_items
E AssertionError: assert [['талх', 2000, 2], ['сүү', 3000, 1], ['өндөг', 500, 10]] == [('талх', 2000, 2), ('сүү', 3000, 1), ('өндөг', 500, 10)]
E
E At index 0 diff: ['талх', 2000, 2] != ('талх', 2000, 2)
E Use -v to get more diff
tests\test_storage.py:13: AssertionErrorТест унасан. 7.4 дүгээр хичээлийн аргаар уншъя. Блокийн эхэнд pytest хоёр fixture-ийн утгыг харуулж байна: tmp_path-ийн бүтэн зам, sample_items-ийн агуулга. E мөрийг анхааралтай хараарай. Хоёр жагсаалт эхний харцаар ижил: ижил бараанууд, ижил тоонууд. Гэхдээ “At index 0 diff” мөр ялгааг тод харуулж байна: ['талх', 2000, 2] ба ('талх', 2000, 2). Дөрвөлжин хаалт ба энгийн хаалт. Ачаалсан бараа бүр list, харин хадгалсан нь tuple.
Яагаад? JSON-д tuple гэсэн төрөл байдаггүй, зөвхөн жагсаалт буюу array бий. json.dump tuple-ийг JSON array болгон бичдэг, json.load JSON array-г Python list болгон уншдаг. Мэдээлэл нь алдагдаагүй ч төрөл нь өөрчлөгдсөн. Хадгалаад ачаалахад анхны зүйл буцаж ирээгүй.
Энэ бол жинхэнэ олдвор. Одоогоор receipt_total зэрэг функцууд list-тэй ч ажилладаг тул программ эвдрэхгүй байж болно. Гэхдээ хэн нэгэн хожим бараануудыг set-д хийх эсвэл dictionary-ийн түлхүүр болгох гэвэл list ажиллахгүй, tuple ажиллана. Тэр үед алдаа энэ хадгалах кодоос хол, огт өөр газар гарч, шалтгааныг олоход хэцүү байх байсан. Round-trip тест үүнийг одоо, эх сурвалж дээр нь барилаа.
7.4 дүгээр хичээлийн асуулт: код уу, тест үү? Манай гэрээ "хадгалсныг ачаалахад ижил зүйл буцна". Тест зөв, load_items-ийг засна. storage.py-д load_items-ийг өөрчилнө үү.
python
def load_items(path):
with open(path, encoding="utf-8") as f:
return [tuple(item) for item in json.load(f)]List comprehension ачаалсан жагсаалт бүрийг tuple болгон хувиргана.
Command:
pytest tests\test_storage.pyГаралт:
collected 2 items
tests\test_storage.py .. [100%]
============================== 2 passed in 0.02s ==============================Ачаалахыг тусад нь тестлэх
Round-trip тест хүчирхэг ч нэг сул талтай: хэрвээ хадгалах ба ачаалах функц хоёулаа ижил аргаар буруу бол, жишээ нь хоёулаа үнэ ба тоог сольж бичдэг бол, хоорондоо таарч, тест давна. Тиймээс ачаалах функцийг save_items-ээс хамааралгүйгээр, гараар бичсэн файлаар тестлэх нь сайн нэмэлт. Файлын төгсгөлд нэмнэ үү.
python
def test_load_reads_existing_file(tmp_path):
path = tmp_path / "items.json"
path.write_text('[["талх", 2000, 2]]', encoding="utf-8")
assert load_items(path) == [("талх", 2000, 2)]path.write_text() нь Path-ийн өөр нэг тохиромжтой арга: файл үүсгэж, өгсөн текстийг шууд бичнэ, open()-ийг with-тэй бичих шаардлагагүй. Бид JSON-ыг гараар бичсэн тул энэ тест save_items-д огт найдахгүй: зөвхөн load_items мэдэгдэж буй файлыг зөв уншиж байгааг шалгана.
Кирилл үсгийг хамгаалах
storage.py-ийн хоёр нарийн тохиргоог тестээр баталгаажуулъя. Эхнийх нь ensure_ascii=False. Тестийн төгсгөлд нэмнэ үү.
python
def test_saved_file_keeps_cyrillic(tmp_path, sample_items):
path = tmp_path / "items.json"
save_items(path, sample_items)
assert "талх" in path.read_text(encoding="utf-8")path.read_text() нь write_text-ийн эсрэг: файлын агуулгыг текстээр буцаана. Тест файлд "талх" гэсэн үг жинхэнэ кирилл үсгээр бичигдсэн эсэхийг шалгана. ensure_ascii=False-гүйгээр json.dump кирилл үсэг бүрийг “\u0442” гэх мэт код болгон бичдэг. Файл нь хүчинтэй JSON хэвээр, программ зөв уншина, гэхдээ хүний нүдээр унших боломжгүй болно. Энэ тест хэн нэгэн хожим энэ тохиргоог "илүүц" гэж устгахаас хамгаална.
Хоёр дахь нь encoding="utf-8". Энэ нь Windows дээр онцгой чухал. Хэрвээ open()-д encoding заахгүй бол Python үйлдлийн системийн анхдагч кодчилолыг ашигладаг бөгөөд Windows-ийн олон тохиргоонд энэ нь UTF-8 биш. Тэр үед кирилл үсэгтэй файл бичих оролдлого алдаа гаргаж эсвэл буруу тэмдэгтүүд бичнэ. Дээрх тест болон round-trip тест хоёулаа кирилл бараатай sample_items-ийг ашигладаг тул encoding устгагдвал энэ тестүүдийн аль нэг нь унана. Энэ нь sample_items-д "талх", "сүү" гэх мэт бодит монгол нэрс ашиглахын бас нэг давуу тал: латин нэрс энэ асуудлыг хэзээ ч илрүүлэхгүй байсан.
Байхгүй файл
Байхгүй файлыг ачаалах гэвэл юу болох вэ? open() FileNotFoundError гаргана. 8.4 дүгээр хичээлийн дүрмийг санацгаая: зөвхөн гэрээний нэг хэсэг болох алдааг тестлэ. Энэ алдааг бид өөрсдөө бичээгүй, open() гаргадаг. Гэхдээ "байхгүй файлыг ачаалахад FileNotFoundError гарна" гэдэг бол load_items-ийг дуудагчдад чухал гэрээ: тэд энэ алдааг барьж, хэрэглэгчид ойлгомжтой мессеж харуулах ёстой. 6.1 дүгээр хичээлийн "туршаад үзээрэй" хэсэгт ярьсан зарлагын бүртгэлийн data файлгүй үеийн асуудал яг үүнтэй холбоотой. Тиймээс бид энэ зан төлөвийг санаатайгаар гэрээ болгож, тестлэе.
python
def test_load_missing_file_raises(tmp_path):
with pytest.raises(FileNotFoundError):
load_items(tmp_path / "missing.json")Файлын эхэнд import pytest нэмнэ үү. tmp_path тест бүрт хоосон фолдер өгдөг тул “missing.json” байхгүй гэдэгт бид бүрэн итгэлтэй. Хэрвээ жинхэнэ фолдер ашигласан бол хэн нэгэн санамсаргүй ийм нэртэй файл үүсгэвэл тест унах эрсдэлтэй байсан. Энэ бол tmp_path-ийн бас нэг ашиг: тест бүр яг юу байгааг, юу байхгүйг мэддэг.
Command:
pytest -v -k storageГаралт:
tests/test_storage.py::test_save_creates_file PASSED [ 20%]
tests/test_storage.py::test_save_and_load_round_trip PASSED [ 40%]
tests/test_storage.py::test_load_reads_existing_file PASSED [ 60%]
tests/test_storage.py::test_saved_file_keeps_cyrillic PASSED [ 80%]
tests/test_storage.py::test_load_missing_file_raises PASSED [100%]Таван тест, тус бүр өөрийн туршилтын шилэнд. Нэг ч файл таны төсөлд үлдээгүй.
Хүсвэл tmp_path-ийн хамгаалалтыг өөрийн нүдээр харж үзээрэй. storage.py-ийн save_items-ээс “ensure_ascii=False”-ийг түр устгаж, pytest ажиллуулаарай: test_saved_file_keeps_cyrillic унаж, E мөрөнд файлын агуулга “\u0442\u0430...” гэх мэт кодуудаар дүүрсэн харагдана. Дараа нь тохиргоог буцааж нэмээрэй. Мөн тест унасны дараа түүний tmp_path фолдер руу File Explorer-оор очиж, items.json-ийг нээж үзэж болно: pytest сүүлийн хуралдаануудын фолдерыг хадгалдаг нь яг ийм шалгалтад зориулагдсан.
Замыг текстээр залгах.
E TypeError: unsupported operand type(s) for +: 'WindowsPath' and 'str'“‘WindowsPath’ ба ‘str’-ийн хооронд + үйлдэл дэмжигдэхгүй” гэсэн утгатай. Энэ нь tmp_path + "items.json" гэж бичсэн үед гарна. Path объект “+”-ээр текст залгахыг дэмждэггүй, “/” ашиглах ёстой: tmp_path / "items.json". Хэрвээ та заавал текст зам хэрэгтэй бол str(tmp_path / "items.json") гэж хувиргаж болно, гэхдээ open() болон ихэнх функцууд Path-ийг шууд хүлээн авдаг тул энэ нь ховор шаардлагатай.
Кодчилолын алдаа.
E UnicodeEncodeError: 'charmap' codec can't encode characters in position 9-12: character maps to <undefined>“‘charmap’ кодчилол 9-12 дугаар байрлал дахь тэмдэгтүүдийг кодлож чадсангүй” гэсэн утгатай. Энэ нь open()-д encoding="utf-8" заагаагүй үед Windows-ийн анхдагч кодчилол кирилл үсгийг бичиж чадахгүй байгаагийн шинж. “position 9-12” нь "талх" гэсэн үгийн байрлал байж болно. storage.py-ийн open() бүрт, мөн тестийн write_text ба read_text-д encoding="utf-8" байгаа эсэхийг шалгаарай. Энэ бол Windows дээр Python-оор текст файлтай ажиллах бүрт санах ёстой дүрэм.
tmp_path-ийн нэрийг буруу бичих. tmp_path бол fixture тул 8.2 дугаар хичээлийн “fixture ‘tmppath’ not found” гэх мэт алдаа гарна. “available fixtures” жагсаалтаас tmp_path-ийг хайж, нэрийг шалгаарай. Мөн tmpdir гэсэн хуучин fixture-ийг ч жагсаалтад харж болно: тэр нь pathlib-аас өмнөх үеийн хувилбар бөгөөд шинэ тестэд tmp_path-ийг ашиглана.
Тест төслийн фолдерт файл үлдээх. Хэрвээ тест ажиллуулсны дараа pytest-dadlaga фолдерт эсвэл жинхэнэ төсөлдөө шинэ JSON файл гарч ирсэн бол аль нэг тест tmp_path-ийн оронд текст зам ашиглаж байна. git status-ийг тест ажиллуулахаас өмнө ба дараа нь харьцуулах нь үүнийг илрүүлэх хамгийн энгийн арга. Тестийн файлуудаас “data/” эсвэл “.json” гэсэн текст замыг хайж, tmp_path-ээр солиорой.
Round-trip тест дан ганцаар найдах. Дээр ярьсанчлан хадгалах ба ачаалах хоёулаа ижил аргаар буруу бол round-trip тест давна. Тиймээс түүнийг үргэлж гараар бичсэн файлаас ачаалах тесттэй хослуулаарай. Хоёр тест хамтдаа "хоёр функц хоорондоо таарч байна" ба "файлын формат зөв" гэсэн хоёр өөр зүйлийг баталгаажуулдаг.
Файлтай ажилладаг кодыг жинхэнэ файлаар тестлэх нь жинхэнэ өгөгдлийг дарж бичих, хог үлдээх, тестүүдийг бие биедээ хамааруулах, бусад компьютер дээр ажиллахгүй байх эрсдэлтэй. pytest-ийн бэлэн fixture tmp_path тест бүрт системийн түр фолдерт шинэ, хоосон, тусдаа фолдер өгдөг бөгөөд сүүлийн гурван хуралдааных нь л хадгалагдана. tmp_path бол pathlib-ийн Path объект: “/” замыг залгадаг, exists() файл байгаа эсэхийг, write_text() ба read_text() файл бичих, уншихыг хялбарчилдаг, open() Path-ийг шууд хүлээн авдаг. Нэг тест tmp_path ба conftest.py-ийн sample_items зэрэг хэд хэдэн fixture-ийг зэрэг хүсэж болно. Round-trip тест хадгалсныг ачаалахад ижил зүйл буцаж ирэхийг шалгадаг бөгөөд JSON tuple-ийг list болгодог шиг нарийн асуудлыг илрүүлдэг, харин түүнийг гараар бичсэн файлаас ачаалах тесттэй хослуулна. Windows дээр текст файлтай ажиллахад encoding="utf-8" заавал хэрэгтэй, JSON-д кирилл үсгийг уншигдахуйц үлдээхэд ensure_ascii=False хэрэгтэй, хоёуланг нь кирилл өгөгдөлтэй тестээр хамгаална. pytest-ийн print-ийг харахын тулд “-s” ашиглана.
Дараагийн хичээлд тестлэх явцад анзаарагддаг нэг ерөнхий зүйлийн тухай ярина: зарим код тестлэхэд маш хялбар, зарим нь маш хэцүү байдаг. Цэвэр функц ба гаж нөлөөтэй функцийн ялгааг энгийн үгээр ойлгож, тестлэхэд хэцүү нэг функцийг жижиг өөрчлөлтөөр тестлэхэд хялбар болгоно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.