แบบฝึกหัด

เสร็จสมบูรณ์เมื่อ

ในแบบฝึกหัดนี้ คุณใช้ Pytest เพื่อทดสอบฟังก์ชัน จากนั้น คุณสามารถค้นหาและแก้ไขปัญหาบางอย่างที่อาจเกิดขึ้นกับฟังก์ชันที่ทําให้เกิดการทดสอบความล้มเหลว ดูความล้มเหลวและใช้การรายงานข้อผิดพลาดที่สมบูรณ์ของ Pytest เป็นสิ่งสําคัญในการระบุและแก้ไขการทดสอบปัญหาหรือข้อบกพร่องในรหัสการผลิต

สําหรับแบบฝึกหัดนี้ เราใช้ฟังก์ชันที่เรียกว่า admin_command() ที่ยอมรับคําสั่งระบบเป็นข้อมูลป้อนเข้า และอาจใส่คํานําหน้าด้วยเครื่องมือ sudo ฟังก์ชันมีบักที่คุณค้นพบโดยการเขียนการทดสอบ

ขั้นตอนที่ 1 - เพิ่มไฟล์ที่มีการทดสอบสําหรับแบบฝึกหัดนี้

  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จากนั้นจะส่งกลับคําสั่งเดียวกันกับที่ระบุเป็นข้อมูลป้อนเข้า

  2. ในไฟล์เดียวกัน ผนวกการทดสอบสําหรับฟังก์ชัน 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 เสมอ

  1. อัปเดตฟังก์ชัน 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
    
  2. รันการทดสอบด้วย Pytest อีกครั้ง ลองเพิ่มความละเอียดของเอาต์พุตโดยใช้ค่าสถานะ -v ที่มี Pytest:

    $ pytest -v test_exercise.py
    
  3. ในตอนนี้ ให้ตรวจสอบเอาต์พุต ซึ่งควรแสดงการทดสอบสองแบบที่ส่งผ่านในขณะนี้:

    ============================= 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 จะเป็นรายการเสมอ มาลองปรับปรุงโดยตรวจสอบให้แน่ใจว่ามีข้อยกเว้นกับข้อความแสดงข้อผิดพลาดที่มีประโยชน์เกิดขึ้น

  1. ก่อนอื่น สร้างการทดสอบที่บันทึกลักษณะการทํางาน แม้ว่าฟังก์ชันนี้ยังไม่ได้รับการอัปเดต ให้ลองวิธี 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)
    
  2. เรียกใช้การทดสอบอีกครั้งด้วย 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 แต่จะเป็นการดีที่จะเพิ่มรหัสด้วยข้อความแสดงข้อผิดพลาดที่มีประโยชน์

  3. อัปเดตฟังก์ชันเพื่อสร้าง 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
    
  4. สุดท้าย ให้อัปเดตวิธีการ 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()

การทดสอบทั้งหมดควรส่งผ่านโดยไม่มีข้อผิดพลาดเมื่อคุณเรียกใช้การทดสอบทั้งหมดในเทอร์มินัล