• בלוג
  • שלושה סוגים של בדיקות יחידה

שלושה סוגים של בדיקות יחידה

05/01/2022

דייב פארלי טוען שיש רק שלושה סוגים של בדיקות יחידה. אם יש לכם רבע שעה פנויה שווה לצפות ובכל מקרה חשבתי לתמצת את עיקרי הדברים. אז הנה שלושת הסוגים של בדיקות יחידה שאי פעם תצטרכו:

1. בדיקת מידע שחוזר מפונקציה

הסוג הראשון והכי קל להבנה הוא בדיקה שצריכה לבדוק שפונקציה מסוימת מחזירה את התוצאה הנכונה עבור קלט מסוים. לצורך הדוגמה אם אנחנו בודקים את הקוד של מערכים ב JavaScript, נוכל לבדוק שהמאפיין length של מערך באמת מחזיר את האורך שלו:

test('array length returns its size', () => {
  const arr = [1, 2, 5];
  expect(arr.length).toEqual(3);
});

בבדיקות כאלה חשוב לבדוק כמה שיותר מקרים שונים. אולי יהיה מעניין לבדוק מערך ריק ובטוח יהיה מעניין לבדוק מערך עם חורים. ככל שנבדוק יותר מצבים שונים כך נוכל לזהות בדיוק שינוי קוד ששובר משהו באיך ש JavaScript מנהלת מערכים (או בכל קוד אחר שאתם בודקים).

נקודה מבלבלת בבדיקות כאלה קורית כשהקוד באופן אוטומטי לוקח מידע מקוד אחר. קחו לדוגמה את הקלאס הבא עדיין ב JavaScript-

class UserManager {
    constructor() {
        this.name = process.env["USER"];
    }

    isAdmin() {
        return this.name === 'ynon';
    }
}

אולי הייתם רוצים לכתוב בדיקת יחידה שמוודאת שהפונקציה isAdmin מחזירה true למשתמש בשם ynon ו false למשתמשים בשמות אחרים. הבעיה שבדיקה כזאת תלויה במשתנה סביבה USER וככה יוצא שאתם בודקים יותר ממה שרציתם לבדוק (או שיוצרים Mock ואז מייצרים תלות גדולה מדי בין קוד הבדיקה לקוד המערכת).

דרך אחת קלה לצאת מהסיפור הזה היא לבנות אוביקט this עצמאי בתחילת הבדיקה ולהשתמש בו. ב JavaScript זה יהיה:

test('UserManager#isAdmin', () => {
    it('returns "true" for user "ynon"', () => {
        expect(UserManager.prototype.isAdmin.call({ name: 'ynon' })).toEqual(true);
    });
});

בצורה כזאת אנחנו מבינים שכל השדות על האוביקט שמפעיל את המתודה הם חלק מהקלט ומעבירים את הערכים שאנחנו צריכים להעביר תוך דילוג על ה constructor.

2. בדיקת השפעה של הפונקציה על State חיצוני

סוג שני של בדיקות הוא זה שבודק את ההשפעה של קריאה מסוימת על State חיצוני. בדוגמה של מערכים אני יכול לבדוק שהפונקציה push באמת מוסיפה פריט למערך:

test('array#push', () => {
    const arr = [1, 2, 5];
    arr.push(10);
    expect(arr.length).toEqual(4);
    expect(arr[3]).toEqual(10);
});

בסוג הזה של בדיקות כשאני כותב State חיצוני אני מדבר ספציפית על מידע - זה יכול להיות משתנים גלובאליים או שדות אחרים באוביקט, אבל לא תהיה פה אינטרקציה עם קוד אחר. דייב פארלי דיבר בסרט על זה שבסוג בדיקות כזה לא מומלץ לכתוב תנאים או לולאות, ועדיף לבדוק בצורה מפורשת את כל השינויים שאתם צריכים.

האתגר בבדיקות כאלה הוא לשים לב לאתחל את הסטייט החיצוני לפני הבדיקה, כדי שלא יווצר לכם קשר בין בדיקות או תלות בסדר הרצת הבדיקות.

3. בדיקת אינטרקציה בין הקוד הנבדק לקוד אחר

הסוג השלישי והמורכב ביותר של בדיקות יחידה הוא בדיקת האינטרקציה בין הקוד הנבדק לבין קוד אחר במערכת. הבדיקות האלה מסובכות כי הרצון להישאר ברמת בדיקת היחידה גורר אותנו לכתוב Mock-ים מתוחכמים שידמו את ההתנהגות של הקוד החיצוני.

הנה דוגמה פשוטה לקוד שמתקשר עם קוד אחר:

function throwDice(sides) {
  return Math.floor(Math.random() * sides) + 1;
}

הפונקציה קוראת לפונקציה Math.random כדי לקבל מספר אקראי בין 0 ל-1, כופלת במספר הפאות של הקוביה, זורקת את כל הספרות שאחרי הנקודה ומוסיפה אחד כדי לקבל מספר אקראי בין 1 למספר ה sides שקיבלנו.

בשביל לכתוב בדיקת אינטרקציה עבורה אני צריך לדרוס את Math.random ולגרום לה להחזיר ערכים ספציפיים שמעניינים אותי, וגם לוודא בסוף הבדיקה שבאמת Math.random נקראה.

ככל שהקוד איתו אני מתקשר מסובך יותר, כך דריסה שלו היא תהליך מסובך יותר עד שהרבה פעמים יוצא שבדיקות מהסוג הזה הן גם מאוד מסובכות וגם לחלוטין חסרות ערך. יש יותר מדי דריסות של קוד חיצוני שכל מה שהן בודקות זה את האינטרקציות בין אותם Mock-ים.

השורה התחתונה שהחלוקה ל-3 סוגים של בדיקות יחידה עזרה לי להבין היא שלפעמים בדיקת יחידה זה לא מה שאני צריך. לפעמים יהיה לי יותר קל לבדוק אינטרקציה עם קוד חיצוני אמיתי מאשר לייצר מימוש Mock שלו, כלומר לכתוב בדיקת אינטגרציה פשוטה במקום בדיקת יחידה מסובכת. בנוסף לפעמים שווה לחלק את הבדיקה למספר בדיקות לפי שלושת הקטגוריות, וכך לקבל בדיקות פשוטות יותר.

אם אתם רוצים לשמוע עוד על בדיקות מוזמנים להצטרף מחר בעשר בבוקר לוובינר בדיקות ב Node.JS.

נתראה בזום.