ดูว่าการครอบคลุมของโค้ดคืออะไรและดูวิธีทั่วไป 4 วิธีในการวัดความครอบคลุมของโค้ด
คุณเคยได้ยินวลี "code configuration" ไหม ในโพสต์นี้ เราจะมาดูกันว่าการครอบคลุมของโค้ดในการทดสอบคืออะไร และวิธีการวัดผลที่ใช้ทั่วๆ ไป 4 วิธี
การครอบคลุมของโค้ดคืออะไร
การครอบคลุมของโค้ดคือเมตริกที่วัดเปอร์เซ็นต์ของซอร์สโค้ดที่ทำการทดสอบ ช่วยให้คุณระบุจุดที่อาจขาดการทดสอบที่เหมาะสม
บ่อยครั้ง การบันทึกเมตริกเหล่านี้จะมีลักษณะดังนี้
ไฟล์ | % คำชี้แจง | % สาขา | % ของฟังก์ชัน | % ของเส้น | เส้นที่ไม่ครอบคลุม |
---|---|---|---|---|---|
file.js | 90% | 100% | 90% | 80% | 89,256 |
coffee.js | 55.55% | 80% | 50% | 62.5% | 10-11, 18 |
เมื่อคุณเพิ่มฟีเจอร์และการทดสอบใหม่ๆ การเพิ่มเปอร์เซ็นต์การครอบคลุมของโค้ดจะทำให้คุณมั่นใจได้มากขึ้นว่าแอปพลิเคชันของคุณได้รับการทดสอบเสร็จสมบูรณ์แล้ว แต่ก็ยังมีสิ่งใหม่ๆ ให้ค้นพบอีกมากมาย
ความครอบคลุมของโค้ดที่พบบ่อย 4 ประเภท
วิธีการทั่วไปในการรวบรวมและคำนวณความครอบคลุมของโค้ดมี 4 วิธี ได้แก่ ฟังก์ชัน บรรทัด สาขา และความครอบคลุมของใบแจ้งยอด
หากต้องการดูวิธีที่ความครอบคลุมของโค้ดแต่ละประเภทคำนวณเปอร์เซ็นต์ ลองพิจารณาตัวอย่างโค้ดต่อไปนี้สำหรับการคำนวณส่วนประกอบของกาแฟ
/* 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) {
// ...
}
การครอบคลุมของฟังก์ชันเป็นเมตริกที่ไม่ซับซ้อน โดยจะบันทึกเปอร์เซ็นต์ของฟังก์ชันในโค้ดที่การทดสอบเรียกใช้
ในตัวอย่างโค้ดจะมี 2 ฟังก์ชัน ได้แก่ 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);
}
ความครอบคลุมของบรรทัดวัดเปอร์เซ็นต์ของบรรทัดโค้ดปฏิบัติการที่ชุดทดสอบดำเนินการ หากบรรทัดโค้ดยังคงไม่มีการเรียกใช้ หมายความว่าโค้ดบางส่วนไม่ได้รับการทดสอบ
ตัวอย่างโค้ดมีโค้ดสั่งการ 8 บรรทัด (ไฮไลต์เป็นสีแดงและสีเขียว) แต่การทดสอบไม่ได้ดำเนินการตามเงื่อนไข americano
(2 บรรทัด) และฟังก์ชัน isValidCoffee
(1 บรรทัด) ซึ่งส่งผลให้มีความครอบคลุมของเส้นที่ 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 {};
}
…
ความครอบคลุมของสาขาวัดเปอร์เซ็นต์ของสาขาที่ดำเนินการหรือจุดการตัดสินใจในโค้ด เช่น เมื่อมีข้อความหรือการวนซ้ำ ซึ่งจะกำหนดว่าการทดสอบจะตรวจสอบทั้งสาขาจริงและเท็จของคำสั่งแบบมีเงื่อนไขหรือไม่
ในตัวอย่างโค้ดมี 5 สาขา ได้แก่
- กำลังโทรหา
calcCoffeeIngredient
โดยใช้เพียงcoffeeName
- กำลังโทรหา
calcCoffeeIngredient
ด้วยcoffeeName
และcup
- กาแฟคือเอสเปรสโซ
- กาแฟคือ Americano
- กาแฟอื่นๆ
การทดสอบ��รอบคลุมสาขาท��้ง��ม��ย���������นเงื่อนไข 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);
}
ความครอบคลุมของใบแจ้งยอดวัดเปอร์เซ็นต์ของใบแจ้งยอดในโค้ดที่ทำการทดสอบ เมื่อมองเผินๆ คุณอาจสงสัยว่า "ไม่เหมือนกับความครอบคลุมบรรทัด" อันที่จริงแล้ว ความครอบคลุมของใบแจ้งยอดคล้ายกับความครอบคลุมของบรรทัด แต่จะพิจารณาบรรทัดของโค้ดบรรทัดเดียวที่ประกอบด้วยหลายข้อความ
ในตัวอย่างโค้ด โค้ดสั่งการ 8 บรรทัด แต่มี 9 ข้อความ คุณเห็นบรรทัดที่มี 2 ข้อความนี้ไหม
espresso = 30 * cup; water = 70 * cup;
การทดสอบครอบคลุมเพียง 5 จาก 9 ข้อความเท่านั้น ความครอบคลุมของใบแจ้งยอดจึงเท่ากับ 55.55%
หากคุณเขียนหนึ่งข้อความต่อบรรทัดเสมอ ความครอบคลุมบรรทัดของคุณจะคล้ายกับความครอบคลุมของใบแจ้งยอด
คุณควรเลือกการครอบคลุมของโค้ดประเภทใด
เครื่องมือความครอบคลุมของโค้ดส่วนใหญ่ประกอบด้วยความครอบคลุมของโค้ดทั่วไป 4 ประเภทเหล่านี้ การเลือกเมตริกการครอบคลุมของโค้ดที่จะจัดลำดับความสำคัญจะขึ้นอยู่กับข้อกำหนดของโปรเจ็กต์ แนวทางปฏิบัติในการพัฒนา และเป้าหมายการทดสอบ
โดยทั่วไปแล้ว ความครอบคลุมของข้อความเป็นจุดเริ่มต้นที่ดีเนื่องจากเป็นเมตริกที่เข้าใจง่ายและเข้าใจง่าย ความครอบคลุมของสาขาและความครอบคลุมของฟังก์ชันจะวัดว่าการทดสอบจะเรียกเงื่อนไข (สาขา) หรือฟังก์ชัน ซึ่งแตกต่างจากความครอบคลุมของคำสั่ง ดังนั้นจึงเป็นความคืบหน้าตามธรรมชาติหลังครอบคลุมคำสั่ง
เมื่อคุณได้รั��ความครอบคลุมของใบแจ้งยอดในระดับสูงแล้ว ก็ต่อไปยังความครอบคลุมของสาขาและความครอบคลุมของฟังก์ชันได้
การครอบคลุมการทดสอบเหมือนกับการครอบคลุมของโค้ดไหม
ไม่ ความครอบคลุมของการทดสอบและความครอบคลุมของโค้ดมักจะสับสน แต่มีความแตกต่างกันดังนี้
- การครอบคลุมของการทดสอบ: เมตริกเกี่ยวกับน้ำที่วัดว่าชุดทดสอบครอบคลุมฟีเจอร์ของซอฟต์แวร์ได้ดีเพียงใด ช่วยกำหนดระดับความเสี่ยงที่เกี่ยวข้อง
- การครอบคลุมของโค้ด: เมตริกเชิงปริมาณที่วัดสัดส่วนของโค้ดที่ดำเ��ินการระหว่างการทดสอบ แต่เป็นเรื่องของจำนวนโค้ดที่การทดสอบครอบคลุม
นี่เป็นตัวอย่างสมมติง่ายๆ ให้จินตนาการว่าเว็บแอปพลิเคชันเป็นเหมือนบ้าน
- การครอบคลุมของการทดสอบจะวัดว่าการทดสอบครอบคลุมห้องต่างๆ ในบ้านมากน้อยแค่ไหน
- การครอบคลุมของโค้ดจะวัดว่าบ้านที่การทดสอบได้เดินไปมากน้อยเพียงใด
การครอบคลุมโค้ด 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)
จะผ่านเสมอไม่ว่าโค้ดจะทำงานถูกต้องหรือไม่
เมตริกที่ไม่ดีแย่กว่าเมตริกทั้งหมด
เมตริกที่ไม่ดีอาจทำให้คุณรู้สึกปลอดภัย ซึ่งแย่กว่าการไม่มีเมตริกเลย ตัวอย่างเช่น ห��กคุณมีชุดทดสอบที่ได้รับการครอบคลุมโค้ด 100% แต่การทดสอบทั้งหมดไม่มีความหมาย อาจเป็นเพราะคุณเข้าใจผิดด้านความปลอดภัยว่าโค้ดได้รับการทดสอบอย่างดีแล้ว หากคุณลบหรือทำให้บางส่วนของโค้ดแอปพลิเคชันเสียหายโดยไม่ตั้งใจ การทดสอบจะยังคงผ่านแม้ว่าแอปพลิเคชันจะทำงานไม่ถูกต้องแล้วก็ตาม
วิธีหลีกเลี่ยงสถานการณ์นี้มีดังนี้
- ตรวจสอบการทดสอบ เขียนและตรวจสอบการทดสอบเพื่อให้แน่ใจว่ามีความหมายและทดสอบโค้ดในสถานการณ์ต่างๆ
- ใช้การครอบคลุมของโค้ดเป็นแนวทาง ไม่ใช่เป็นเพียงการวัดประสิทธิภาพการทดสอบหรือคุณภาพของโค้ดเพียงอย่างเดียว
การใช้การครอบคลุมของโค้ดในการทดสอบประเภทต่างๆ
มาดูรายละเอียดวิธีใช้ความครอบคลุมของโค้ดกับการทดสอบทั่วไป 3 ประเภทกัน
- การทดสอบ 1 หน่วย เป็นประเภทการทดสอบที่ดีที่สุดในการรวบรวมความครอบคลุมของโค้ด เนื่องจากออกแบบมาเพื่อครอบคลุมสถานการณ์เล็กๆ และเส้นทางการทดสอบที่หลากหลาย
- การทดสอบการผสานรวม เครื่องมือเหล่านี้ช่วยรวบรวมการครอบคลุมโค้ดสำหรับการทดสอบการผสานรวมได้ แต่โปรดใช้ด้วยความระมัดระวัง ในกรณีนี้ คุณจะคำนวณความครอบคลุมของซอร์สโค้ดที่มากกว่า และอาจเป็นการยากที่จะพิจารณาว่าการทดสอบใดครอบคลุมส่วนไหนของโค้ดจริงๆ อย่างไรก็ตาม การคำนวณความครอบคลุมของโค้ดของการทดสอบการผสานรวมอาจเป็นประโยชน์สำหรับระบบเดิมที่ไม่มีหน่วยแยกต่างหาก
- การทดสอบตั้งแต่ต้นจนจบ (E2E) การวัดการครอบคลุมของโค้ดสำหรับการทดสอบ E2E ทำได้ยากและท้าทายเนื่องจากลักษณะของการทดสอบเหล่านี้มีความซับซ้อน แทนที่จะใช้ความครอบคลุมของโค้ด การครอบคลุมของข้อกำหนดอาจเป็นวิธีที่ดีกว่า เนื่องจากจุดมุ่งเน้นของการทดสอบ E2E คือการครอบคลุมข้อกำหนดของการทดสอบ ไม่ใช่การมุ่งเน้นเรื่องซอร์สโค้ด
บทสรุป
การครอบคลุมโค้ดอาจเป็นเมตริกที่มีประโยชน์ในการวัดประสิทธิภาพของการทดสอบ ซึ่งจะช่วยปรับปรุงคุณภาพของแอปพลิเคชันได้โดยการตรวจสอบให้แน่ใจว่าตรรกะที่สำคัญในโค้ด���ด้����บ��าร������อบ����็น����่าง���ี
อย่างไรก็ตาม โปรดทราบว่าความครอบคลุมของโค้ดเป็นเพียงเมตริกเดียว และอย่าลืมพิจารณาปัจจัยอื่นๆ ด้วย เช่น คุณภาพของการทดสอบและข้อกำหนดในการสมัคร
เป้าหมายคือการครอบคลุมของโค้ด 100% คุณควรใช้ความครอบคลุมของโค้ดควบคู่กับแผนการทดสอบที่ครบถ้วนสมบูรณ์ซึ่งใช้วิธีการทดสอบที่หลากหลาย เช่น การทดสอบ 1 หน่วย การทดสอบการผสานรวม การทดสอบตั้งแต่ต้นจนจบ และการทดสอบด้วยตนเอง
ดูตัวอย่างโค้ดแบบเต็มและการทดสอบที่มีการครอบคลุมโค้ดที่ดี นอกจากนี้คุณยังสามารถเรียกใช้โค้ดและทดสอบได้ในการสาธิตการใช้งานจริงนี้
/* 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({});
});
});