תרגיל

הושלם

בתרגיל זה, אתה משתמש ב- 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. תחילה, צור בדיקה הלוכדת את אופן הפעולה. למרות שהפונקציה עדיין לא עודכנה, נסה גישה לבדיקה ראשונה (שנקראת גם פיתוח מונחה בדיקה או 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() זו.

כל הבדיקות אמורות לעבור ללא שגיאות בעת הפעלתן במסוף.