Trong phần đầu tiên của series All things about unit test with Jest, mình sẽ giới thiệu về các concept cơ bản về unit test.
Đặt vấn đề
- Unit test đã không còn quá xa lạ đối với bất kì một lập trình viên nào. Tuy nhiên đôi khi những bạn mới bắt đầu vọc vạch và viết unit test nhằm đảm bảo chất lượng phần mềm của mình thì sẽ hơi khó khăn và không biết bắt đầu từ đâu.
- Như mình khi bắt đầu tìm hiểu về nó cũng rất mơ hồ và
confuse
khi có quá nhiềulí thuyết
mới lạ khi mình mới tiếp xúc 🤒 Vì thế ở bài viết này mình sẽ chia sẻ về quá trình tham khảo tài liệu cũng như kinh nghiệm của mình, để phần nào giúp các bạn có cách tiếp cận dễ dàng hơn vớiunit test
. - Ngoài ra trong bài viết này, mình có sử dụng mã một vài nơi sẽ là viết với ngôn ngữ
typescript
để viết ví dụđược dễ dàng
vàdễ đọc
hơn.
Thông tin package
- "jest": "^29.7.0"
- "express": "^4.18.2"
Bài viết này khá dài, vì vậy mình sẽ chia series này thành 4 phần
, ở phần đầu này chủ yếu mình sẽ chia sẻ về concept của 1 unit test, vì vậy nếu bạn nào chưa biết thì mình recommend nên đọc trước khi sang các phần khác, nếu bạn đã biết rồi thì có thể skip sang phần 2 nghen 🙇🏼♂️
Lý thuyết
1. Unit test là gì?
Unit test là kiểm thử tự động cho một đơn vị mã nguồn (hơi khó hiểu đúng không :D
).
1 unit test sẽ xác nhận (assert) liệu hành vi (behavior) của đơn vị có phù hợp với mong đợi (expectation) hay không.
Tuy vài dòng khó hiểu như vậy, nhưng thực chất:
Nói ngắn gọn là để kiểm tra chức năng chúng ta viết đúng như mong đợi hay không và sau này có chỉnh sửa chức năng đó thì có thể phát hiện ra lỗi kịp thời nếu có vấn đề phát sinh.
Tổng quan lại thì mục đích
của unit test
nhằm để đảm bảo ứng dụng của chúng ta hoạt động đúng như mong đợi, góp phần tăng độ tin cậy
và tính ổn định
.
2. Test double là gì?
Khái niệm Test double là một kỹ thuật được sử dụng trong unit testing để tạo ra các đối tượng giả để đại diện cho các đối tượng thật trong hệ thống, nhằm kiểm tra việc tương tác giữa chúng và giúp cho việc test có thể được thực hiện dễ dàng hơn. Tại sao phải sử dụng Có một vài lý do để sử dụng test double trong unit test như: (Chat GPT sinh cho cả đấy ) skip cũng được) (skip lí do thôi nhé )
- Đảm bảo tính cách biệt trong việc kiểm thử một thành phần riêng lẻ của hệ thống, giúp giảm thiểu các ảnh hưởng không mong muốn đến các thành phần khác.
- Tạo điều kiện cho việc tái sử dụng các phần kiểm thử, giảm thiểu thời gian và chi phí cho việc phát triển hệ thống.
- Giúp tách biệt các thành phần của hệ thống để phát triển, test và triển khai một cách độc lập.
Tất nhiên nếu chỉ có vài lí do như vậy thì khá khó nhớ, vậy nên mình sẽ đưa ra ví dụ cho mọi người hiểu rõ hơn: VD: các bạn đang viết một ứng dụng đặt lịch (booking phòng khách sạn) chẳng hạn, thì mỗi lần đặt lịch các bạn sẽ phải call API, tuy nhiên mỗi lần gọi API sẽ phải trả phí và bị limit số lần gọi )
Bạn sẽ nghĩ là
ồ, khó nhằn đây, chả nhẽ việc test nó mà mình cũng mất tiền ư, có cách nào mà vẫn đảm bảo được (input, outcome) cần test mà không mất phí không nhỉ 🤨
===> Và đó chính là lí do mà Test Double tới với chúng ta.
Test Double giúp chúng ta giả lập (input, outcome) ~ (đầu vào, đầu ra)
, và nhờ vậy mà ta có thể control
cũng như dễ dàng setup
và thực hiện kiểm thử đơn vị.
Khá hay đúng không nào, vậy hãy cùng mình jump
vào các thành phần trong Test Double:
Các thành phần chính: (gồm 5 thành phần)
Dummy
Dummy object
thường chỉ dùng cho việc fill parameter lists
trong function
hay constructor
, và thực vậy nên nó cũng không có tác dụng nào hơn cả )
Dummy
cũng thường dùng cho việc ám chỉ rằng ta chỉ tạo ra một vài thứ nhằm để điền cho đủ chứ không nhằm mục đích sử dụng
.
VD:
// userAuthen.ts
class UserAuthen { constructor(){}; function authorize(username: string, password: string){ return false; }
} const userAuthen = new UserAuthen();
const dummyUsername = 'John Doe';
const dummyPassword = 'test';
userAuthen.authorize(dummyUsername, dummyPassword);
Oh, bạn thấy đấy, rằng việc bạn phải pass cho đủ số lượng parameter dù cho chúng chẳng dùng để làm gì
, thì đó chính xác là những gì mà dummy object
làm
Stub
Stub
dùng để cung cấp sẵn một kết quả(outcome)
cho function
mình muốn mock
(thường thì hay dùng cho việc mock
kết quả của external dependency (các đoạn mã khác mà hàm đang phụ thuộc)
.
Spy
Spy
thì là một stub
, tuy nhiên mục đích của nó lại khác, nó dùng để theo dõi các đối tượng trong quá trình thực thi (VD: số lượng đối số
, đối số gọi vào
, được gọi bao nhiêu lần
)
Mock
Mock
khá giống spy + stub
, nó vừa có thể record information
về các đối tượng trong quá trình thực thi, vừa có thể giả định
kết quả trả về. Tuy nhiên mục đích thực của nó sẽ là thay thế cho các hàm thật
.
Fake
Fake object
sẽ thực sự có các triển khai hoạt động, và thông thường nó sẽ sử dụng cho một vài trường hợp cần đến 1 vài field trong 1 object
.
Cũng khá khó để có thể minh hoạ cho các bạn đầy đủ về stub, spy, mock, fake, hơn nữa bài viết này chỉ nhằm mục đích nêu rõ các concept behind the scene of unit test.
Mình có để tài liệu tham khảo phía bên dưới bài viết, nếu bạn nào mong muốn hiểu tường mình hơn thì nhấn vào nhé! 😆
Sau khi tìm hiểu một lượt rồi, mình muốn đưa đến kết luận cho các bạn để các bạn có thể thực sự phân biệt được các loại test double (dù trong thực tế khi viết unit test sẽ không còn để ý lắm đến điều đó nữa :v ):
- We can say that a Mock is a kind of spy, a spy is a kind of stub, and a stub is a kind of dummy.
- But a fake isn’t a kind of any of them. It’s a completely different kind of test double.
3. Mô hình AAA testing
AAA model là một phương pháp đặt tên và phân chia cấu trúc của unit test, nó bao gồm 3 phần chính:
- Arrange: phần chuẩn bị dữ liệu, tạo các mock object, stub, fake object, ... để sẵn sàng cho việc test. Phần này nên được đặt ở đầu của test case.
- Act: phần thực hiện hành động cần được test, ví dụ như gọi một phương thức hay thay đổi một giá trị biến. Phần này cần được đặt ngay sau phần chuẩn bị dữ liệu.
- Assert: phần kiểm tra kết quả trả về từ phần Act, đảm bảo rằng hành động đã được thực hiện đúng và kết quả trả về đúng như mong đợi. Phần này nên được đặt cuối cùng của test case.
Ví dụ:
function sum(a, b) { return a + b;
} describe('sum function', () => { test('should return the correct sum', () => { // Arrange const a = 2; const b = 3; // Act const result = sum(a, b); // Assert expect(result).toBe(5); });
});
Giải thích:
Arrange
: Khởi tạo các giá trị cần thiết cho việc test. Trong ví dụ này, chúng ta khởi tạo giá trị a và b là 2 và 3.Act
: Thực thi hàm cần test và lấy ra kết quả trả về. Trong ví dụ này, chúng ta gọi hàm sum với tham số a và b và lưu kết quả trả về vào biến result.Assert
: So sánh kết quả trả về với giá trị mong đợi. Trong ví dụ này, chúng ta kiểm tra xem kết quả trả về của hàm sum có phải là 5 hay không bằng cách sử dụng expect của Jest. Có thể thấy phương phápAAA
giúp chotest case
được phân chiarõ ràng
vàdễ hiểu
, giúp cho việc đọc và sửa lỗi test case dễ dàng hơn. Ngoài ra, phương pháp này cũng đảm bảo việc chuẩn bị và thực hiện các bước test case được độc lập với nhau, giúp tăngtính ổn định
vàđộ tin cậy
củaunit test
.
Kết luận
Trong phần 1 này, chúng ta đã cùng đi qua các concept quan trọng
hình thành nên unit test
cũng như test double
.
Cảm ơn các bạn đã dành thời gian đọc bài viết, hẹn gặp lại vào các bài viết tiếp theo ^_^.