Mỗi lần bạn "test thủ công" bằng cách mở browser và click, bạn đang làm một việc có thể tự động hóa. Và khi refactor code, bạn sẽ không biết mình đã break thứ gì. Test tự động giải quyết cả hai vấn đề.
Cài Đặt Và Test Đầu Tiên
pip install pytest pytest-django pytest-cov
# test_calculator.py
def add(a, b):
return a + b
def test_add_two_numbers():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
def test_add_floats():
assert add(0.1, 0.2) == pytest.approx(0.3)
# Chạy
pytest test_calculator.py -v
Fixtures — Setup Và Teardown
import pytest
@pytest.fixture
def sample_post():
return {
'title': 'Test Post',
'content': 'Nội dung test',
'status': 'published'
}
@pytest.fixture
def db_post(db, sample_post):
from posts.models import Post, User
author = User.objects.create_user('testuser')
return Post.objects.create(author=author, **sample_post)
def test_post_title(sample_post):
assert 'Test' in sample_post['title']
def test_post_is_published(db_post):
assert db_post.status == 'published'
parametrize — Test Nhiều Input
import pytest
from posts.utils import slugify_vi
@pytest.mark.parametrize("input_str, expected", [
("Hello World", "hello-world"),
("Tiếng Việt", "tieng-viet"),
(" spaces ", "spaces"),
("Special@Chars!", "specialchars"),
])
def test_slugify(input_str, expected):
assert slugify_vi(input_str) == expected
# pytest chạy test 4 lần với 4 bộ input khác nhau
Mocking — Cô Lập Dependencies
from unittest.mock import patch, MagicMock
def test_send_email_called():
with patch('posts.tasks.send_mail') as mock_send:
from posts.tasks import send_welcome_email
send_welcome_email(user_id=1)
mock_send.assert_called_once()
def test_external_api():
mock_response = MagicMock()
mock_response.json.return_value = {'status': 'ok'}
mock_response.status_code = 200
with patch('requests.get', return_value=mock_response):
result = fetch_data('https://api.example.com')
assert result['status'] == 'ok'
Django Integration Tests
import pytest
from django.test import Client
from django.urls import reverse
@pytest.fixture
def client():
return Client()
@pytest.fixture
def logged_in_client(client, django_user_model):
user = django_user_model.objects.create_user(
username='testuser', password='testpass'
)
client.login(username='testuser', password='testpass')
return client
def test_homepage_returns_200(client):
response = client.get(reverse('posts:index'))
assert response.status_code == 200
def test_create_post_requires_login(client):
response = client.post('/admin/posts/post/add/')
assert response.status_code == 302 # Redirect to login
Coverage Report
# Chạy với coverage
pytest --cov=posts --cov-report=html
# Xem report trong browser
open htmlcov/index.html
# Đặt threshold trong CI
pytest --cov=posts --cov-fail-under=80
Test coverage 100% không có nghĩa là không có bug — nó chỉ có nghĩa là mọi dòng code đều được chạy qua. Quan trọng hơn là test đúng behavior, không phải test implementation.
Kết Luận
Bắt đầu với test cho business logic quan trọng nhất — những function tính toán, xử lý data. Sau đó thêm integration test cho API endpoints. Đừng cố đạt 100% coverage ngay từ đầu — bắt đầu từ 0% lên 50% đã tạo ra sự khác biệt rất lớn.
Chưa có bình luận. Hãy là người đầu tiên!