כתיבת טסטים בפייתון - מדריך למתחילים¶
בואו נדבר בכנות. כשמתחילים לתכנת, "בודקים" קוד בצורה אחת - מריצים אותו, מסתכלים על הפלט, ואם זה נראה נכון, ממשיכים הלאה. זה עובד כשהתוכנית שלכם היא עשרים שורות. זה מפסיק לעבוד ברגע שהפרויקט גדל, יש לכם עשרות פונקציות שתלויות אחת בשנייה, ואתם כבר לא זוכרים מה בדיוק בדקתם בפעם הקודמת. אז בואו נדבר על טסטים.
למה בדיקה ידנית לא מספיקה¶
תארו לעצמכם פונקציה שמחשבת הנחה על מחיר. כתבתם אותה, הרצתם עם כמה מספרים, ראיתם שהתוצאה הגיונית, המשכתם הלאה. שבוע אחר כך שיניתם משהו קטן בקוד כדי להוסיף פיצ'ר חדש, ובלי לשים לב שברתם את חישוב ההנחה. מי יגלה את זה? במקרה הטוב אתם, אחרי שעות של דיבוג. במקרה הרע - משתמש אמיתי, בייצור.
זה בדיוק התפקיד של טסטים אוטומטיים. במקום לבדוק ידנית בכל פעם שמשהו משתנה, כותבים קוד שבודק את הקוד, ומריצים אותו תוך שניות, שוב ושוב, כמה שרוצים.
הכלי - pytest¶
הספרייה הכי נפוצה לכתיבת טסטים בפייתון היא pytest. מתקינים אותה כמו כל חבילה אחרת:
עכשיו ניקח דוגמה פשוטה - פונקציה שמחשבת הנחה, בקובץ בשם discount.py:
def apply_discount(price, percent):
if percent < 0 or percent > 100:
raise ValueError("אחוז הנחה חייב להיות בין 0 ל-100")
return price - (price * percent / 100)
עכשיו נכתוב לה טסט, בקובץ נפרד בשם test_discount.py. השם test_ בתחילת הקובץ ובתחילת הפונקציות הוא לא מקרי - pytest מזהה לפיו אילו קבצים ואילו פונקציות הן טסטים:
from discount import apply_discount
def test_apply_discount_regular_case():
assert apply_discount(100, 10) == 90
def test_apply_discount_zero_percent():
assert apply_discount(50, 0) == 50
def test_apply_discount_invalid_percent_raises():
try:
apply_discount(100, 150)
assert False, "הייתה אמורה להיזרק שגיאה"
except ValueError:
pass
מה זה בעצם assert¶
assert היא מילת מפתח בפייתון שבודקת האם תנאי מסוים נכון. אם כן - כלום לא קורה, הקוד ממשיך. אם לא - נזרקת שגיאה. זה בדיוק מה שהופך אותה לכלי מושלם לטסטים - כותבים מה אתם מצפים שיקרה, ואם המציאות לא תואמת לציפייה, הטסט נכשל וצועק על זה.
הרצת הטסטים¶
מריצים את כל הטסטים בתיקייה בפקודה אחת:
הכלי סורק את כל הקבצים שמתחילים ב-test_, מריץ את כל הפונקציות שמתחילות ב-test_, ומדפיס לכם סיכום - כמה עברו, כמה נכשלו, ובאיזו שורה בדיוק. אם משהו נשבר, תראו מיד את השם המדויק של הטסט שנכשל ואת השורה שבה ה-assert לא התקיים.
האלטרנטיבה המובנית - unittest¶
פייתון מגיע גם עם מודול טסטים מובנה בשם unittest, בלי צורך להתקין כלום. אותו רעיון, אבל בסגנון מונחה עצמים במקום פונקציות פשוטות:
import unittest
from discount import apply_discount
class TestDiscount(unittest.TestCase):
def test_regular_case(self):
self.assertEqual(apply_discount(100, 10), 90)
if __name__ == "__main__":
unittest.main()
זה שימושי לדעת שהוא קיים, בעיקר בפרויקטים ותיקים או ארגוניים שכבר בנויים עליו, אבל ברוב הפרויקטים החדשים אנשים בוחרים ב-pytest כי הוא פשוט יותר לכתוב ולקרוא - אין צורך במחלקות, אין צורך ב-self.assertEqual, פשוט assert רגיל.
התועלת האמיתית - תפיסת רגרסיות¶
המילה החשובה כאן היא "רגרסיה" - מצב שבו שינוי חדש שובר משהו שכבר עבד. בלי טסטים, רגרסיות מתגלות אצל המשתמשים שלכם, או אצל חבר צוות שממש לא ציפה שהקוד שלו ישבור פיצ'ר בצד אחר לגמרי של המערכת. עם טסטים, כל שינוי שאתם מבצעים רץ מול כל מה שכבר נבדק בעבר, וברגע שמשהו נשבר, אתם יודעים תוך שניות, לפני שהקוד בכלל מגיע לייצור או למישהו אחר בצוות.
זה לא אומר שצריך לכתוב טסט לכל שורת קוד. בהתחלה, מספיק לכתוב טסטים לפונקציות החשובות באמת - חישובים, לוגיקה עסקית, כל דבר שאם הוא נשבר, זה יגרום נזק אמיתי. ההרגל הזה נבנה בהדרגה, כמו כל דבר אחר בתכנות.
מי שעוד לומד את היסודות של פייתון, כדאי לבנות קודם ביטחון בכתיבת פונקציות ולוגיקה בסיסית בקורס התכנות הבסיסי שלנו, ואז לחזור לנושא הטסטים כשהקוד שלכם כבר מספיק מורכב שבאמת שווה לבדוק אותו אוטומטית.
לסיכום¶
טסטים הם לא "עוד עבודה" מיותרת, הם ההשקעה שחוסכת לכם את הכי הרבה זמן בטווח הארוך. במקום לבדוק ידנית בכל פעם, אתם כותבים פעם אחת מה נכון, ונותנים למחשב לבדוק את זה שוב ושוב, מהר, בלי לשכוח כלום.