Từng Bước Trở Thành Dev Front End Xịn (Phần 3)
Tiếp theo Phần 2: Unit Test ở Front End với Karma của loạt bài viết về Front End, trong phần này mình sẽ giới thiệu đến các bạn Phần 3: Code coverage với Istanbul.
Trong phần trước chúng ta đã nói chuyện về Unit Test, các khái niệm, kinh nghiệm và cách cài đặt để chạy Unit Test. Tuy nhiên làm sao chúng ta biết được UT của chúng ta viết ra đã đạt tiêu chuẩn chưa? UT đã bao phủ hết những trường hợp cần thiết chưa? Làm sao các trưởng nhóm, trưởng dự án biết bạn đã viết tốt hay không? ... và rất nhiều câu hỏi khác.
Để trả lời những câu hỏi đó, chúng ta phải có một tiêu chuẩn để đánh giá, một cách để report tự động những gì quá trình UT đã làm. Đó chính là Code coverage.
Các khái niệm về code coverage
- Statements: là tiêu chuẩn đánh giá các câu lệnh được/không được phủ(được gọi) bởi code Unit Test.
- Branches: là tiêu chuẩn đánh giá các nhánh điều kiện được/không được phủ(được gọi) bởi code Unit Test.
- Functions: là tiêu chuẩn đánh giá các hàm được/không được phủ(được gọi) bởi code Unit Test.
- Lines: là tiêu chuẩn đánh giá số dòng code được/không được phủ(được gọi) bởi code Unit Test.
Bạn xem hình sau:
Ở dòng đầu tiên của report này là kết quả cho cả package bạn đang chọn:
Ở các dòng tiếp theo là kết quả cho từng file/class đã được test:
Ý nghĩa màu report:
- Xanh lá cây: nghĩa là thông số được report đạt tiêu chuẩn đặt ra
- Vàng: nghĩa là thông số được report chưa được tốt lắm, cần chú ý
- Đỏ: tốt nhất bạn đi fix đi, nó là FAILED
Ý nghĩa các con số:
- Statements 62.5% - 10/16: nghĩa là trong class được test có 16 câu lệnh nhưng chỉ có 10 câu lệnh được test, còn 6 câu lệnh chưa được code UT chạy qua
- Branches 38.46% - 5/13: nghĩa là trong code của bạn có 13 nhánh điều kiện nhưng chi có 5 nhánh được code UT chạy qua
- Functions 50% - 1/2: nghĩa là trong code của bạn có 2 functions nhưng chi có 1 function được code UT chạy qua
- Lines 64.29% - 9/14: nghĩa là trong code của bạn có 14 dòng lệnh nhưng chỉ có 9 dòng lệnh được code UT chạy qua
Làm sao để tính các con số này từ source code?
Đây là câu hỏi khó cho dân Dev ^_^, bởi vì nó không đơn giản là nhìn vào các dòng code, điều kiện if else, code block để đưa ra các con số, nó phức tạp hơn các bạn nghĩ.
Nếu bạn thực sự muốn biết chi tiết, bạn đọc các tài liệu liên quan đến kỹ thuật testing, kiểm thử nhé. Một vài tài liệu mình từng đọc:
(Siêu nhân QA nào đi ngang qua bài này, nhờ bạn comment vài tài liệu hữu ích cho người đi sau tham khảo nghen ^_^).
Report code coverage tốt có nghĩa là chất lượng tốt?
Nếu bạn nhìn vào bảng report trên thì bạn nghĩ rằng cứ con số 100% là ngon? chưa hẳn như vậy, bạn xem các ví dụ sau nhé:
Ví dụ 1: Statements vs Lines
// Code chính
export const getUserInfor = (yearOld, userName) => {
let output = [];
if (yearOld > 18) { output.push(`${userName} is an adult`); } else {return "Nothing"}
return output;
}
// Code test
describe('Testing Coverage Sample', () => {
it('Mr. Namx is an adult', () => {
const yearOld = 20;
const userName = "Mr. Namx";
const outputs = getUserInfor(yearOld, userName);
expect(outputs).to.include.members([
"Mr. Namx is an adult"
]);
});
});
Kết quả chạy UT của ví dụ này sẽ là:
- Statements màu Xanh với 88.33% - 5/6 câu lệnh được phủ, tuy nhiên bạn nhìn kỹ hơn ở code UT, bạn truyền vào giá trị yearOld là 20, có nghĩa là câu lệnh if chỉ được thực hiện ở về đầu, vế else phía sau chưa được kiểm thử. Nên code UT và kết quả này chưa tốt.
- Lines Xanh lè với 100% - 4/4 dòng được phủ kín. ^_^ nhìn vào kết quả này thì có câu nói nổi tiếng trong nghề Dev "Có Thánh mới biết bạn làm gì", không ai đủ thời gian để đi đọc từng câu lệnh của bạn đã viết để phát hiện ra là con số 100% đó chưa đủ tốt.
- May thay trong kết quả này có thông số Branches 50%, nó sẽ bắt được trường hợp này cho bạn để bạn sửa lại.
Ví dụ 2: 100%
// Code chính
export const getUserInfor = (yearOld, userName) => {
let output = [];
if (userName.length % 2 === 0) {
output.push(`${userName} is a man`);
} else {
output.push(`${userName} is a woman`);
}
return output;
}
// Code UT
describe('Testing Coverage Sample', () => {
it('Mr. Namx is a man', () => {
const yearOld = 20;
const userName = "Mr. Namx";
const outputs = getUserInfor(yearOld, userName);
expect(outputs).to.include.members([
"Mr. Namx is a man"
]);
});
it('Mr. Nam is a woman', () => {
const yearOld = 20;
const userName = "Mr. Nam";
const outputs = getUserInfor(yearOld, userName);
expect(outputs).to.include.members([
"Mr. Nam is a woman"
]);
});
});
Ở ví dụ này mình chạy 2 test cases, với độ dài của tên là chẳn và lẻ để phủ kín toàn bộ code chính, report của ví dụ này chắc chắn 100% toàn bộ thông số
Nhưng bạn biết code này chết ở đâu không?
Một ngày đẹp trời nào đó, đầu vào của hàm đó với tham số là userName = null. Ứng dụng của bạn sẽ phát sinh lỗi
TypeError: Cannot read property 'length' of null
và nếu may mắn thì ứng dụng của bạn vẫn tiếp tục chạy, nếu không may mắn thì ứng dụng dừng hoạt động.
Vậy viết như thế nào cho tốt?
Theo kinh nghiệm của mình, một UT tốt cần đảm bảo 2 yếu tố:
- Pass các thông số report Code Coverage mà chúng ta đã bàn luận ở trên.
- Pass các test cases của chức năng bạn đang làm.
Cài đặt code coverage với Istanbul
Cài Thư viện hỗ trợ report code coverage và highlight
npm i istanbul-instrumenter-loader karma-coverage-istanbul-reporter --save-dev
Bạn xem tài liệu gốc ở đây nhé: https://github.com/webpack-contrib/istanbul-instrumenter-loader
Khai báo suorce code và UT ở file test-suite.js
var tests = require.context('../', true, /-test\.(js|jsx)$/i);
tests.keys().forEach(tests);
var sources = require.context('../../src/', true, /\.(js|jsx)$/i);
sources.keys().forEach(sources);
Cấu hình report:
coverageIstanbulReporter: {
// Thư mục chứa kết quả report
dir : 'coverage/',
// Các kiểu report
reports: ['html', 'text-summary', 'lcov'],
// Cấu hình từng kiểu report
'report-config': {
html: {
// outputs the report in ./coverage/html
subdir: 'html'
}
},
fixWebpackSourcePaths: true,
// Phần này cài đặt tiêu chuẩn cho các thông số report chúng ta đã bàn ở trên
thresholds: {
emitWarning: true, // false thì khi test, test runner sẽ dừng nếu gặp lỗi
// Thông số chung cho toàn bộ test
global: {
statements: 80,
lines: 80,
branches: 80,
functions: 80
},
// Thông số cho mỗi file
each: {
statements: 80,
lines: 80,
branches: 80,
functions: 80,
// Một vài trường hợp chúng ta không muốn test nhiều
overrides: {
'src/components/**/*.js': {
statements: 50,
lines: 50,
branches: 50,
functions: 50,
}
}
}
},
},
Source code mẫu cho phần này các bạn checkout ở đây nhé:
cd $PROJECT_HOME
# Lệnh này sẽ checkout duy nhất branch InitCoverage
git clone --single-branch --branch InitCoverage https://github.com/lapth/FE-User-Management.git
cd FE-User-Management
npm install
npm test
Kết quả test có trong thư mục $PROJECT_HOME/coverage.
Tạm kết
Vậy là quá trình tìm hiểu về Code Coverage đã hoàn thành. Chúc các bạn thành công! Nhớ đón đọc các bài tiếp theo nhé. Hẹn gặp lai các bạn trong phần 4 :)