ducdev
Python / Bài viết

pytest — Testing Trong Python Từ Unit Test Đến Integration Test

Code không có test là code chờ bị break. pytest là framework testing mạnh nhất trong Python ecosystem — fixtures, parametrize, mocking và coverage report.

a
admin
17/09/2025 · 2 phút đọc · 0 lượt xem
Chia sẻ

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.

#pytest #Testing #Python #TDD
a
Tác giả
admin

Lập trình viên, yêu thích chia sẻ kiến thức về công nghệ và phát triển phần mềm.

Bình luận

Chưa có bình luận. Hãy là người đầu tiên!

Để lại bình luận

Bình luận sẽ được duyệt trước khi hiển thị.