بيتيست هو المعيار الفعلي لاختبار كود بايثون في عام 2026 – وهو أبسط وأقوى من اختبار الوحدة المدمج. تعمل الاختبارات الجيدة على كشف الأخطاء قبل الإنتاج، وتمكين إعادة البناء الواثق، وتوثيق كيفية تصرف التعليمات البرمجية الخاصة بك. يأخذك هذا الدليل من اختبارك الأول إلى الأنماط المتقدمة.
📋 Table of Contents
- الإعداد
- اختبارك الأول
- نموذج AAA (ترتيب، تصرف، تأكيد)
- تركيبات – إعداد اختبار قابل لإعادة الاستخدام
- المعلمات – اختبار العديد من الحالات
- السخرية – عزل التعليمات البرمجية عن التبعيات
- اختبار الاستثناءات وحالات الحافة
- قياس التغطية
- التكوين (pytest.ini)
- أفضل الممارسات
- الأسئلة المتداولة
- pytest يجعل اختبار بايثون بسيطًا وقويًا. ابدأ بالبسيط
الإعداد
pip install pytest pytest-cov
# Project structure
myproject/
├── src/
│ └── calculator.py
├── tests/
│ └── test_calculator.py
└── pytest.ini
اختبارك الأول
# src/calculator.py
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# tests/test_calculator.py
from src.calculator import add, divide
import pytest
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_divide():
assert divide(10, 2) == 5
def test_divide_by_zero_raises():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
# Run the tests
pytest # run all tests
pytest -v # verbose (shows each test name)
pytest tests/test_calculator.py::test_add # run one test
pytest -k "divide" # run tests matching "divide"
نموذج AAA (ترتيب، تصرف، تأكيد)
def test_user_discount():
# Arrange — set up test data
user = User(name="Alice", is_premium=True)
cart = Cart(total=100)
# Act — call the code under test
final_price = apply_discount(cart, user)
# Assert — verify the result
assert final_price == 80 # 20% premium discount
تركيبات – إعداد اختبار قابل لإعادة الاستخدام
import pytest
@pytest.fixture
def sample_user():
# Provides a fresh user for each test that needs one
return User(name="Alice", email="alice@example.com")
@pytest.fixture
def db_connection():
# Setup and teardown - code after yield runs as cleanup
conn = create_test_db()
yield conn # test runs here
conn.close() # cleanup after test
# Tests receive fixtures as arguments
def test_user_email(sample_user):
assert sample_user.email == "alice@example.com"
def test_save_user(sample_user, db_connection):
db_connection.save(sample_user)
assert db_connection.count() == 1
المعلمات – اختبار العديد من الحالات
import pytest
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add_many_cases(a, b, expected):
assert add(a, b) == expected
# Runs 4 separate tests, one per tuple — clear failure reporting per case
السخرية – عزل التعليمات البرمجية عن التبعيات
from unittest.mock import patch, MagicMock
# Mock an external API call so tests don't hit the network
def test_fetch_user_data():
with patch('src.api.requests.get') as mock_get:
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
mock_get.return_value.status_code = 200
result = fetch_user_data(1)
assert result["name"] == "Alice"
mock_get.assert_called_once_with("https://api.example.com/users/1")
# Mock with pytest-mock (cleaner)
def test_with_mocker(mocker):
mock_db = mocker.patch('src.service.database')
mock_db.get_user.return_value = {"name": "Bob"}
result = get_user_name(1)
assert result == "Bob"
اختبار الاستثناءات وحالات الحافة
def test_raises_on_invalid_input():
with pytest.raises(ValueError) as exc_info:
validate_age(-5)
assert "must be positive" in str(exc_info.value)
def test_edge_cases():
assert process([]) == [] # empty input
assert process([1]) == [1] # single item
assert process(None) is None # None handling
قياس التغطية
# Run tests with coverage report
pytest --cov=src --cov-report=term-missing
# Output shows which lines aren't tested:
# Name Stmts Miss Cover Missing
# src/calculator.py 10 1 90% 15
# Generate an HTML report
pytest --cov=src --cov-report=html
# Open htmlcov/index.html to see line-by-line coverage
التكوين (pytest.ini)
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --cov=src --cov-report=term-missing
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: integration tests
أفضل الممارسات
- مفهوم تأكيد واحد لكل اختبار: يتحقق كل اختبار من سلوك واحد — تشير حالات الفشل مباشرة إلى المشكلة
- الأسماء الوصفية:
test_divide_by_zero_raises_valueerrorيخبرك بما حدث دون قراءة الكود - سلوك الاختبار وليس التنفيذ: اختبر ما تفعله الوظيفة، وليس كيف – حتى لا تؤدي إعادة البناء إلى كسر الاختبارات
- اجعل الاختبارات سريعة ومعزولة: يجب أن يتم تشغيل كل اختبار بشكل مستقل بأي ترتيب
- التبعيات الخارجية الوهمية: يجب الاستهزاء بقواعد البيانات وواجهات برمجة التطبيقات وأنظمة الملفات في اختبارات الوحدة
- تهدف إلى تغطية ذات معنى: يعد 80%+ هدفًا جيدًا، ولكنه يغطي المسارات الحرجة بدقة بدلاً من مطاردة 100%
الأسئلة المتداولة
س: pytest مقابل Unittest؟
ج: pytest أبسط (عادي ، ولا توجد فئات نمطية)، وأكثر قوة (التركيبات، والمعاملات)، ويحتوي على نظام بيئي غني بالمكونات الإضافية. يمكنه تشغيل اختبارات الوحدة أيضًا. استخدم pytest للمشاريع الجديدة.assertس: ما مقدار تغطية الاختبار التي أحتاجها؟
ج: 80% هدف مشترك. لكن التغطية هي دليل، وليست هدفًا – فالتغطية بنسبة 100% مع التأكيدات الضعيفة أسوأ من 80% مع اختبارات شاملة للمنطق النقدي. ركز على تغطية الكود المهم.
س: هل يجب علي الاستهزاء بقاعدة البيانات أو استخدام قاعدة بيانات اختبارية؟
ج: سخر منه في اختبارات الوحدة (سريع، معزول). استخدم قاعدة بيانات اختبارية حقيقية في اختبارات التكامل (التحقق من عمل الاستعلامات الفعلية). كلاهما لهما مكانهما – اختبارات الوحدة للمنطق، واختبارات التكامل لطبقة البيانات.
س: ما الفرق بين التركيب والوظيفة العادية؟
ج: تتم إدارة التركيبات بواسطة pytest — حيث يتم إعدادها قبل الاختبارات التي تطلبها ويمكن تنظيفها بعد ذلك (عبر العائد). أنها توفر تبعيات اختبار معزولة وقابلة لإعادة الاستخدام مع إدارة دورة الحياة التلقائية.
س: كيف يمكنني اختبار التعليمات البرمجية غير المتزامنة؟
ج: تثبيت
ووضع علامة على الاختبارات باستخدامpytest-asyncio. ثم يمكنك@pytest.mark.asyncio داخل وظائف الاختبار الخاصة بك لاختبار وظائف غير المتزامنة.awaitالخلاصة
pytest يجعل اختبار بايثون بسيطًا وقويًا. ابدأ بالبسيط
البيانات في نمط AAA، واستخدام التركيبات للإعداد القابل لإعادة الاستخدام، وتحديد المعلمات لتغطية العديد من الحالات بإيجاز، ومحاكاة التبعيات الخارجية لإبقاء الاختبارات سريعة ومعزولة. قم بقياس التغطية للعثور على التعليمات البرمجية التي لم يتم اختبارها، ولكن قم بإعطاء الأولوية لاختبار المنطق النقدي بدقة بدلاً من السعي لتحقيق 100%. تعتبر الاختبارات الجيدة بمثابة استثمار يؤتي ثماره في كل مرة تقوم فيها بإعادة البناء أو إضافة ميزات بثقة.assert البيانات في نمط AAA، واستخدام التركيبات للإعداد القابل لإعادة الاستخدام، وتحديد المعلمات لتغطية العديد من الحالات بإيجاز، ومحاكاة التبعيات الخارجية لإبقاء الاختبارات سريعة ومعزولة. قم بقياس التغطية للعثور على التعليمات البرمجية التي لم يتم اختبارها، ولكن قم بإعطاء الأولوية لاختبار المنطق النقدي بدقة بدلاً من السعي لتحقيق 100%. تعتبر الاختبارات الجيدة بمثابة استثمار يؤتي ثماره في كل مرة تقوم فيها بإعادة البناء أو إضافة ميزات بثقة.
🔗 Share this article
✍️ Leave a Comment