چهار نوع رایج پوشش کد

یاد بگیرید پوشش کد چیست و چهار روش رایج برای اندازه گیری آن را کشف کنید.

رامونا شورینگ
رامونا شورینگ
جسلین ین
جسلین ین

آیا عبارت "پوشش کد" را شنیده اید؟ در این پست، پوشش کد در تست ها و چهار روش رایج برای اندازه گیری آن را بررسی خواهیم کرد.

پوشش کد چیست؟

پوشش کد معیاری است که درصد کد منبعی را که آزمایش های شما اجرا می کند اندازه گیری می کند. این به شما کمک می کند مناطقی را که ممکن است فاقد آزمایش مناسب باشند شناسایی کنید.

اغلب، ثبت این معیارها به شکل زیر است:

فایل ٪ بیانیه ٪ شاخه ٪ کارکرد % خطوط خطوط بدون پوشش
file.js 90% 100% 90% 80% 89256
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 {};
}
…

پوشش شعبه درصد شاخه های اجرا شده یا نقاط تصمیم گیری در کد را اندازه گیری می کند، مانند دستورات if یا حلقه ها. تعیین می کند که آیا آزمون ها هر دو شاخه درست و نادرست عبارات شرطی را بررسی می کنند.

در مثال کد پنج شاخه وجود دارد:

  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);
}

پوشش بیانیه درصد عبارات موجود در کد شما را که آزمایشات شما اجرا می کند اندازه گیری می کند. در نگاه اول، ممکن است تعجب کنید، "آیا این همان پوشش خط نیست؟" در واقع، پوشش بیانیه مشابه پوشش خط است، اما خطوط منفردی از کد را در نظر می‌گیرد که حاوی چندین عبارت است.

در مثال کد، هشت خط کد اجرایی وجود دارد، اما 9 دستور وجود دارد. آیا می توانید خط حاوی دو عبارت را تشخیص دهید؟

پاسخ خود را بررسی کنید

این خط زیر است: espresso = 30 * cup; water = 70 * cup;

آزمون ها تنها پنج مورد از 9 عبارت را پوشش می دهند، بنابراین پوشش بیانیه 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
  });
});

این تست 100% پوشش عملکرد، خط، شاخه و دستور را به دست می آورد، اما منطقی نیست زیرا در واقع کد را آزمایش نمی کند. ادعای expect(true).toBe(true) همیشه بدون در نظر گرفتن اینکه آیا کد به درستی کار می کند یا خیر، تصویب می شود.

یک معیار بد بدتر از عدم معیار است

یک معیار بد می تواند به شما احساس امنیت کاذب بدهد، که بدتر از نداشتن معیار است. به عنوان مثال، اگر یک مجموعه آزمایشی دارید که پوشش کد ۱۰۰٪ را به دست می‌آورد، اما همه آزمایش‌ها بی‌معنی هستند، ممکن است احساس امنیت کاذبی داشته باشید که کد شما به خوبی آزمایش شده است. اگر به‌طور تصادفی بخشی از کد برنامه را حذف یا شکسته‌اید، با وجود اینکه برنامه دیگر به درستی کار نمی‌کند، آزمون‌ها همچنان با موفقیت انجام می‌شوند.

برای جلوگیری از این سناریو:

  • بررسی تست. تست ها را بنویسید و مرور کنید تا مطمئن شوید که معنی دار هستند و کد را در سناریوهای مختلف آزمایش کنید.
  • از پوشش کد به عنوان یک راهنما استفاده کنید ، نه به عنوان تنها معیار سنجش اثربخشی آزمون یا کیفیت کد.

استفاده از پوشش کد در انواع مختلف تست

بیایید نگاهی دقیق‌تر به نحوه استفاده از پوشش کد با سه نوع آزمایش معمول بیندازیم:

  • تست های واحد آنها بهترین نوع تست برای جمع آوری پوشش کد هستند زیرا برای پوشش چندین سناریو کوچک و مسیرهای آزمایشی طراحی شده اند.
  • تست های یکپارچه سازی آنها می توانند به جمع آوری پوشش کد برای تست های یکپارچه سازی کمک کنند، اما از آنها با احتیاط استفاده کنید. در این مورد، شما پوشش بخش بزرگتری از کد منبع را محاسبه می‌کنید، و تعیین اینکه کدام آزمایش واقعاً کدام بخش از کد را پوشش می‌دهد، می‌تواند دشوار باشد. با این وجود، محاسبه پوشش کد تست‌های یکپارچه‌سازی ممکن است برای سیستم‌های قدیمی که واحدهای جدا شده خوبی ندارند مفید باشد.
  • تست های End-to-End (E2E). اندازه گیری پوشش کد برای تست های E2E به دلیل ماهیت پیچیده این تست ها دشوار و چالش برانگیز است. به جای استفاده از پوشش کد، پوشش نیازمندی ممکن است راه بهتری باشد. این به این دلیل است که تمرکز تست‌های E2E پوشش الزامات آزمون شما است، نه تمرکز بر روی کد منبع.

نتیجه

پوشش کد می تواند یک معیار مفید برای اندازه گیری اثربخشی تست های شما باشد. این می تواند به شما کمک کند تا با اطمینان از اینکه منطق حیاتی در کد شما به خوبی آزمایش شده است، کیفیت برنامه خود را بهبود بخشید.

با این حال، به یاد داشته باشید که پوشش کد تنها یک معیار است. اطمینان حاصل کنید که سایر عوامل مانند کیفیت آزمون ها و الزامات درخواست خود را نیز در نظر بگیرید.

هدف برای پوشش 100٪ کد هدف نیست. در عوض، شما باید از پوشش کد همراه با یک طرح آزمایشی جامع استفاده کنید که شامل انواع روش‌های آزمایش، از جمله تست‌های واحد، تست‌های یکپارچه‌سازی، تست‌های سرتاسر و تست‌های دستی است.

نمونه کد کامل و تست های با پوشش کد خوب را ببینید. همچنین می توانید کدها را اجرا کرده و با این دمو زنده آزمایش کنید.

/* 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({});
  });
});