Cơn ác mộng mang tên “Assert thủ công”
Hãy tưởng tượng bạn vừa viết xong một API trả về object JSON dài 150 dòng. Cách làm thông thường? Bạn ngồi gõ mỏi tay từng dòng assert data['user']['address']['city'] == 'Hanoi'. Cách này thực sự là một cái bẫy.
Chỉ cần cấu trúc database thay đổi nhẹ, hàng chục test case sẽ lăn ra chết. Việc ngồi sửa lại đống assert đó không chỉ gây ức chế mà còn cực kỳ tốn thời gian. Mình từng mất 3 tiếng đồng hồ chỉ để cập nhật test sau khi refactor một hàm format dữ liệu. Thật lãng phí!
Snapshot Testing xuất hiện để giải quyết vấn đề này. Thay vì so sánh từng giá trị, chúng ta chụp lại một “bức ảnh” kết quả chuẩn và lưu vào file. Khi chạy test lần sau, máy sẽ tự so sánh kết quả mới với bức ảnh này. Nếu lệch dù chỉ một dấu phẩy, hệ thống sẽ báo lỗi ngay lập tức.
Tại sao bạn nên đổi sang Snapshot Testing?
Dưới đây là bảng so sánh dựa trên kinh nghiệm thực tế của mình khi áp dụng vào các dự án quy mô lớn:
| Tiêu chí | Manual Assertions | Snapshot Testing |
|---|---|---|
| Tốc độ viết test | Rất chậm (gõ từng key) | Gần như tức thì |
| Độ phủ (Coverage) | Thường chỉ check vài field chính | Kiểm soát 100% dữ liệu |
| Bảo trì | Sửa code test cực hình | Update bằng 1 câu lệnh |
| Độ chính xác | Dễ bỏ sót field nhỏ | Chính xác tuyệt đối |
Triển khai với Pytest-snapshot
Thư viện pytest-snapshot là lựa chọn hàng đầu cho Python. Nó nhẹ, dễ dùng và tích hợp thẳng vào workflow của Pytest.
1. Cài đặt nhanh
Mở terminal và chạy lệnh sau:
pip install pytest-snapshot
2. Kiểm thử dữ liệu JSON phức tạp
Giả sử bạn có hàm get_user_profile trả về một dict lồng nhau nhiều lớp. Thay vì viết 20 dòng assert, hãy dùng snapshot:
import pytest
import json
def get_user_profile(user_id):
return {
"id": user_id,
"name": "Nguyen Van A",
"metadata": {
"login_count": 10,
"preferences": {"theme": "dark", "lang": "vi"}
},
"tags": ["active", "premium"]
}
def test_get_user_profile(snapshot):
user_data = get_user_profile(1)
# Format JSON đẹp để file snapshot dễ đọc hơn
snapshot.assert_match(json.dumps(user_data, indent=4), "user_profile_1.json")
Khi chạy pytest lần đầu, thư mục snapshots sẽ tự động được tạo. Nếu sau này logic bị lỗi làm mất trường tags, Pytest sẽ chỉ ra chính xác dòng nào bị mất. Rất trực quan!
3. Kiểm thử HTML Render
Kiểm thử giao diện HTML thường rất khó chịu vì cấu trúc thẻ phức tạp. Snapshot Testing xử lý việc này chỉ trong 3 dòng code:
def test_render_homepage(snapshot, client):
response = client.get("/")
assert response.status_code == 200
snapshot.assert_match(response.data.decode("utf-8"), "homepage.html")
Mẹo nhỏ: Nếu cần kiểm tra nhanh các mẫu Regex trước khi đưa vào code, mình thường dùng Regex Tester. Công cụ này giúp đảm bảo chuỗi output chuẩn chỉnh trước khi bạn lưu snapshot vĩnh viễn.
Xử lý dữ liệu động: Kẻ thù của Snapshot
Dữ liệu động như created_at hay timestamp sẽ khiến snapshot bị lỗi liên tục. Chỉ cần lệch 1 giây, test sẽ fail. Có hai cách xử lý triệt để:
- Dùng Freezegun: Cố định thời gian hệ thống tại một thời điểm duy nhất.
- Scrubbing: Ghi đè các giá trị động bằng một chuỗi cố định trước khi so sánh.
Ví dụ về cách Scrubbing đơn giản:
def test_api_with_dynamic_data(snapshot):
raw_data = call_real_api()
# Ghi đè timestamp để tránh lỗi lệch thời gian
raw_data["created_at"] = "fixed-timestamp"
snapshot.assert_match(json.dumps(raw_data, indent=4), "api_response.json")
Cập nhật Snapshot khi logic thay đổi
Nếu bạn chủ động thay đổi logic và muốn cập nhật lại toàn bộ file snapshot, đừng sửa tay. Hãy dùng lệnh:
pytest --snapshot-update
Lưu ý quan trọng: Luôn dùng git diff để kiểm tra lại các thay đổi sau khi update. Đừng vô tình biến một con bug thành “kết quả chuẩn” mới.
Lời kết
Snapshot Testing không phải là chiếc đũa thần, nhưng nó giúp bạn bớt việc. Hãy dùng nó cho dữ liệu lớn, cấu trúc phức tạp hoặc khi cần Regression testing. Với các logic tính toán đơn giản như 1 + 1 = 2, assert truyền thống vẫn là lựa chọn tốt nhất. Hãy thử áp dụng vào dự án ngay hôm nay, bạn sẽ thấy việc viết test trở nên dễ thở hơn nhiều.

