ארבעה סוגים נפוצים של כיסוי קוד

למדו מהו כיסוי קוד וגלו ארבע דרכים נפוצות למדוד אותו.

שמעת את הביטוי "כיסוי קוד"? בפוסט הזה נסביר מהו הכיסוי של הקוד בבדיקות ונסביר על ארבע דרכים נפוצות למדוד אותו.

מהו כיסוי קוד?

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

לעיתים קרובות, תיעוד המדדים האלה נראה כך:

קובץ % דוחות % סניף פונקציות% % קווים קווים לא מוסתרים
file.js 90% 100% 90% 80% 89,256
coffee.js 55.55% 80% 50% 62.5% 10-11, 18

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

ארבעה סוגים נפוצים של כיסוי קוד

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

ארבעה סוגים של כיסוי טקסט.

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

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

הבדיקות שמאמתות את הפונקציה calcCoffeeIngredient הן:

/* coffee.test.js */

import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown');
    expect(result).to.deep.equal({});
  });
});

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

כיסוי הפונקציות

כיסוי הקוד: 50%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
}

function isValidCoffee(name) {
  // ...
}

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

בדוגמת הקוד יש שתי פונקציות: calcCoffeeIngredient ו-isValidCoffee. הבדיקות קוראות רק לפונקציה calcCoffeeIngredient, כך שהכיסוי של הפונקציה הוא 50%.

כיסוי קו

כיסוי הקוד: 62.5%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

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

קטע הקוד בדוגמה כולל שמונה שורות של קוד הפעלה (מודגש באדום וירוק), אבל הבדיקות לא מבצעות את התנאי americano (שתי שורות) ואת הפונקציה isValidCoffee (שורה אחת). התוצאה היא כיסוי קו של 62.5%.

לתשומת ליבך, כיסוי השורות לא מתייחס להצהרות הצהרות, כמו function isValidCoffee(name) ו-let espresso, water;, כי הן לא קובצי הפעלה.

כיסוי הסניף

כיסוי הקוד: 80%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...

  if (coffeeName === 'espresso') {
    // ...
    return { espresso };
  }

  if (coffeeName === 'americano') {
    // ...
    return { espresso, water };
  }

  return {};
}
…

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

בדוגמת הקוד יש חמש הסתעפויות:

  1. שיחה אל calcCoffeeIngredient עם coffeeName סימן המחאה. בלבד
  2. שיחה אל calcCoffeeIngredient עם coffeeName ועם cup סימן המחאה.
  3. קפה הוא אספרסו סימן המחאה.
  4. קפה בסגנון אמריקנו סימן X.
  5. קפה אחר סימן המחאה.

הבדיקות מתייחסות לכל ההסתעפויות, מלבד התנאי Coffee is Americano. כלומר, כיסוי הסניף הוא 80%.

כיסוי דף החשבון

כיסוי הקוד: 55.55%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

כיסוי דף החשבון מודד את אחוז ההצהרות בקוד שלך שהבדיקות מתבצעות. במבט ראשון, ייתכן שתשאל את עצמך "האם זה לא זהה לכיסוי שורות?". אכן, הכיסוי בהצהרה דומה לכיסוי שורות, אבל הוא מביא בחשבון שורות קוד בודדות שמכילות כמה הצהרות.

בדוגמה של הקוד, יש שמונה שורות של קוד הפעלה, אבל יש תשע הצהרות. האם אתם יכולים לזהות את השורה שמכילה שתי הצהרות?

לבדיקת התשובה

זו השורה הבאה: espresso = 30 * cup; water = 70 * cup;

הבדיקות מכסה רק חמישה מתוך תשע ההצהרות, ולכן הכיסוי של ההצהרה הוא 55.55%.

אם אתם תמיד כותבים דוח אחד בכל שורה, כיסוי השורה יהיה דומה לכיסוי הדף בדף.

באיזה סוג של כיסוי קוד כדאי לבחור?

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

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

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

האם כיסוי הבדיקה זהה לכיסוי קוד?

לא. לעיתים קרובות יש בלבול בין כיסוי הבדיקה וכיסוי הקוד, אבל הם שונים:

  • כיסוי בדיקה: מדד ימי שמודד באיזו מידה חבילת הבדיקות מכסה את התכונות של התוכנה. הוא עוזר לקבוע את רמת הסיכון הכרוכה.
  • כיסוי הקוד: מדד כמותי שמודד את החלק של הקוד שבוצע במהלך הבדיקה. זה קשור לכמות הקוד שהבדיקות כוללות.

הנה אנלוגיה פשוטה: דמיינו אפליקציית אינטרנט בתור בית.

  • כיסוי בדיקות מודד עד כמה הבדיקות מכסות את החדרים בבית.
  • כיסוי הקוד מודד את החלק בבית שהבדיקות בוצעו.

כיסוי 100% בקוד לא מעיד על באגים

אמנם רצוי מאוד שהכיסוי של הקוד יהיה גבוה במהלך הבדיקה, אבל 100% כיסוי הקוד לא מבטיח שלא יהיו באגים או פגמים בקוד.

דרך חסרת משמעות להשגת כיסוי קוד ב-100%

מומלץ לשקול את הבדיקה הבאה:

/* coffee.test.js */

// ...
describe('Warning: Do not do this', () => {
  it('is meaningless', () => { 
    calcCoffeeIngredient('espresso', 2);
    calcCoffeeIngredient('americano');
    calcCoffeeIngredient('unknown');
    isValidCoffee('mocha');
    expect(true).toBe(true); // not meaningful assertion
  });
});

הבדיקה הזו משיגה כיסוי מלא של הפונקציה, השורות, ההסתעפות וההצהרה, אבל היא לא הגיונית כי היא לא בודקת את הקוד בפועל. טענת הנכוֹנוּת (assertion) expect(true).toBe(true) תמיד תעבור בין שהקוד פועל כראוי.

מדד לא תקין יותר גרוע מכל מדד

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

כדי להימנע מתרחיש כזה:

  • בדיקה לבדיקה. כתבו בדיקות ובדקו אותן כדי לוודא שיש להן משמעות ולבדוק את הקוד במגוון תרחישים שונים.
  • השתמשו בכיסוי הקוד כהנחיה, ולא בתור המדד היחיד ליעילות הבדיקה או לאיכות הקוד.

שימוש בכיסוי קוד בסוגים שונים של בדיקות

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

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

סיכום

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

עם זאת, חשוב לזכור שכיסוי הקוד הוא רק מדד אחד. חשוב להביא בחשבון גם גורמים אחרים, כמו איכות הבדיקות והדרישות להגשת בקשה.

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

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

/* coffee.js - a complete example */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  if (!isValidCoffee(coffeeName)) return {};

  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  throw new Error (`${coffeeName} not found`);
}

function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */

import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have americano', () => {
    const result = calcCoffeeIngredient('americano');
    expect(result.espresso).to.equal(30);
    expect(result.water).to.equal(70);
  });

  it('should throw error', () => {
    const func = () => calcCoffeeIngredient('mocha');
    expect(func).toThrowError(new Error('mocha not found'));
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown')
    expect(result).to.deep.equal({});
  });
});