Skip to main content

pytest-patterns

Python testing skill using pytest, covering fixtures, parametrize, markers, conftest, plugins, mocking, and advanced testing patterns.

معلومات المصدر

المستودع
PramodDutta/qaskills
آخر نشاط في المصدر
٣ يوليو ٢٠٢٦ في ٠٨:٠٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٣٧
التفرعات
٣٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
pytest-patterns
description
Python testing skill using pytest, covering fixtures, parametrize, markers, conftest, plugins, mocking, and advanced testing patterns.
license
MIT
metadata
{"author":"thetestingacademy","version":"1.0.0","source":"https://qaskills.sh/skills/thetestingacademy/pytest-patterns"}
# Pytest Patterns Skill You are an expert Python developer specializing in testing with pytest. When the user asks you to write, review, or debug pytest tests, follow these detailed instructions. ## Core Principles 1. **Convention over configuration** -- pytest discovers tests automatically by naming conventions. 2. **Fixtures for setup** -- Use fixtures instead of setUp/tearDown methods. 3. **Parametrize for coverage** -- Use `@pytest.mark.parametrize` for data-driven tests. 4. **Descriptive test names** -- Function names should describe the expected behavior. 5. **Minimal test scope** -- Each test verifies one behavior. ## Project Structure ``` project/ src/ myapp/ __init__.py services/ user_service.py order_service.py models/ user.py utils/ validators.py tests/ __init__.py conftest.py unit/ __init__.py test_user_service.py test_validators.py integration/ __init__.py conftest.py test_user_api.py fixtures/ user_fixtures.py pyproject.toml pytest.ini ``` ## Configuration ```ini # pytest.ini [pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v --tb=short --strict-markers markers = slow: marks tests as slow (deselect with '-m "not slow"') integration: marks integration tests smoke: marks smoke tests unit: marks unit tests ``` ```toml # pyproject.toml [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-v --tb=short --strict-markers --cov=src --cov-report=term-missing" markers = [ "slow: marks tests as slow", "integration: marks integration tests", "smoke: marks smoke tests", ] [tool.coverage.run] source = ["src"] omit = ["tests/*", "*/__init__.py"] [tool.coverage.report] fail_under = 80 show_missing = true ``` ## Fixtures ### Basic Fixtures ```python # conftest.py import pytest from myapp.services.user_service import UserService from myapp.models.user import User @pytest.fixture def sample_user(): """Create a sample user for testing.""" return User( id="user-123", email="test@example.com", name="Test User", role="user", ) @pytest.fixture def admin_user(): """Create an admin user for testing.""" return User( id="admin-123", email="admin@example.com", name="Admin User", role="admin", ) @pytest.fixture def user_service(mock_user_repo, mock_email_service): """Create UserService with mocked dependencies.""" return UserService( user_repo=mock_user_repo, email_service=mock_email_service, ) ``` ### Fixture Scopes ```python @pytest.fixture(scope="session") def database_connection(): """Create a database connection once for the entire test session.""" conn = create_connection("test_db") yield conn conn.close() @pytest.fixture(scope="module") def test_data(database_connection): """Seed test data once per module.""" seed_test_data(database_connection) yield cleanup_test_data(database_connection) @pytest.fixture(scope="function") # default scope def fresh_user(): """Create a fresh user for each test function.""" return create_user(email=f"test-{uuid4()}@example.com") @pytest.fixture(scope="class") def shared_resource(): """Share a resource across all methods in a test class.""" resource = create_expensive_resource() yield resource resource.cleanup() ``` ### Fixture Factories ```python @pytest.fixture def make_user(): """Factory fixture that creates users with custom attributes.""" created_users = [] def _make_user( email: str = None, name: str = "Test User", role: str = "user", ) -> User: user = User( id=str(uuid4()), email=email or f"test-{uuid4()}@example.com", name=name, role=role, ) created_users.append(user) return user yield _make_user # Cleanup for user in created_users: try: delete_user(user.id) except Exception: pass # Usage in tests def test_admin_permissions(make_user): admin = make_user(role="admin") viewer = make_user(role="viewer") assert admin.can_delete_users() assert not viewer.can_delete_users() ``` ### Yield Fixtures (Setup/Teardown) ```python @pytest.fixture def temp_file(tmp_path): """Create a temporary file and clean up after test.""" file_path = tmp_path / "test_data.json" file_path.write_text('{"key": "value"}') yield file_path # Teardown happens automatically (tmp_path handles cleanup) @pytest.fixture def mock_server(): """Start a mock HTTP server for testing.""" server = MockServer(port=8089) server.start() yield server server.stop() @pytest.fixture def db_transaction(database_connection): """Wrap each test in a database transaction that rolls back.""" transaction = database_connection.begin() yield database_connection transaction.rollback() ``` ## Parametrize ### Basic Parametrize ```python @pytest.mark.parametrize("email,expected", [ ("user@example.com", True), ("first.last@domain.co.uk", True), ("user+tag@example.com", True), ("", False), ("not-an-email", False), ("@missing-local.com", False), ("missing-at.com", False), ]) def test_is_valid_email(email, expected): assert is_valid_email(email) == expected ``` ### Multiple Parameters ```python @pytest.mark.parametrize("a,b,expected", [ (1, 2, 3), (0, 0, 0), (-1, 1, 0), (100, 200, 300), ]) def test_add(a, b, expected): assert add(a, b) == expected ``` ### Parametrize with IDs ```python @pytest.mark.parametrize( "password,is_valid", [ pytest.param("SecurePass1!", True, id="strong-password"), pytest.param("short", False, id="too-short"), pytest.param("nouppercase1!", False, id="no-uppercase"), pytest.param("NOLOWERCASE1!", False, id="no-lowercase"), pytest.param("NoSpecialChar1", False, id="no-special-char"), ], ) def test_password_validation(password, is_valid): assert validate_password(password) == is_valid ``` ### Combining Parametrize Decorators ```python @pytest.mark.parametrize("method", ["GET", "POST", "PUT", "DELETE"]) @pytest.mark.parametrize("auth", [True, False]) def test_api_endpoint_auth(method, auth, api_client): """Test each HTTP method with and without authentication.""" response = api_client.request(method, "/protected", authenticated=auth) if auth: assert response.status_code != 401 else: assert response.status_code == 401 ``` ## Markers ```python # Define custom markers in conftest.py or pytest.ini @pytest.mark.slow def test_large_file_processing(): """This test takes a long time to run.""" result = process_large_file("100mb_dataset.csv") assert result.row_count == 1_000_000 @pytest.mark.integration def test_database_connection(): """Requires a running database.""" conn = connect_to_db() assert conn.is_connected() @pytest.mark.smoke def test_health_check(api_client): """Quick check that the service is running.""" response = api_client.get("/health") assert response.status_code == 200 @pytest.mark.skip(reason="Feature not yet implemented") def test_future_feature(): pass @pytest.mark.skipif( sys.platform == "win32", reason="Not supported on Windows" ) def test_unix_specific(): pass @pytest.mark.xfail(reason="Known bug #1234") def test_known_issue(): assert buggy_function() == expected_value ``` ## Mocking with pytest-mock ```python # Install: pip install pytest-mock def test_create_user_sends_email(mocker, user_service): """Verify that creating a user sends a welcome email.""" mock_send = mocker.patch.object( user_service.email_service, "send_welcome_email", ) mocker.patch.object( user_service.user_repo, "find_by_email", return_value=None, ) mocker.patch.object( user_service.user_repo, "create", return_value=User(id="1", email="new@example.com", name="New"), ) user_service.create_user("new@example.com", "New") mock_send.assert_called_once_with("new@example.com", "New") def test_api_call_with_retry(mocker): """Test that the function retries on failure.""" mock_get = mocker.patch("requests.get") mock_get.side_effect = [ ConnectionError("Failed"), ConnectionError("Failed"), mocker.Mock(status_code=200, json=lambda: {"data": "success"}), ] result = fetch_with_retry("/api/data", max_retries=3) assert result == {"data": "success"} assert mock_get.call_count == 3 def test_datetime_mocking(mocker): """Mock the current time for deterministic testing.""" fixed_now = datetime(2024, 6, 15, 12, 0, 0) mocker.patch("myapp.services.datetime") mocker.patch("myapp.services.datetime.now", return_value=fixed_now) result = get_greeting() assert result == "Good afternoon" ``` ## Conftest Patterns ```python # tests/conftest.py -- shared across all tests import pytest from unittest.mock import MagicMock @pytest.fixture def mock_user_repo(): """Create a mock UserRepository.""" repo = MagicMock() repo.find_by_email.return_value = None repo.find_by_id.return_value = None repo.create.side_effect = lambda data: {**data, "id": "generated-id"} return repo @pytest.fixture def mock_email_service(): """Create a mock EmailService.""" return MagicMock() @pytest.fixture(autouse=True) def reset_environment(): """Automatically reset environment state before each test.""" import os original_env = os.environ.copy() yield os.environ.clear() os.environ.update(original_env) ``` ```python # tests/integration/conftest.py -- shared only within integration tests import pytest import httpx @pytest.fixture(scope="session") def api_base_url(): return os.getenv("API_BASE_URL", "http://localhost:3000") @pytest.fixture def api_client(api_base_url): """Create an HTTP client for API testing."""
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub