Định nghĩa unit tests
Unit Test được xem là tài liệu của source code. Bên dưới đây là 1 ví dụ về code document trong Unit Test, mỗi test case sẽ được mô tả đầy đủ, khi đọc qua Unit Test chúng ta sẽ hiểu được các logic đang hoạt động ra sao chỉ bằng cách đọc các Test Desciption.
import {RankEvaluator} from "../RankEvaluator";
describe('RankEvaluator', () => {
it('should return `Excelent` when the point is between 0 and 10', () => {});
it('should return `Good` when the point is between 7 and 8', () => {});
it('should return throw an error when the score in invalid', () => {});
});
Tới đây chúng ta có 1 câu hỏi đặt ra: Nếu như có nhiều test case quá khi chúng ta hiện thực thì viết như thế nào để phân biệt được các case với nhau và dễ xem?
Ở đây mình sử dụng framework Jest để minh họa. Mọi người hãy nhìn vào ảnh ví dụ sẽ thấy 2 từ khóa, đó là describe
và it.
describe
: có thể hiểu là một block dùng để nhóm các test case có liên quan lại với nhau.it
: đại diện cho mỗi một test case.
Các test case của một module hay là 1 component sẽ được test đang viết theo BDD style (Behavior Driven Development). Thì BDD có thể hiểu đơn giản là mỗi test case sẽ dựa trên một câu chuyện của user được mô tả bằng từ vựng tiếng anh và từ đó được thống nhất giữa các bên (user, developer,..)
Mọi người có thể thấy hình ảnh đây là cách viết test theo BDD, phần describe sẽ là môt tả đơn vị công việc hoặc đơn vị test. Phần test name của it
thì sẽ có 2 vế được phân chia giữa when. Test name nên bắt đầu bằng should và kết quả mong đợi. Sau when sẽ là ngữ cảnh hoặc kịch bản để xảy ra kết quả đó.
Theo dõi độ bao phủ của Unit Test
Hiện tại ở Cybozu, chúng tôi đang viết unit test cho các function, component mới hoặc là những code được refactor từ những code cũ. Không chỉ viết unit test không mà ở Cybozu cùng tracking cả độ phủ của unit test, mục đích của việc này là giúp ta có thể đo lường được bao nhiêu phần trăm của code được test qua khi chạy unittest. Về mặt tổng quan, số phần trăm này càng cao thì mức độ đảm bảo của code càng cao.
Mọi người có thể nhìn thấy sơ đồ là độ phủ của unit test ngày càng được nâng cao theo thời gian.
Các loại Test
White box and Black box
Trước khi đến về test pattern và ví dụ thì mình xin chia sẻ về các dạng test thì sẽ có 2 dạng test đó là Black box and white box
-
Black box testing
- Đối với black box thì developer không cần quan tâm implement bên trong component hay function, họ chỉ cần chuẩn bị tập data test và truyền vào phía bên trong.
-
White box testing
- Còn đối với White box thì developer quan tâm đến implement bên trong component hay function, test tất cả các trường hợp rẽ nhánh, trường hợp đặc biệt.
-
Ngoài ra, chúng ta còn có một khám niệm khác là Grey box testing
- Kiểm thử hộp xám có sự kết hợp giữa lợi ích của kiểm thử hộp đen và hộp trắng.
- Kiểm thử hộp xám thường được sử dụng trong Kiểm thử tích hợp (Intergration test). Tuy nhiên, dựa vào giải thuật và chức năng, nó cũng có thể được sử dụng ở nhiều mức kiểm thử khác nhau.
AAA pattern
Khi viết test case không phải chúng ta cứ code đại là được, như thế code có thể sẽ khó đọc và debug. Do đó, có một tiêu chuẩn đã được sinh ra khi chúng ta viết test case đó là AAA pattern, là một kỹ thuật sắp xếp và định dạng code khi viết Unit Test.
Với AAA pattern, test case của chúng ta sẽ được chia ra làm 3 phần, mỗi phần chỉ có một nhiệm vụ cho chính phần đó.
- Đầu tiên là Arrage: Phần này mọi người có thể hiểu đó là setup test như là mock module, khởi tạo các class, chuẩn bị data,….
- Tiếp theo là phần Act: Phần này là phần gọi các method chúng ta cần test.
- Cuối cùng là Assert: phần này là phần verify xem kết quả có đúng như mong đợi hay không?
Test pattern
Về test pattern thì mình xin phép đưa ra 3 ví dụ thường hay gặp là:
- Normal Cases (case user sử dụng bình thường)
- Abnormal Case (exceptions)
- Edge Cases (test cận & chạm biên)
Normal cases
Đầu tiên mình xin phép nói về normal case. Normal case là những case mà user sử dụng một cách bình thường.
Ví dụ ở đây, chúng ta cùng xem xét hàm đơn giản là xếp hạng học sinh dựa vào số điểm. Chức năng của hàm là nhập vào số điểm thì hàm sẽ trả về kết quả xếp hạng của học sinh đó. Nếu điểm nhỏ hơn 0 và lớn hơn 10 thì sẽ quăng lỗi điểm không hợp lệ. Vậy đối với trường hợp này chúng ta sẽ test case normal như thế nào?
Mọi người có thể thấy cơ bản mình chia ra 3 step như là phần AAA pattern mình trình bày phía dưới:
Arrange:
- Input data: score is 10
Act: Excute the production code.
- `rankEvaluator.getRank(10);`
Assert: Verify result is `Excellent`
- Step đầu tiên là prepare data, chuẩn bị 2 bộ data. Bộ data đầu tiên là input data dùng để excute production code. Bộ data thứ 2 là expected data dùng để so sánh với kết quả trả về sau khi chạy production code.
- Step thứ 2 là thực hiện chạy production code, gọi hàm, truyền input data và nhận kết quả trả về sau khi chạy hàm đó.
- Step thứ 3 là bước verify. Chúng ta thực hiện so sánh kết quả trả về ở bước 2 có giống với kết quả mong đợi của chúng ta chuẩn bị hay không? Đó là 3 bước khi viết và chạy 1 test case.
Abnormal cases
Tiếp theo là đến case abnormal, vẫn là hàm đó. Chúng ta vẫn có 3 step như case normal. Nhưng mọi người để ý có thể thấy bước prepare data và bước verify khác so với case normal
Cụ thể là bước prepare data là chúng ta chuẩn bị số điểm rơi vào trường hợp không hợp lệ, ở đây mình chuẩn bị là -1 và expected data là một mã lỗi. Bước verify thì sẽ kiểm tra là sau khi chạy production code thì sẽ quăng ra lỗi và mã lỗi phải giống như expected data của chúng ta đã chuẩn bị.
Arrange:
- Input data: score is -1.
Act: Call the production code and input score.
- rankEvaluator.getRank(-1);
Assert: Verify step excute throw error.
Edge cases
Cuối cùng là chúng ta đến case edge cases tức là case test cận và chạm biên. Case này có ý nghĩa là nhập vào một bộ data đặc biệt để kiểm tra tính đúng đăng xem chương trình có chạy bình thường hay không (các giá trị biên, ...)
Arrange:
- Input data: score is 7.
Act: Call the production code and input score.
- rankEvaluator.getRank(7);
Assert: Verify result is `Good`
- Mọi người nhìn vô có thể thấy các step tương tự y chang case normal. Vậy điểm khác biệt ở đây là gì? Ở đây mình đã chuẩn bị input data là biên trong một khoảng. Ở đây mình chuẩn bị score là 7 tức là số điểm biên của khoảng điểm tính điểm học sinh giỏi. Có thể thấy các bước test ở đây rất giống nhau không có khác gì nhau. Nhưng mindset ở chỗ này quan trọng là mỗi case đều có một bộ data test khác nhau. Mỗi bộ data sẽ dẫn đến mỗi kết quả khác nhau cho hàm của mình.
- Chúng ta nên viết test một cách cẩn thận vì unit test có thể được xem là document cho code của mình, mọi người nhìn vào ví dụ phần unit test có thể thấy là code của mình xảy ra những trường hợp như thế nào mà không phần phải đọc production code. Nếu ta viết test code không đủ tốt thì nó không thể cover hết những trường hợp xảy ra. Case thường hay bị miss nhất là edge cases.
Tổ chức test cases
Mỗi test case chúng ta nên test 1 trường hợp. Tránh gom nhiều verification không liên quan vào một test case nếu fail thì khó nhận ra lý do bị fail và nên gom nhóm chúng lại trong block.
Như hình bên dưới mọi người có thể thấy ví dụ đang gom block lại những test case tương tự nhau. Và mỗi test case đều mô tả chỉ cho 1 trường hợp.
Vậy nếu có nhiều file test thì như thế nào? Mọi người có thể nhìn thấy ví dụ bên tay trái. Hiện tại dự án của mình đang tổ chức test case theo cấu trúc thư mục như hình bên dưới.
Ví dụ mọi người muốn viết unit test cho file component A và component B thì ta sẽ tạo một folder __test__
cùng cấp với 2 file ta cùng cần test, folder đó sẽ chứa 2 file test cho component A và B.
File test nên có suffix là .test hoặc .ts Hiện tại đang sử dụng 2 loại file đó là .ts (typescript) và .js (javascript). Mặc dù sử dụng 2 loại file khác nhau nhưng cách tổ chức test case vẫn không thay đổi, chỉ khác mỗi phần extension file.
Fixture là một bộ dữ liệu mẫu cố định, có thể gọi là bối cảnh thử nghiệm. Fixture có thể chứng file html giả định trên màn hình, giả database dạng json,... Nếu mà mọi người có phần fixtures thì phần này thì được đặt trong thư mục __fixtures__
, và thư mục này sẽ nằm trong thư mục test. Thì bối cảnh ở đây là giao diện frontend nên là sẽ chứa html giả định trên màn hình.
Tổng kết
- Cải tiến là điều tất yếu cần làm để duy trì một sản phẩm phát triển ổn định và lâu dài. bao phủ Unit test cũng là 1 bước cải tiến của sản phẩm vì nó có thể đảm bảo chất lượng code và cảnh báo cho developer sớm nhất những rủi ro tiềm ẩn.
- Mặc dù Unit Test được xem là một tùy chọn đối với một số developer, nhưng đối với một sản phẩm lâu năm thì Unit Test rất cần thiết để được xem là 1 tiêu chí đánh giá chất lượng một dự án phần mềm. Việc thông thạo và có thói quen bao phủ source code của bản thân là 1 thói quen tốt để tăng kỹ năng của bản thân.
Phần sơ lượt về Unit Test trong Frontend tới đây đã kết thúc. Mọi người hãy chờ những bài viết tiếp theo của mình nhé.