Snapshot Testing với Pytest-snapshot: Đừng để Assert thủ công làm khổ bạn

Python tutorial - IT technology blog
Python tutorial - IT technology blog

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.

Share: