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