Sử Dụng Mock Với Unittest Trong Python
Unittest là gì
Unit tes là các test dùng để test kiến trúc nội tại của chương trình, unit test gắn liền với thiết kế chương trình. Khi viết unit test, tôi thường kiểm tra xem các hàm có được gọi và gọi đúng với parameter cần thiết hay không. Mỗi một unit test chỉ nên test 1 thứ.
Đặc điểm của unit test là rất ngắn, một test case chỉ nên được viết dưới 10 dòng. Nếu bạn cần viết hơn, hãy suy nghĩ lại về thiết kế của mình. Các developer nên viết unit test cho các phần code mình viết. Tôi thường setup môi trường phát triển, để bất cứ khi nào bạn commit một đoạn code, chương trình quản lý mã nguồn sẽ chạy test tự động liên quan đến đoạn code đó. Điều này giúp tôi kiểm tra ngay được code mình viết có gây ảnh hưởng tới các phần khác hay không.
Chính vì thế, unit test cần được chạy rất nhanh. Mỗi một đoạn code chỉ nên được test một lần. Nếu bạn có 2 method A và B, B gọi đến A, code A đã được viết test, thì code test cho B không nên test lại A lần nữa
Unit test không nhất thiết phải cover hết code của bạn. Nếu cover được đầy đủ thì rất tốt, nhưng công sức bỏ ra sẽ rất lớn. Hãy viết unit test đủ để bạn thấy tự tin khi deploy code của mình.
Sử dụng mock với unittest
Tôi có một class sinh ra empty image với kích thước có sẵn, kèm theo barcode image ở vị trí đã được định trước
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Để sinh ra barcode, tôi connect tới một webservice và lấy dữ liệu về. Hàm utils.get_image_from_url
trả về Image object từ content của một URL.
decorator @lazy
biến một method của class thành property của class đó, và cached lại result, do đó nếu bạn gọi tới property lần thứ hai, bạn sẽ sự dụng lại giá trị từ trong cached
Đây là code test cho class trên
1 2 3 4 5 6 7 8 9 10 |
|
Đoạn code trên generate BackCoverImage với một barcode xác định trước, và so sánh check sum của image được sinh ra, với image mà tôi đã sinh ra từ trước
Tuy nhiên, có vấn đề ở đây. Đó là mỗi lần tôi chạy code test, tôi sẽ phải connect tới service của http://www.barcoding.com
. Tức là tốc độ của code test sẽ bị ảnh hưởng bởi network, hơn nữa hàm run()
của class BackCoverImage gọi tới barcode_image
, nếu chúng ta test như trên, thì code test không phải là một unit test, mà là một integration test. Để giải quyết vấn đề này, chúng ta sử dụng thư viện mock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Tôi đã mock thuộc tính barcode_image của class BackCoverImage với PropertyMock
. Tốc độ của test được cải thiện đáng kể, từ 3-4s khi test không có mock, xuống < 0.3s
Xét tiếp ví dụ tiếp theo, tôi có một class Order, mỗi khi muốn order, tôi cần sinh ra một pdf file cho class Order. Pdf file này cần có một page được sinh ra từ class BackCoverImage
1 2 3 4 5 6 7 8 9 10 |
|
Để test hàm create_pdf_file
, chúng ta sẽ mock BackCoverImage.run
với một Image và kiểm tra xem hàm đó có được gọi hay không?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Kết luận
Bằng việc có một bộ test để đảm bảo hệ thống đang hoạt động đúng, bạn giúp các lập trình viên khác trong đội của bạn, hay chính bản thân bạn (sau một thời gian) tự tin khi viết thêm/thay đổi code, mà không sợ ảnh hướng tới logic của những chức năng khác. Điều này đặc biệt hữu ích khi bạn muốn refactor code.
Tuy nhiên để làm điều đó, bộ test của bạn cần chạy trong một thời gian ngắn. Nếu bộ test của bọn tốn vài phút mới thực hiện xong, thì thật khó để yêu cầu các developer khác chạy nó mỗi lần họ commit code.
Bằng cách sử dụng mock, bạn có thể isolate các unittest, đảm bảo mỗi một đoạn code chỉ cần test duy nhất một lần, qua đó tăng tốc độ của unittest lên rất nhiều.