🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

أفضل ممارسات اختبار بايثون 2026: الفرضية والسخرية والتغطية

⏱️4 min read  ·  815 words

تعد كتابة الاختبارات الجيدة إحدى مهارات المطورين الكبار. إلى جانب إعداد pytest الأساسي، يتطلب اختبار Python للإنتاج في عام 2026 فهم بنية الاختبار والتركيبات واستراتيجيات السخرية والاختبار القائم على الخاصية وتكامل CI. يغطي هذا الدليل أنماط الاختبار المتقدمة المستخدمة في مشاريع بايثون الاحترافية.

هندسة الاختبار: هرم الاختبار

Testing Pyramid:

         /   E2E   \       - Few, slow, expensive
        /  Integration \   - Some, moderate
       /  Unit Tests    \  - Many, fast, cheap

Rule: 70% unit, 20% integration, 10% E2E

Good test properties:
- Fast: unit tests < 1ms each
- Independent: no shared state between tests
- Repeatable: same result every run
- Self-validating: clear pass/fail
- Timely: written alongside/before code

تركيبات متقدمة

import pytest
import asyncio
from unittest.mock import MagicMock, AsyncMock, patch
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

# Fixture scope hierarchy
@pytest.fixture(scope="session")
def event_loop():
    loop = asyncio.new_event_loop()
    yield loop
    loop.close()

@pytest.fixture(scope="session")
async def db_engine():
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    await create_all_tables(engine)
    yield engine
    await engine.dispose()

@pytest.fixture
async def db(db_engine):
    async with AsyncSession(db_engine) as session:
        yield session
        await session.rollback()  # clean state after each test

@pytest.fixture
def mock_email_service():
    with patch("app.services.email.EmailService.send") as mock:
        mock.return_value = True
        yield mock

# Factory fixture pattern
@pytest.fixture
def make_user(db):
    async def _factory(email="test@example.com", role="user", **kwargs):
        user = User(email=email, role=role, **kwargs)
        db.add(user)
        await db.commit()
        return user
    return _factory

# Usage
async def test_user_creation(db, make_user, mock_email_service):
    user = await make_user(email="alice@example.com")
    assert user.id is not None
    mock_email_service.assert_called_once_with(
        to="alice@example.com",
        subject="Welcome!"
    )

الاختبار المبني على الملكية مع الفرضية

pip install hypothesis

from hypothesis import given, settings, strategies as st
from hypothesis.extra.pandas import data_frames, column

# Test with arbitrary inputs
@given(st.integers(), st.integers())
def test_add_commutative(a: int, b: int):
    assert add(a, b) == add(b, a)

@given(st.lists(st.integers(), min_size=1))
def test_sort_preserves_length(lst: list[int]):
    sorted_lst = sorted(lst)
    assert len(sorted_lst) == len(lst)

@given(st.text(min_size=1, max_size=100))
def test_hash_always_returns_string(text: str):
    result = hash_password(text)
    assert isinstance(result, str)
    assert len(result) > 0

# Complex data generation
@given(
    st.builds(
        CreateUserRequest,
        email=st.emails(),
        name=st.text(min_size=2, max_size=50).filter(str.strip),
        password=st.text(min_size=8, max_size=128),
    )
)
@settings(max_examples=50)
def test_user_creation_accepts_valid_data(request: CreateUserRequest):
    user = create_user(request)
    assert user.email == request.email.lower()

# Stateful testing
from hypothesis.stateful import RuleBasedStateMachine, rule, invariant

class QueueMachine(RuleBasedStateMachine):
    def __init__(self):
        super().__init__()
        self.model = []
        self.implementation = Queue()

    @rule(value=st.integers())
    def enqueue(self, value: int):
        self.model.append(value)
        self.implementation.enqueue(value)

    @rule()
    def dequeue(self):
        if self.model:
            expected = self.model.pop(0)
            actual = self.implementation.dequeue()
            assert actual == expected

    @invariant()
    def sizes_match(self):
        assert len(self.model) == self.implementation.size()

السخرية من أفضل الممارسات

from unittest.mock import MagicMock, AsyncMock, patch, call
import pytest

# Mock at the right level — where it's USED, not where it's defined
# If service.py imports: from utils import send_email
# Mock: "service.send_email" not "utils.send_email"

def test_order_sends_confirmation(mocker):
    # Mock async method
    mock_send = mocker.patch("app.services.order.send_email", new=AsyncMock())
    mock_send.return_value = True

    order = asyncio.run(create_order(user_id=1, items=[...]))

    mock_send.assert_called_once()
    call_kwargs = mock_send.call_args.kwargs
    assert call_kwargs["to"] == "alice@example.com"
    assert "order confirmation" in call_kwargs["subject"].lower()

# Mock with side effects
def test_retry_on_network_error(mocker):
    mock_get = mocker.patch("requests.get")
    mock_get.side_effect = [
        ConnectionError("Network error"),  # first call fails
        ConnectionError("Network error"),  # second call fails
        MagicMock(json=lambda: {"data": "success"}),  # third succeeds
    ]

    result = fetch_with_retry("https://api.example.com")
    assert result == {"data": "success"}
    assert mock_get.call_count == 3

# Context manager mock
def test_file_processing(mocker):
    mock_open = mocker.mock_open(read_data="line1
line2
line3")
    mocker.patch("builtins.open", mock_open)

    result = count_lines("any_file.txt")
    assert result == 3

اختبارات التكامل مع الخدمات الحقيقية

# Using pytest-docker for real database in CI
import pytest
import psycopg2
from testcontainers.postgres import PostgresContainer

@pytest.fixture(scope="session")
def postgres():
    with PostgresContainer("postgres:16") as pg:
        yield pg

@pytest.fixture(scope="session")
def db_url(postgres):
    return postgres.get_connection_url()

# Using respx to mock HTTP at socket level
import respx
import httpx

@pytest.mark.asyncio
async def test_external_api_integration():
    with respx.mock:
        respx.get("https://api.github.com/users/alice").mock(
            return_value=httpx.Response(200, json={"login": "alice", "followers": 1000})
        )
        result = await fetch_github_user("alice")
        assert result["followers"] == 1000

تغطية الاختبار ومقاييس الجودة

# pytest.ini or pyproject.toml
# [tool.pytest.ini_options]
# addopts = "--cov=app --cov-report=html --cov-report=term-missing --cov-fail-under=80"

# Run with coverage
pytest --cov=app --cov-report=html

# Mutation testing (verify your tests actually catch bugs)
pip install mutmut
mutmut run --paths-to-mutate app/services/
mutmut results  # shows surviving mutants (untested code paths)

# Performance benchmarks
pip install pytest-benchmark

@pytest.mark.benchmark
def test_encryption_performance(benchmark):
    result = benchmark(encrypt_password, "test_password")
    assert len(result) > 0  # benchmark.stats shows timing

أفضل ممارسات اختبار Python في عام 2026: كتابة الاختبارات جنبًا إلى جنب مع التعليمات البرمجية، واستخدام تركيبات المصنع لبيانات الاختبار، والمحاكاة على الحدود وليس داخل التنفيذ، واستخدام الاختبار القائم على الخاصية للتحقق من صحة البيانات، وقياس درجة الطفرة للتحقق من أن الاختبارات تلتقط الأخطاء بالفعل. تغطية 80% هي الأرضية، وليس السقف – يجب أن تكون المسارات عالية القيمة 100%.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা