Ở bài viết How to Write Testable Code có đề cập đế khái niệm Mock
. Vậy Mock
là gì? Và đặc biệt là các sử dụng nó dể thực hiện Unit Test
đơn giản hơn nhé.
What’s Mock
Mock
là một fake class
cho phép ta pre-programmed
ở bước runtime
nhằm giả lập phản ứng của fake class
đó với một lời gọi cụ thể nào đó. Ngoài ra Mock
còn cho phép ta verify
xem các method trong fake class
đã được gọi bao nhiêu lần.
Chà! Định nghĩ nghe có vẻ khó hiểu quá!
Mình sẽ ví dụ test case sau:
Test một phương thức truy vấn thông tin khách hàng mua nhiều hơn 30 triệu từ Database
Khi đó ta có các vấn đề sau khi thực hiện Unit Test
:
- Khi
Unit Test
phải tạo connection tới Database hay sao? Trường hợp cho phép connection xuống Database lỡ có ai đó thay đổi Database thì sao? - Làm sao để biết bên trong hàm thực sự truy vấn thông tin khách hàng mua nhiều hơn 30 triệu hay 29 triệu? Số lần gọi truy vấn xuống Database? Vì nếu ta chuẩn bị data trong Database không tốt có thể kết quả trả về của truy vấn 29tr và 30tr là như nhau, cũng như việc ta truy vấn xuống Database 2 lần trong một lần gọi cũng không thể biết vì kết quả trả về là như nhau.
Và khi này Mock
xuất hiện như một người hùng và giúp ta giải quyết các vấn đề trên.
Ngoài ví dụ trên Mock
còn có thể giúp ta trong các trường hợp sau:
- Đối tượng cung cấp thông tin không xác định. Như: trả về số random, thời gian hiện tại, thời tiết, nhiệt độ…
- Đối tượng cung cấp thông tin qua database, web service, rpc, cache…
- Đối tượng cần tính toán lâu, tốn resource
- Đối tượng chưa tồn tại hoặc có thể bị thay đổi. Như trong ví dụ trong bài Custom Exception mình có thể thực hiện
Unit Test
trước phần SalaryTransfer mà không phải đợi một bạn khác làm phần TransferMoney bằng cáchMock
kết quả 💪. - Các lời gọi liên quan tới context như thread context, security context, scope context…
- …
Note: Có một bài viết chi tiết của Martin Fowler ở đây, có so sánh về Stub
và Mock
và cũng như cung cấp rất nhiều thông tin khác về Test
nên nếu có thời gian các bạn nên đọc qua nhé. Ở đây mình chỉ muốn giới thiệu về Mock
thôi.
Mock and Dependency Injection
Như ví dụ đầu ở What’s Mock nếu theo cách code truyền thống class xử lý và class truy xuất DB có sự liên hệ chặt chẻ với nhau dẫn đến muốn test ta cần phải setup một DB thật và data trong DB cũng phải thật. May mắn nhờ có IoC và DJ
(xem thêm ở Inversion of Control and Dependency Injection), ta có thể injection
class truy xuất DB vào class xử lý trong lúc runtime và injection
mock
class truy xuất DB trong trường hợp test. Phần tiếp theo sẽ làm rõ hơn ý mình muốn nói.
Example Unit Test with Mock
Ví dụ có yêu cầu sau:
Hiển thị full name của những khách hàng mua nhiều hơn N từ Database với N được input vào.
Từ yêu cầu trên mình cần implement CustomerRepository
:
|
|
Và từ các Customer
nhận về mình map()
thành full name như sau:
|
|
Và setup mock
|
|
Và đây là một trong các test case
:
|
|
Một test case
tiêu chuẩn sẽ bao gồm 3 phần:
Setup: từ dòng 5-9, để setup cho mock biết được cần trả về
Customer(firstName = "Tri", lastName = "Le")
khicustomerRepository.findByTotalBuyPriceGreaterThan(3_000)
được gọiAssert: từ dòng 11-14 so sánh kết quả mong muốn với kết quả thực tế.
Verify: dòng 16 kiểm tra xem thật sự mock đã được gọi hay không, được gọi với các tham số như thế nào cũng như số lần được gọi. Một số bạn hay bỏ qua bước này nhưng thật ra nó rất quan trọng vì sẽ tránh được các tình huống như code gọi khác số lần mong muốn, hãy tưởng tượng như ở ví dụ bài Clean Code with Exception chuyển lương nhiều lần cho nhân viên thì như thế nào 😓 hoặc lời gọi cần được thực hiện nhưng lại không tham gia vào kết quả trả về 👿.
Note 1: Ở đây mình sử dụng Spring JPA
nên không cần phải có implement class
. Các trường hợp khác các bạn cần implement concrete class
và Spring
sẽ tự động biết mà inject
đúng concrete class
khi ứng dụng được khởi chạy. Mình sẽ có có bài chi tiết hơn nha.
Note 2: Phần code sample mình update vào repo này nhé.
Kết: Với sự giúp sức của Mock
việc Unit Test
trở nên đơn giản và đáng tin cậy hơn rất nhiều.
Cảm ơn các bạn đã đọc tới đây và mong các bạn sẽ thích bài viết này.