แบบฝึกหัด
ในแบบฝึกหัดนี้ คุณใช้ Pytest เพื่อทดสอบฟังก์ชัน จากนั้น คุณสามารถค้นหาและแก้ไขปัญหาบางอย่างที่อาจเกิดขึ้นกับฟังก์ชันที่ทําให้เกิดการทดสอบความล้มเหลว ดูความล้มเหลวและใช้การรายงานข้อผิดพลาดที่สมบูรณ์ของ Pytest เป็นสิ่งสําคัญในการระบุและแก้ไขการทดสอบปัญหาหรือข้อบกพร่องในรหัสการผลิต
สําหรับแบบฝึกหัดนี้ เราใช้ฟังก์ชันที่เรียกว่า admin_command() ที่ยอมรับคําสั่งระบบเป็นข้อมูลป้อนเข้า และอาจใส่คํานําหน้าด้วยเครื่องมือ sudo ฟังก์ชันมีบักที่คุณค้นพบโดยการเขียนการทดสอบ
ขั้นตอนที่ 1 - เพิ่มไฟล์ที่มีการทดสอบสําหรับแบบฝึกหัดนี้
ใช้มาตรฐานชื่อไฟล์ของ Python สําหรับไฟล์ทดสอบ สร้างไฟล์ทดสอบใหม่ ตั้งชื่อไฟล์ทดสอบ test_exercise.py และเพิ่มโค้ดต่อไปนี้:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return commandฟังก์ชัน
admin_command()รับรายการเป็นข้อมูลป้อนเข้าโดยใช้อาร์กิวเมนต์commandและอาจนําหน้ารายการด้วยsudoได้ ถ้าอาร์กิวเมนต์คําสําคัญsudoถูกตั้งค่าเป็นFalseจากนั้นจะส่งกลับคําสั่งเดียวกันกับที่ระบุเป็นข้อมูลป้อนเข้าในไฟล์เดียวกัน ผนวกการทดสอบสําหรับฟังก์ชัน
admin_command()การทดสอบใช้เมธอดตัวช่วยเหลือที่ส่งกลับคําสั่งตัวอย่าง:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
โน้ต
เป็นเรื่องปกติที่จะมีการทดสอบภายในไฟล์เดียวกันเป็นโค้ดจริง เพื่อความง่าย ตัวอย่างในแบบฝึกหัดนี้จะมีโค้ดจริงในไฟล์เดียวกัน ในโครงการ Python ในโลกแห่งความเป็นจริง การทดสอบโดยทั่วไปจะถูกแยกออกด้วยไฟล์และไดเรกทอรีจากโค้ดที่พวกเขากําลังทดสอบ
ขั้นตอนที่ 2 - เรียกใช้การทดสอบและระบุความล้มเหลว
ตอนนี้ไฟล์ทดสอบมีฟังก์ชันในการทดสอบและการทดสอบสองรายการเพื่อตรวจสอบลักษณะการทํางานแล้ว ถึงเวลาที่จะเรียกใช้การทดสอบและทํางานกับความล้มเหลว
ดําเนินการไฟล์ด้วย Python:
$ pytest test_exercise.pyการเรียกใช้ควรเสร็จสมบูรณ์ด้วยการส่งผ่านการทดสอบหนึ่งครั้งและความล้มเหลวหนึ่งครั้ง และผลลัพธ์ที่ล้มเหลวควรคล้ายกับผลลัพธ์ต่อไปนี้:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================ผลลัพธ์ล้มเหลวในการทดสอบ
test_sudo()Pytest จะให้รายละเอียดเกี่ยวกับสองรายการที่ถูกเปรียบเทียบ ในกรณีนี้ ตัวแปรresultไม่มีคําสั่งsudoซึ่งเป็นการทดสอบที่คาดหวัง
ขั้นตอนที่ 3 - แก้ไขข้อบกพร่องและทําการทดสอบผ่าน
ก่อนที่จะทําการเปลี่ยนแปลง คุณต้องทําความเข้าใจสาเหตุที่เกิดความล้มเหลวในตอนแรก แม้ว่าคุณจะเห็นว่าไม่เป็นไปตามความคาดหวัง (sudo ไม่ได้อยู่ในผลลัพธ์) แต่คุณต้องค้นหาสาเหตุ
ดูบรรทัดโค้ดต่อไปนี้จากฟังก์ชัน admin_command() เมื่อตรงตามเงื่อนไข sudo=True:
if sudo:
["sudo"] + command
ไม่มีการใช้การดําเนินการของรายการเพื่อส่งกลับค่า เนื่องจากไม่มีการส่งคืนฟังก์ชันจะส่งคืนคําสั่งโดยไม่มี sudo เสมอ
อัปเดตฟังก์ชัน
admin_command()เพื่อส่งคืนการดําเนินการรายการเพื่อให้ผลลัพธ์ที่ปรับเปลี่ยนถูกใช้เมื่อร้องขอคําสั่งsudoฟังก์ชันที่อัปเดตแล้วควรมีลักษณะดังนี้:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return commandรันการทดสอบด้วย Pytest อีกครั้ง ลองเพิ่มความละเอียดของเอาต์พุตโดยใช้ค่าสถานะ
-vที่มี Pytest:$ pytest -v test_exercise.pyในตอนนี้ ให้ตรวจสอบเอาต์พุต ซึ่งควรแสดงการทดสอบสองแบบที่ส่งผ่านในขณะนี้:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
โน้ต
เนื่องจากฟังก์ชันสามารถทํางานกับค่าของปลอกที่แตกต่างกันได้มากขึ้น ควรเพิ่มการทดสอบเพิ่มเติมเพื่อให้ครอบคลุมการเปลี่ยนแปลงเหล่านั้น ซึ่งจะป้องกันไม่ให้มีการเปลี่ยนแปลงในอนาคตไปยังฟังก์ชันทําให้เกิดลักษณะการทํางาน (ที่ไม่คาดคิด) ที่แตกต่างกัน
ขั้นตอนที่ 4 - เพิ่มรหัสใหม่ด้วยการทดสอบ
หลังจากเพิ่มการทดสอบในขั้นตอนก่อนหน้านี้แล้ว คุณควรรู้สึกสบายใจที่จะทําการเปลี่ยนแปลงฟังก์ชันมากขึ้นและตรวจสอบด้วยการทดสอบ แม้ว่าการเปลี่ยนแปลงนั้นไม่ได้ครอบคลุมโดยการทดสอบที่มีอยู่ คุณสามารถมั่นใจได้ว่าคุณไม่ได้สร้างสมมติฐานใด ๆ ก่อนหน้านี้
ในกรณีนี้ ฟังก์ชัน admin_command() จะเชื่อถือได้แบบตาบอดว่าอาร์กิวเมนต์ command จะเป็นรายการเสมอ มาลองปรับปรุงโดยตรวจสอบให้แน่ใจว่ามีข้อยกเว้นกับข้อความแสดงข้อผิดพลาดที่มีประโยชน์เกิดขึ้น
ก่อนอื่น สร้างการทดสอบที่บันทึกลักษณะการทํางาน แม้ว่าฟังก์ชันนี้ยังไม่ได้รับการอัปเดต ให้ลองวิธี test-first (หรือที่เรียกว่า Test Driven Development หรือ TDD)
- อัปเดตไฟล์ test_exercise.py เพื่อให้นําเข้า
pytestที่ด้านบน การทดสอบนี้ใช้ตัวช่วยเหลือภายในจากเฟรมเวิร์กpytest:
import pytest- ในตอนนี้ ให้ผนวกการทดสอบใหม่กับคลาสเพื่อตรวจสอบข้อยกเว้น การทดสอบนี้ควรคาดหวัง
TypeErrorจากฟังก์ชัน เมื่อค่าที่ส่งผ่านไปไม่ใช่รายการ:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)- อัปเดตไฟล์ test_exercise.py เพื่อให้นําเข้า
เรียกใช้การทดสอบอีกครั้งด้วย Pytest ผู้ใช้ทั้งหมดควรส่งผ่าน:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================การทดสอบนั้นดีพอที่จะตรวจสอบ
TypeErrorแต่จะเป็นการดีที่จะเพิ่มรหัสด้วยข้อความแสดงข้อผิดพลาดที่มีประโยชน์อัปเดตฟังก์ชันเพื่อสร้าง
TypeErrorอย่างชัดเจนด้วยข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return commandสุดท้าย ให้อัปเดตวิธีการ
test_non_list_commands()เพื่อตรวจสอบข้อความแสดงข้อผิดพลาด:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"การทดสอบที่อัปเดตใช้
errorเป็นตัวแปรที่เก็บข้อมูลข้อยกเว้นทั้งหมด เมื่อใช้error.value.argsคุณจะสามารถค้นหาอาร์กิวเมนต์ของข้อยกเว้นได้ ในกรณีนี้ อาร์กิวเมนต์แรกมีสตริงข้อผิดพลาดที่การทดสอบสามารถตรวจสอบได้
ตรวจสอบงานของคุณ
ในตอนนี้คุณควรมีไฟล์ทดสอบ Python ที่ชื่อ test_exercise.py ที่รวมถึง:
- ฟังก์ชัน
admin_command()ที่ยอมรับอาร์กิวเมนต์และอาร์กิวเมนต์คําสําคัญ - ข้อยกเว้น
TypeErrorที่มีข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์ในฟังก์ชันadmin_command() - คลาสการทดสอบ
TestAdminCommand()ที่มีวิธีการช่วยเหลือcommand()และสามวิธีการทดสอบที่ตรวจสอบฟังก์ชันadmin_command()
การทดสอบทั้งหมดควรส่งผ่านโดยไม่มีข้อผิดพลาดเมื่อคุณเรียกใช้การทดสอบทั้งหมดในเทอร์มินัล