[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70KA8E5ET5XAJSDC43NS235":3,"skill-same:01M70KA8E5ET5XAJSDC43NS235":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":52,"repositoryHtmlUrl":53,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":56,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M70KA8E5ET5XAJSDC43NS235","python-testing-patterns",null,"Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices.","shengdabai\u002FTony-Claude-Code-Skills","shengdabai","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F116417419?v=4","01M70F6D2WY5PQV5A92BKYJQEW","Tony Sheng","01M70F6D3GAAAYZ5H4CC1G8VXM",3,"MIT","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002F_collections\u002Fwshobson-agents\u002Fplugins\u002Fpython-development\u002Fskills\u002Fpython-testing-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"testing","patterns","python","python testing","python testing patterns","testing patterns","use","testing strategies","tests","mocking","development","pytest","writing python","strategies","when","writing","testing best practices","implementing","up","test-driven","2026-11-10 10:12:30",false,"---\nname: python-testing-patterns\ndescription: Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices.\n---\n\n# Python Testing Patterns\n\nComprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices.\n\n## When to Use This Skill\n\n- Writing unit tests for Python code\n- Setting up test suites and test infrastructure\n- Implementing test-driven development (TDD)\n- Creating integration tests for APIs and services\n- Mocking external dependencies and services\n- Testing async code and concurrent operations\n- Setting up continuous testing in CI\u002FCD\n- Implementing property-based testing\n- Testing database operations\n- Debugging failing tests\n\n## Core Concepts\n\n### 1. Test Types\n- **Unit Tests**: Test individual functions\u002Fclasses in isolation\n- **Integration Tests**: Test interaction between components\n- **Functional Tests**: Test complete features end-to-end\n- **Performance Tests**: Measure speed and resource usage\n\n### 2. Test Structure (AAA Pattern)\n- **Arrange**: Set up test data and preconditions\n- **Act**: Execute the code under test\n- **Assert**: Verify the results\n\n### 3. Test Coverage\n- Measure what code is exercised by tests\n- Identify untested code paths\n- Aim for meaningful coverage, not just high percentages\n\n### 4. Test Isolation\n- Tests should be independent\n- No shared state between tests\n- Each test should clean up after itself\n\n## Quick Start\n\n```python\n# test_example.py\ndef add(a, b):\n    return a + b\n\ndef test_add():\n    \"\"\"Basic test example.\"\"\"\n    result = add(2, 3)\n    assert result == 5\n\ndef test_add_negative():\n    \"\"\"Test with negative numbers.\"\"\"\n    assert add(-1, 1) == 0\n\n# Run with: pytest test_example.py\n```\n\n## Fundamental Patterns\n\n### Pattern 1: Basic pytest Tests\n\n```python\n# test_calculator.py\nimport pytest\n\nclass Calculator:\n    \"\"\"Simple calculator for testing.\"\"\"\n\n    def add(self, a: float, b: float) -> float:\n        return a + b\n\n    def subtract(self, a: float, b: float) -> float:\n        return a - b\n\n    def multiply(self, a: float, b: float) -> float:\n        return a * b\n\n    def divide(self, a: float, b: float) -> float:\n        if b == 0:\n            raise ValueError(\"Cannot divide by zero\")\n        return a \u002F b\n\n\ndef test_addition():\n    \"\"\"Test addition.\"\"\"\n    calc = Calculator()\n    assert calc.add(2, 3) == 5\n    assert calc.add(-1, 1) == 0\n    assert calc.add(0, 0) == 0\n\n\ndef test_subtraction():\n    \"\"\"Test subtraction.\"\"\"\n    calc = Calculator()\n    assert calc.subtract(5, 3) == 2\n    assert calc.subtract(0, 5) == -5\n\n\ndef test_multiplication():\n    \"\"\"Test multiplication.\"\"\"\n    calc = Calculator()\n    assert calc.multiply(3, 4) == 12\n    assert calc.multiply(0, 5) == 0\n\n\ndef test_division():\n    \"\"\"Test division.\"\"\"\n    calc = Calculator()\n    assert calc.divide(6, 3) == 2\n    assert calc.divide(5, 2) == 2.5\n\n\ndef test_division_by_zero():\n    \"\"\"Test division by zero raises error.\"\"\"\n    calc = Calculator()\n    with pytest.raises(ValueError, match=\"Cannot divide by zero\"):\n        calc.divide(5, 0)\n```\n\n### Pattern 2: Fixtures for Setup and Teardown\n\n```python\n# test_database.py\nimport pytest\nfrom typing import Generator\n\nclass Database:\n    \"\"\"Simple database class.\"\"\"\n\n    def __init__(self, connection_string: str):\n        self.connection_string = connection_string\n        self.connected = False\n\n    def connect(self):\n        \"\"\"Connect to database.\"\"\"\n        self.connected = True\n\n    def disconnect(self):\n        \"\"\"Disconnect from database.\"\"\"\n        self.connected = False\n\n    def query(self, sql: str) -> list:\n        \"\"\"Execute query.\"\"\"\n        if not self.connected:\n            raise RuntimeError(\"Not connected\")\n        return [{\"id\": 1, \"name\": \"Test\"}]\n\n\n@pytest.fixture\ndef db() -> Generator[Database, None, None]:\n    \"\"\"Fixture that provides connected database.\"\"\"\n    # Setup\n    database = Database(\"sqlite:\u002F\u002F\u002F:memory:\")\n    database.connect()\n\n    # Provide to test\n    yield database\n\n    # Teardown\n    database.disconnect()\n\n\ndef test_database_query(db):\n    \"\"\"Test database query with fixture.\"\"\"\n    results = db.query(\"SELECT * FROM users\")\n    assert len(results) == 1\n    assert results[0][\"name\"] == \"Test\"\n\n\n@pytest.fixture(scope=\"session\")\ndef app_config():\n    \"\"\"Session-scoped fixture - created once per test session.\"\"\"\n    return {\n        \"database_url\": \"postgresql:\u002F\u002Flocalhost\u002Ftest\",\n        \"api_key\": \"test-key\",\n        \"debug\": True\n    }\n\n\n@pytest.fixture(scope=\"module\")\ndef api_client(app_config):\n    \"\"\"Module-scoped fixture - created once per test module.\"\"\"\n    # Setup expensive resource\n    client = {\"config\": app_config, \"session\": \"active\"}\n    yield client\n    # Cleanup\n    client[\"session\"] = \"closed\"\n\n\ndef test_api_client(api_client):\n    \"\"\"Test using api client fixture.\"\"\"\n    assert api_client[\"session\"] == \"active\"\n    assert api_client[\"config\"][\"debug\"] is True\n```\n\n### Pattern 3: Parameterized Tests\n\n```python\n# test_validation.py\nimport pytest\n\ndef is_valid_email(email: str) -> bool:\n    \"\"\"Check if email is valid.\"\"\"\n    return \"@\" in email and \".\" in email.split(\"@\")[1]\n\n\n@pytest.mark.parametrize(\"email,expected\", [\n    (\"user@example.com\", True),\n    (\"test.user@domain.co.uk\", True),\n    (\"invalid.email\", False),\n    (\"@example.com\", False),\n    (\"user@domain\", False),\n    (\"\", False),\n])\ndef test_email_validation(email, expected):\n    \"\"\"Test email validation with various inputs.\"\"\"\n    assert is_valid_email(email) == expected\n\n\n@pytest.mark.parametrize(\"a,b,expected\", [\n    (2, 3, 5),\n    (0, 0, 0),\n    (-1, 1, 0),\n    (100, 200, 300),\n    (-5, -5, -10),\n])\ndef test_addition_parameterized(a, b, expected):\n    \"\"\"Test addition with multiple parameter sets.\"\"\"\n    from test_calculator import Calculator\n    calc = Calculator()\n    assert calc.add(a, b) == expected\n\n\n# Using pytest.param for special cases\n@pytest.mark.parametrize(\"value,expected\", [\n    pytest.param(1, True, id=\"positive\"),\n    pytest.param(0, False, id=\"zero\"),\n    pytest.param(-1, False, id=\"negative\"),\n])\ndef test_is_positive(value, expected):\n    \"\"\"Test with custom test IDs.\"\"\"\n    assert (value > 0) == expected\n```\n\n### Pattern 4: Mocking with unittest.mock\n\n```python\n# test_api_client.py\nimport pytest\nfrom unittest.mock import Mock, patch, MagicMock\nimport requests\n\nclass APIClient:\n    \"\"\"Simple API client.\"\"\"\n\n    def __init__(self, base_url: str):\n        self.base_url = base_url\n\n    def get_user(self, user_id: int) -> dict:\n        \"\"\"Fetch user from API.\"\"\"\n        response = requests.get(f\"{self.base_url}\u002Fusers\u002F{user_id}\")\n        response.raise_for_status()\n        return response.json()\n\n    def create_user(self, data: dict) -> dict:\n        \"\"\"Create new user.\"\"\"\n        response = requests.post(f\"{self.base_url}\u002Fusers\", json=data)\n        response.raise_for_status()\n        return response.json()\n\n\ndef test_get_user_success():\n    \"\"\"Test successful API call with mock.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_response = Mock()\n    mock_response.json.return_value = {\"id\": 1, \"name\": \"John Doe\"}\n    mock_response.raise_for_status.return_value = None\n\n    with patch(\"requests.get\", return_value=mock_response) as mock_get:\n        user = client.get_user(1)\n\n        assert user[\"id\"] == 1\n        assert user[\"name\"] == \"John Doe\"\n        mock_get.assert_called_once_with(\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F1\")\n\n\ndef test_get_user_not_found():\n    \"\"\"Test API call with 404 error.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_response = Mock()\n    mock_response.raise_for_status.side_effect = requests.HTTPError(\"404 Not Found\")\n\n    with patch(\"requests.get\", return_value=mock_response):\n        with pytest.raises(requests.HTTPError):\n            client.get_user(999)\n\n\n@patch(\"requests.post\")\ndef test_create_user(mock_post):\n    \"\"\"Test user creation with decorator syntax.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_post.return_value.json.return_value = {\"id\": 2, \"name\": \"Jane Doe\"}\n    mock_post.return_value.raise_for_status.return_value = None\n\n    user_data = {\"name\": \"Jane Doe\", \"email\": \"jane@example.com\"}\n    result = client.create_user(user_data)\n\n    assert result[\"id\"] == 2\n    mock_post.assert_called_once()\n    call_args = mock_post.call_args\n    assert call_args.kwargs[\"json\"] == user_data\n```\n\n### Pattern 5: Testing Exceptions\n\n```python\n# test_exceptions.py\nimport pytest\n\ndef divide(a: float, b: float) -> float:\n    \"\"\"Divide a by b.\"\"\"\n    if b == 0:\n        raise ZeroDivisionError(\"Division by zero\")\n    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):\n        raise TypeError(\"Arguments must be numbers\")\n    return a \u002F b\n\n\ndef test_zero_division():\n    \"\"\"Test exception is raised for division by zero.\"\"\"\n    with pytest.raises(ZeroDivisionError):\n        divide(10, 0)\n\n\ndef test_zero_division_with_message():\n    \"\"\"Test exception message.\"\"\"\n    with pytest.raises(ZeroDivisionError, match=\"Division by zero\"):\n        divide(5, 0)\n\n\ndef test_type_error():\n    \"\"\"Test type error exception.\"\"\"\n    with pytest.raises(TypeError, match=\"must be numbers\"):\n        divide(\"10\", 5)\n\n\ndef test_exception_info():\n    \"\"\"Test accessing exception info.\"\"\"\n    with pytest.raises(ValueError) as exc_info:\n        int(\"not a number\")\n\n    assert \"invalid literal\" in str(exc_info.value)\n```\n\n## Advanced Patterns\n\n### Pattern 6: Testing Async Code\n\n```python\n# test_async.py\nimport pytest\nimport asyncio\n\nasync def fetch_data(url: str) -> dict:\n    \"\"\"Fetch data asynchronously.\"\"\"\n    await asyncio.sleep(0.1)\n    return {\"url\": url, \"data\": \"result\"}\n\n\n@pytest.mark.asyncio\nasync def test_fetch_data():\n    \"\"\"Test async function.\"\"\"\n    result = await fetch_data(\"https:\u002F\u002Fapi.example.com\")\n    assert result[\"url\"] == \"https:\u002F\u002Fapi.example.com\"\n    assert \"data\" in result\n\n\n@pytest.mark.asyncio\nasync def test_concurrent_fetches():\n    \"\"\"Test concurrent async operations.\"\"\"\n    urls = [\"url1\", \"url2\", \"url3\"]\n    tasks = [fetch_data(url) for url in urls]\n    results = await asyncio.gather(*tasks)\n\n    assert len(results) == 3\n    assert all(\"data\" in r for r in results)\n\n\n@pytest.fixture\nasync def async_client():\n    \"\"\"Async fixture.\"\"\"\n    client = {\"connected\": True}\n    yield client\n    client[\"connected\"] = False\n\n\n@pytest.mark.asyncio\nasync def test_with_async_fixture(async_client):\n    \"\"\"Test using async fixture.\"\"\"\n    assert async_client[\"connected\"] is True\n```\n\n### Pattern 7: Monkeypatch for Testing\n\n```python\n# test_environment.py\nimport os\nimport pytest\n\ndef get_database_url() -> str:\n    \"\"\"Get database URL from environment.\"\"\"\n    return os.environ.get(\"DATABASE_URL\", \"sqlite:\u002F\u002F\u002F:memory:\")\n\n\ndef test_database_url_default():\n    \"\"\"Test default database URL.\"\"\"\n    # Will use actual environment variable if set\n    url = get_database_url()\n    assert url\n\n\ndef test_database_url_custom(monkeypatch):\n    \"\"\"Test custom database URL with monkeypatch.\"\"\"\n    monkeypatch.setenv(\"DATABASE_URL\", \"postgresql:\u002F\u002Flocalhost\u002Ftest\")\n    assert get_database_url() == \"postgresql:\u002F\u002Flocalhost\u002Ftest\"\n\n\ndef test_database_url_not_set(monkeypatch):\n    \"\"\"Test when env var is not set.\"\"\"\n    monkeypatch.delenv(\"DATABASE_URL\", raising=False)\n    assert get_database_url() == \"sqlite:\u002F\u002F\u002F:memory:\"\n\n\nclass Config:\n    \"\"\"Configuration class.\"\"\"\n\n    def __init__(self):\n        self.api_key = \"production-key\"\n\n    def get_api_key(self):\n        return self.api_key\n\n\ndef test_monkeypatch_attribute(monkeypatch):\n    \"\"\"Test monkeypatching object attributes.\"\"\"\n    config = Config()\n    monkeypatch.setattr(config, \"api_key\", \"test-key\")\n    assert config.get_api_key() == \"test-key\"\n```\n\n### Pattern 8: Temporary Files and Directories\n\n```python\n# test_file_operations.py\nimport pytest\nfrom pathlib import Path\n\ndef save_data(filepath: Path, data: str):\n    \"\"\"Save data to file.\"\"\"\n    filepath.write_text(data)\n\n\ndef load_data(filepath: Path) -> str:\n    \"\"\"Load data from file.\"\"\"\n    return filepath.read_text()\n\n\ndef test_file_operations(tmp_path):\n    \"\"\"Test file operations with temporary directory.\"\"\"\n    # tmp_path is a pathlib.Path object\n    test_file = tmp_path \u002F \"test_data.txt\"\n\n    # Save data\n    save_data(test_file, \"Hello, World!\")\n\n    # Verify file exists\n    assert test_file.exists()\n\n    # Load and verify data\n    data = load_data(test_file)\n    assert data == \"Hello, World!\"\n\n\ndef test_multiple_files(tmp_path):\n    \"\"\"Test with multiple temporary files.\"\"\"\n    files = {\n        \"file1.txt\": \"Content 1\",\n        \"file2.txt\": \"Content 2\",\n        \"file3.txt\": \"Content 3\"\n    }\n\n    for filename, content in files.items():\n        filepath = tmp_path \u002F filename\n        save_data(filepath, content)\n\n    # Verify all files created\n    assert len(list(tmp_path.iterdir())) == 3\n\n    # Verify contents\n    for filename, expected_content in files.items():\n        filepath = tmp_path \u002F filename\n        assert load_data(filepath) == expected_content\n```\n\n### Pattern 9: Custom Fixtures and Conftest\n\n```python\n# conftest.py\n\"\"\"Shared fixtures for all tests.\"\"\"\nimport pytest\n\n@pytest.fixture(scope=\"session\")\ndef database_url():\n    \"\"\"Provide database URL for all tests.\"\"\"\n    return \"postgresql:\u002F\u002Flocalhost\u002Ftest_db\"\n\n\n@pytest.fixture(autouse=True)\ndef reset_database(database_url):\n    \"\"\"Auto-use fixture that runs before each test.\"\"\"\n    # Setup: Clear database\n    print(f\"Clearing database: {database_url}\")\n    yield\n    # Teardown: Clean up\n    print(\"Test completed\")\n\n\n@pytest.fixture\ndef sample_user():\n    \"\"\"Provide sample user data.\"\"\"\n    return {\n        \"id\": 1,\n        \"name\": \"Test User\",\n        \"email\": \"test@example.com\"\n    }\n\n\n@pytest.fixture\ndef sample_users():\n    \"\"\"Provide list of sample users.\"\"\"\n    return [\n        {\"id\": 1, \"name\": \"User 1\"},\n        {\"id\": 2, \"name\": \"User 2\"},\n        {\"id\": 3, \"name\": \"User 3\"},\n    ]\n\n\n# Parametrized fixture\n@pytest.fixture(params=[\"sqlite\", \"postgresql\", \"mysql\"])\ndef db_backend(request):\n    \"\"\"Fixture that runs tests with different database backends.\"\"\"\n    return request.param\n\n\ndef test_with_db_backend(db_backend):\n    \"\"\"This test will run 3 times with different backends.\"\"\"\n    print(f\"Testing with {db_backend}\")\n    assert db_backend in [\"sqlite\", \"postgresql\", \"mysql\"]\n```\n\n### Pattern 10: Property-Based Testing\n\n```python\n# test_properties.py\nfrom hypothesis import given, strategies as st\nimport pytest\n\ndef reverse_string(s: str) -> str:\n    \"\"\"Reverse a string.\"\"\"\n    return s[::-1]\n\n\n@given(st.text())\ndef test_reverse_twice_is_original(s):\n    \"\"\"Property: reversing twice returns original.\"\"\"\n    assert reverse_string(reverse_string(s)) == s\n\n\n@given(st.text())\ndef test_reverse_length(s):\n    \"\"\"Property: reversed string has same length.\"\"\"\n    assert len(reverse_string(s)) == len(s)\n\n\n@given(st.integers(), st.integers())\ndef test_addition_commutative(a, b):\n    \"\"\"Property: addition is commutative.\"\"\"\n    assert a + b == b + a\n\n\n@given(st.lists(st.integers()))\ndef test_sorted_list_properties(lst):\n    \"\"\"Property: sorted list is ordered.\"\"\"\n    sorted_lst = sorted(lst)\n\n    # Same length\n    assert len(sorted_lst) == len(lst)\n\n    # All elements present\n    assert set(sorted_lst) == set(lst)\n\n    # Is ordered\n    for i in range(len(sorted_lst) - 1):\n        assert sorted_lst[i] \u003C= sorted_lst[i + 1]\n```\n\n## Testing Best Practices\n\n### Test Organization\n\n```python\n# tests\u002F\n#   __init__.py\n#   conftest.py           # Shared fixtures\n#   test_unit\u002F            # Unit tests\n#     test_models.py\n#     test_utils.py\n#   test_integration\u002F     # Integration tests\n#     test_api.py\n#     test_database.py\n#   test_e2e\u002F            # End-to-end tests\n#     test_workflows.py\n```\n\n### Test Naming\n\n```python\n# Good test names\ndef test_user_creation_with_valid_data():\n    \"\"\"Clear name describes what is being tested.\"\"\"\n    pass\n\n\ndef test_login_fails_with_invalid_password():\n    \"\"\"Name describes expected behavior.\"\"\"\n    pass\n\n\ndef test_api_returns_404_for_missing_resource():\n    \"\"\"Specific about inputs and expected outcomes.\"\"\"\n    pass\n\n\n# Bad test names\ndef test_1():  # Not descriptive\n    pass\n\n\ndef test_user():  # Too vague\n    pass\n\n\ndef test_function():  # Doesn't explain what's tested\n    pass\n```\n\n### Test Markers\n\n```python\n# test_markers.py\nimport pytest\n\n@pytest.mark.slow\ndef test_slow_operation():\n    \"\"\"Mark slow tests.\"\"\"\n    import time\n    time.sleep(2)\n\n\n@pytest.mark.integration\ndef test_database_integration():\n    \"\"\"Mark integration tests.\"\"\"\n    pass\n\n\n@pytest.mark.skip(reason=\"Feature not implemented yet\")\ndef test_future_feature():\n    \"\"\"Skip tests temporarily.\"\"\"\n    pass\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Unix only test\")\ndef test_unix_specific():\n    \"\"\"Conditional skip.\"\"\"\n    pass\n\n\n@pytest.mark.xfail(reason=\"Known bug #123\")\ndef test_known_bug():\n    \"\"\"Mark expected failures.\"\"\"\n    assert False\n\n\n# Run with:\n# pytest -m slow          # Run only slow tests\n# pytest -m \"not slow\"    # Skip slow tests\n# pytest -m integration   # Run integration tests\n```\n\n### Coverage Reporting\n\n```bash\n# Install coverage\npip install pytest-cov\n\n# Run tests with coverage\npytest --cov=myapp tests\u002F\n\n# Generate HTML report\npytest --cov=myapp --cov-report=html tests\u002F\n\n# Fail if coverage below threshold\npytest --cov=myapp --cov-fail-under=80 tests\u002F\n\n# Show missing lines\npytest --cov=myapp --cov-report=term-missing tests\u002F\n```\n\n## Testing Database Code\n\n```python\n# test_database_models.py\nimport pytest\nfrom sqlalchemy import create_engine, Column, Integer, String\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm import sessionmaker, Session\n\nBase = declarative_base()\n\n\nclass User(Base):\n    \"\"\"User model.\"\"\"\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True)\n    name = Column(String(50))\n    email = Column(String(100), unique=True)\n\n\n@pytest.fixture(scope=\"function\")\ndef db_session() -> Session:\n    \"\"\"Create in-memory database for testing.\"\"\"\n    engine = create_engine(\"sqlite:\u002F\u002F\u002F:memory:\")\n    Base.metadata.create_all(engine)\n\n    SessionLocal = sessionmaker(bind=engine)\n    session = SessionLocal()\n\n    yield session\n\n    session.close()\n\n\ndef test_create_user(db_session):\n    \"\"\"Test creating a user.\"\"\"\n    user = User(name=\"Test User\", email=\"test@example.com\")\n    db_session.add(user)\n    db_session.commit()\n\n    assert user.id is not None\n    assert user.name == \"Test User\"\n\n\ndef test_query_user(db_session):\n    \"\"\"Test querying users.\"\"\"\n    user1 = User(name=\"User 1\", email=\"user1@example.com\")\n    user2 = User(name=\"User 2\", email=\"user2@example.com\")\n\n    db_session.add_all([user1, user2])\n    db_session.commit()\n\n    users = db_session.query(User).all()\n    assert len(users) == 2\n\n\ndef test_unique_email_constraint(db_session):\n    \"\"\"Test unique email constraint.\"\"\"\n    from sqlalchemy.exc import IntegrityError\n\n    user1 = User(name=\"User 1\", email=\"same@example.com\")\n    user2 = User(name=\"User 2\", email=\"same@example.com\")\n\n    db_session.add(user1)\n    db_session.commit()\n\n    db_session.add(user2)\n\n    with pytest.raises(IntegrityError):\n        db_session.commit()\n```\n\n## CI\u002FCD Integration\n\n```yaml\n# .github\u002Fworkflows\u002Ftest.yml\nname: Tests\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n\n    steps:\n      - uses: actions\u002Fcheckout@v3\n\n      - name: Set up Python\n        uses: actions\u002Fsetup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          pip install -e \".[dev]\"\n          pip install pytest pytest-cov\n\n      - name: Run tests\n        run: |\n          pytest --cov=myapp --cov-report=xml\n\n      - name: Upload coverage\n        uses: codecov\u002Fcodecov-action@v3\n        with:\n          file: .\u002Fcoverage.xml\n```\n\n## Configuration Files\n\n```ini\n# pytest.ini\n[pytest]\ntestpaths = tests\npython_files = test_*.py\npython_classes = Test*\npython_functions = test_*\naddopts =\n    -v\n    --strict-markers\n    --tb=short\n    --cov=myapp\n    --cov-report=term-missing\nmarkers =\n    slow: marks tests as slow\n    integration: marks integration tests\n    unit: marks unit tests\n    e2e: marks end-to-end tests\n```\n\n```toml\n# pyproject.toml\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\naddopts = [\n    \"-v\",\n    \"--cov=myapp\",\n    \"--cov-report=term-missing\",\n]\n\n[tool.coverage.run]\nsource = [\"myapp\"]\nomit = [\"*\u002Ftests\u002F*\", \"*\u002Fmigrations\u002F*\"]\n\n[tool.coverage.report]\nexclude_lines = [\n    \"pragma: no cover\",\n    \"def __repr__\",\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n]\n```\n\n## Resources\n\n- **pytest documentation**: https:\u002F\u002Fdocs.pytest.org\u002F\n- **unittest.mock**: https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Funittest.mock.html\n- **hypothesis**: Property-based testing\n- **pytest-asyncio**: Testing async code\n- **pytest-cov**: Coverage reporting\n- **pytest-mock**: pytest wrapper for mock\n\n## Best Practices Summary\n\n1. **Write tests first** (TDD) or alongside code\n2. **One assertion per test** when possible\n3. **Use descriptive test names** that explain behavior\n4. **Keep tests independent** and isolated\n5. **Use fixtures** for setup and teardown\n6. **Mock external dependencies** appropriately\n7. **Parametrize tests** to reduce duplication\n8. **Test edge cases** and error conditions\n9. **Measure coverage** but focus on quality\n10. **Run tests in CI\u002FCD** on every commit\n","# Python Testing Patterns\n\nComprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices.\n\n## When to Use This Skill\n\n- Writing unit tests for Python code\n- Setting up test suites and test infrastructure\n- Implementing test-driven development (TDD)\n- Creating integration tests for APIs and services\n- Mocking external dependencies and services\n- Testing async code and concurrent operations\n- Setting up continuous testing in CI\u002FCD\n- Implementing property-based testing\n- Testing database operations\n- Debugging failing tests\n\n## Core Concepts\n\n### 1. Test Types\n- **Unit Tests**: Test individual functions\u002Fclasses in isolation\n- **Integration Tests**: Test interaction between components\n- **Functional Tests**: Test complete features end-to-end\n- **Performance Tests**: Measure speed and resource usage\n\n### 2. Test Structure (AAA Pattern)\n- **Arrange**: Set up test data and preconditions\n- **Act**: Execute the code under test\n- **Assert**: Verify the results\n\n### 3. Test Coverage\n- Measure what code is exercised by tests\n- Identify untested code paths\n- Aim for meaningful coverage, not just high percentages\n\n### 4. Test Isolation\n- Tests should be independent\n- No shared state between tests\n- Each test should clean up after itself\n\n## Quick Start\n\n```python\n# test_example.py\ndef add(a, b):\n    return a + b\n\ndef test_add():\n    \"\"\"Basic test example.\"\"\"\n    result = add(2, 3)\n    assert result == 5\n\ndef test_add_negative():\n    \"\"\"Test with negative numbers.\"\"\"\n    assert add(-1, 1) == 0\n\n# Run with: pytest test_example.py\n```\n\n## Fundamental Patterns\n\n### Pattern 1: Basic pytest Tests\n\n```python\n# test_calculator.py\nimport pytest\n\nclass Calculator:\n    \"\"\"Simple calculator for testing.\"\"\"\n\n    def add(self, a: float, b: float) -> float:\n        return a + b\n\n    def subtract(self, a: float, b: float) -> float:\n        return a - b\n\n    def multiply(self, a: float, b: float) -> float:\n        return a * b\n\n    def divide(self, a: float, b: float) -> float:\n        if b == 0:\n            raise ValueError(\"Cannot divide by zero\")\n        return a \u002F b\n\n\ndef test_addition():\n    \"\"\"Test addition.\"\"\"\n    calc = Calculator()\n    assert calc.add(2, 3) == 5\n    assert calc.add(-1, 1) == 0\n    assert calc.add(0, 0) == 0\n\n\ndef test_subtraction():\n    \"\"\"Test subtraction.\"\"\"\n    calc = Calculator()\n    assert calc.subtract(5, 3) == 2\n    assert calc.subtract(0, 5) == -5\n\n\ndef test_multiplication():\n    \"\"\"Test multiplication.\"\"\"\n    calc = Calculator()\n    assert calc.multiply(3, 4) == 12\n    assert calc.multiply(0, 5) == 0\n\n\ndef test_division():\n    \"\"\"Test division.\"\"\"\n    calc = Calculator()\n    assert calc.divide(6, 3) == 2\n    assert calc.divide(5, 2) == 2.5\n\n\ndef test_division_by_zero():\n    \"\"\"Test division by zero raises error.\"\"\"\n    calc = Calculator()\n    with pytest.raises(ValueError, match=\"Cannot divide by zero\"):\n        calc.divide(5, 0)\n```\n\n### Pattern 2: Fixtures for Setup and Teardown\n\n```python\n# test_database.py\nimport pytest\nfrom typing import Generator\n\nclass Database:\n    \"\"\"Simple database class.\"\"\"\n\n    def __init__(self, connection_string: str):\n        self.connection_string = connection_string\n        self.connected = False\n\n    def connect(self):\n        \"\"\"Connect to database.\"\"\"\n        self.connected = True\n\n    def disconnect(self):\n        \"\"\"Disconnect from database.\"\"\"\n        self.connected = False\n\n    def query(self, sql: str) -> list:\n        \"\"\"Execute query.\"\"\"\n        if not self.connected:\n            raise RuntimeError(\"Not connected\")\n        return [{\"id\": 1, \"name\": \"Test\"}]\n\n\n@pytest.fixture\ndef db() -> Generator[Database, None, None]:\n    \"\"\"Fixture that provides connected database.\"\"\"\n    # Setup\n    database = Database(\"sqlite:\u002F\u002F\u002F:memory:\")\n    database.connect()\n\n    # Provide to test\n    yield database\n\n    # Teardown\n    database.disconnect()\n\n\ndef test_database_query(db):\n    \"\"\"Test database query with fixture.\"\"\"\n    results = db.query(\"SELECT * FROM users\")\n    assert len(results) == 1\n    assert results[0][\"name\"] == \"Test\"\n\n\n@pytest.fixture(scope=\"session\")\ndef app_config():\n    \"\"\"Session-scoped fixture - created once per test session.\"\"\"\n    return {\n        \"database_url\": \"postgresql:\u002F\u002Flocalhost\u002Ftest\",\n        \"api_key\": \"test-key\",\n        \"debug\": True\n    }\n\n\n@pytest.fixture(scope=\"module\")\ndef api_client(app_config):\n    \"\"\"Module-scoped fixture - created once per test module.\"\"\"\n    # Setup expensive resource\n    client = {\"config\": app_config, \"session\": \"active\"}\n    yield client\n    # Cleanup\n    client[\"session\"] = \"closed\"\n\n\ndef test_api_client(api_client):\n    \"\"\"Test using api client fixture.\"\"\"\n    assert api_client[\"session\"] == \"active\"\n    assert api_client[\"config\"][\"debug\"] is True\n```\n\n### Pattern 3: Parameterized Tests\n\n```python\n# test_validation.py\nimport pytest\n\ndef is_valid_email(email: str) -> bool:\n    \"\"\"Check if email is valid.\"\"\"\n    return \"@\" in email and \".\" in email.split(\"@\")[1]\n\n\n@pytest.mark.parametrize(\"email,expected\", [\n    (\"user@example.com\", True),\n    (\"test.user@domain.co.uk\", True),\n    (\"invalid.email\", False),\n    (\"@example.com\", False),\n    (\"user@domain\", False),\n    (\"\", False),\n])\ndef test_email_validation(email, expected):\n    \"\"\"Test email validation with various inputs.\"\"\"\n    assert is_valid_email(email) == expected\n\n\n@pytest.mark.parametrize(\"a,b,expected\", [\n    (2, 3, 5),\n    (0, 0, 0),\n    (-1, 1, 0),\n    (100, 200, 300),\n    (-5, -5, -10),\n])\ndef test_addition_parameterized(a, b, expected):\n    \"\"\"Test addition with multiple parameter sets.\"\"\"\n    from test_calculator import Calculator\n    calc = Calculator()\n    assert calc.add(a, b) == expected\n\n\n# Using pytest.param for special cases\n@pytest.mark.parametrize(\"value,expected\", [\n    pytest.param(1, True, id=\"positive\"),\n    pytest.param(0, False, id=\"zero\"),\n    pytest.param(-1, False, id=\"negative\"),\n])\ndef test_is_positive(value, expected):\n    \"\"\"Test with custom test IDs.\"\"\"\n    assert (value > 0) == expected\n```\n\n### Pattern 4: Mocking with unittest.mock\n\n```python\n# test_api_client.py\nimport pytest\nfrom unittest.mock import Mock, patch, MagicMock\nimport requests\n\nclass APIClient:\n    \"\"\"Simple API client.\"\"\"\n\n    def __init__(self, base_url: str):\n        self.base_url = base_url\n\n    def get_user(self, user_id: int) -> dict:\n        \"\"\"Fetch user from API.\"\"\"\n        response = requests.get(f\"{self.base_url}\u002Fusers\u002F{user_id}\")\n        response.raise_for_status()\n        return response.json()\n\n    def create_user(self, data: dict) -> dict:\n        \"\"\"Create new user.\"\"\"\n        response = requests.post(f\"{self.base_url}\u002Fusers\", json=data)\n        response.raise_for_status()\n        return response.json()\n\n\ndef test_get_user_success():\n    \"\"\"Test successful API call with mock.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_response = Mock()\n    mock_response.json.return_value = {\"id\": 1, \"name\": \"John Doe\"}\n    mock_response.raise_for_status.return_value = None\n\n    with patch(\"requests.get\", return_value=mock_response) as mock_get:\n        user = client.get_user(1)\n\n        assert user[\"id\"] == 1\n        assert user[\"name\"] == \"John Doe\"\n        mock_get.assert_called_once_with(\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F1\")\n\n\ndef test_get_user_not_found():\n    \"\"\"Test API call with 404 error.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_response = Mock()\n    mock_response.raise_for_status.side_effect = requests.HTTPError(\"404 Not Found\")\n\n    with patch(\"requests.get\", return_value=mock_response):\n        with pytest.raises(requests.HTTPError):\n            client.get_user(999)\n\n\n@patch(\"requests.post\")\ndef test_create_user(mock_post):\n    \"\"\"Test user creation with decorator syntax.\"\"\"\n    client = APIClient(\"https:\u002F\u002Fapi.example.com\")\n\n    mock_post.return_value.json.return_value = {\"id\": 2, \"name\": \"Jane Doe\"}\n    mock_post.return_value.raise_for_status.return_value = None\n\n    user_data = {\"name\": \"Jane Doe\", \"email\": \"jane@example.com\"}\n    result = client.create_user(user_data)\n\n    assert result[\"id\"] == 2\n    mock_post.assert_called_once()\n    call_args = mock_post.call_args\n    assert call_args.kwargs[\"json\"] == user_data\n```\n\n### Pattern 5: Testing Exceptions\n\n```python\n# test_exceptions.py\nimport pytest\n\ndef divide(a: float, b: float) -> float:\n    \"\"\"Divide a by b.\"\"\"\n    if b == 0:\n        raise ZeroDivisionError(\"Division by zero\")\n    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):\n        raise TypeError(\"Arguments must be numbers\")\n    return a \u002F b\n\n\ndef test_zero_division():\n    \"\"\"Test exception is raised for division by zero.\"\"\"\n    with pytest.raises(ZeroDivisionError):\n        divide(10, 0)\n\n\ndef test_zero_division_with_message():\n    \"\"\"Test exception message.\"\"\"\n    with pytest.raises(ZeroDivisionError, match=\"Division by zero\"):\n        divide(5, 0)\n\n\ndef test_type_error():\n    \"\"\"Test type error exception.\"\"\"\n    with pytest.raises(TypeError, match=\"must be numbers\"):\n        divide(\"10\", 5)\n\n\ndef test_exception_info():\n    \"\"\"Test accessing exception info.\"\"\"\n    with pytest.raises(ValueError) as exc_info:\n        int(\"not a number\")\n\n    assert \"invalid literal\" in str(exc_info.value)\n```\n\n## Advanced Patterns\n\n### Pattern 6: Testing Async Code\n\n```python\n# test_async.py\nimport pytest\nimport asyncio\n\nasync def fetch_data(url: str) -> dict:\n    \"\"\"Fetch data asynchronously.\"\"\"\n    await asyncio.sleep(0.1)\n    return {\"url\": url, \"data\": \"result\"}\n\n\n@pytest.mark.asyncio\nasync def test_fetch_data():\n    \"\"\"Test async function.\"\"\"\n    result = await fetch_data(\"https:\u002F\u002Fapi.example.com\")\n    assert result[\"url\"] == \"https:\u002F\u002Fapi.example.com\"\n    assert \"data\" in result\n\n\n@pytest.mark.asyncio\nasync def test_concurrent_fetches():\n    \"\"\"Test concurrent async operations.\"\"\"\n    urls = [\"url1\", \"url2\", \"url3\"]\n    tasks = [fetch_data(url) for url in urls]\n    results = await asyncio.gather(*tasks)\n\n    assert len(results) == 3\n    assert all(\"data\" in r for r in results)\n\n\n@pytest.fixture\nasync def async_client():\n    \"\"\"Async fixture.\"\"\"\n    client = {\"connected\": True}\n    yield client\n    client[\"connected\"] = False\n\n\n@pytest.mark.asyncio\nasync def test_with_async_fixture(async_client):\n    \"\"\"Test using async fixture.\"\"\"\n    assert async_client[\"connected\"] is True\n```\n\n### Pattern 7: Monkeypatch for Testing\n\n```python\n# test_environment.py\nimport os\nimport pytest\n\ndef get_database_url() -> str:\n    \"\"\"Get database URL from environment.\"\"\"\n    return os.environ.get(\"DATABASE_URL\", \"sqlite:\u002F\u002F\u002F:memory:\")\n\n\ndef test_database_url_default():\n    \"\"\"Test default database URL.\"\"\"\n    # Will use actual environment variable if set\n    url = get_database_url()\n    assert url\n\n\ndef test_database_url_custom(monkeypatch):\n    \"\"\"Test custom database URL with monkeypatch.\"\"\"\n    monkeypatch.setenv(\"DATABASE_URL\", \"postgresql:\u002F\u002Flocalhost\u002Ftest\")\n    assert get_database_url() == \"postgresql:\u002F\u002Flocalhost\u002Ftest\"\n\n\ndef test_database_url_not_set(monkeypatch):\n    \"\"\"Test when env var is not set.\"\"\"\n    monkeypatch.delenv(\"DATABASE_URL\", raising=False)\n    assert get_database_url() == \"sqlite:\u002F\u002F\u002F:memory:\"\n\n\nclass Config:\n    \"\"\"Configuration class.\"\"\"\n\n    def __init__(self):\n        self.api_key = \"production-key\"\n\n    def get_api_key(self):\n        return self.api_key\n\n\ndef test_monkeypatch_attribute(monkeypatch):\n    \"\"\"Test monkeypatching object attributes.\"\"\"\n    config = Config()\n    monkeypatch.setattr(config, \"api_key\", \"test-key\")\n    assert config.get_api_key() == \"test-key\"\n```\n\n### Pattern 8: Temporary Files and Directories\n\n```python\n# test_file_operations.py\nimport pytest\nfrom pathlib import Path\n\ndef save_data(filepath: Path, data: str):\n    \"\"\"Save data to file.\"\"\"\n    filepath.write_text(data)\n\n\ndef load_data(filepath: Path) -> str:\n    \"\"\"Load data from file.\"\"\"\n    return filepath.read_text()\n\n\ndef test_file_operations(tmp_path):\n    \"\"\"Test file operations with temporary directory.\"\"\"\n    # tmp_path is a pathlib.Path object\n    test_file = tmp_path \u002F \"test_data.txt\"\n\n    # Save data\n    save_data(test_file, \"Hello, World!\")\n\n    # Verify file exists\n    assert test_file.exists()\n\n    # Load and verify data\n    data = load_data(test_file)\n    assert data == \"Hello, World!\"\n\n\ndef test_multiple_files(tmp_path):\n    \"\"\"Test with multiple temporary files.\"\"\"\n    files = {\n        \"file1.txt\": \"Content 1\",\n        \"file2.txt\": \"Content 2\",\n        \"file3.txt\": \"Content 3\"\n    }\n\n    for filename, content in files.items():\n        filepath = tmp_path \u002F filename\n        save_data(filepath, content)\n\n    # Verify all files created\n    assert len(list(tmp_path.iterdir())) == 3\n\n    # Verify contents\n    for filename, expected_content in files.items():\n        filepath = tmp_path \u002F filename\n        assert load_data(filepath) == expected_content\n```\n\n### Pattern 9: Custom Fixtures and Conftest\n\n```python\n# conftest.py\n\"\"\"Shared fixtures for all tests.\"\"\"\nimport pytest\n\n@pytest.fixture(scope=\"session\")\ndef database_url():\n    \"\"\"Provide database URL for all tests.\"\"\"\n    return \"postgresql:\u002F\u002Flocalhost\u002Ftest_db\"\n\n\n@pytest.fixture(autouse=True)\ndef reset_database(database_url):\n    \"\"\"Auto-use fixture that runs before each test.\"\"\"\n    # Setup: Clear database\n    print(f\"Clearing database: {database_url}\")\n    yield\n    # Teardown: Clean up\n    print(\"Test completed\")\n\n\n@pytest.fixture\ndef sample_user():\n    \"\"\"Provide sample user data.\"\"\"\n    return {\n        \"id\": 1,\n        \"name\": \"Test User\",\n        \"email\": \"test@example.com\"\n    }\n\n\n@pytest.fixture\ndef sample_users():\n    \"\"\"Provide list of sample users.\"\"\"\n    return [\n        {\"id\": 1, \"name\": \"User 1\"},\n        {\"id\": 2, \"name\": \"User 2\"},\n        {\"id\": 3, \"name\": \"User 3\"},\n    ]\n\n\n# Parametrized fixture\n@pytest.fixture(params=[\"sqlite\", \"postgresql\", \"mysql\"])\ndef db_backend(request):\n    \"\"\"Fixture that runs tests with different database backends.\"\"\"\n    return request.param\n\n\ndef test_with_db_backend(db_backend):\n    \"\"\"This test will run 3 times with different backends.\"\"\"\n    print(f\"Testing with {db_backend}\")\n    assert db_backend in [\"sqlite\", \"postgresql\", \"mysql\"]\n```\n\n### Pattern 10: Property-Based Testing\n\n```python\n# test_properties.py\nfrom hypothesis import given, strategies as st\nimport pytest\n\ndef reverse_string(s: str) -> str:\n    \"\"\"Reverse a string.\"\"\"\n    return s[::-1]\n\n\n@given(st.text())\ndef test_reverse_twice_is_original(s):\n    \"\"\"Property: reversing twice returns original.\"\"\"\n    assert reverse_string(reverse_string(s)) == s\n\n\n@given(st.text())\ndef test_reverse_length(s):\n    \"\"\"Property: reversed string has same length.\"\"\"\n    assert len(reverse_string(s)) == len(s)\n\n\n@given(st.integers(), st.integers())\ndef test_addition_commutative(a, b):\n    \"\"\"Property: addition is commutative.\"\"\"\n    assert a + b == b + a\n\n\n@given(st.lists(st.integers()))\ndef test_sorted_list_properties(lst):\n    \"\"\"Property: sorted list is ordered.\"\"\"\n    sorted_lst = sorted(lst)\n\n    # Same length\n    assert len(sorted_lst) == len(lst)\n\n    # All elements present\n    assert set(sorted_lst) == set(lst)\n\n    # Is ordered\n    for i in range(len(sorted_lst) - 1):\n        assert sorted_lst[i] \u003C= sorted_lst[i + 1]\n```\n\n## Testing Best Practices\n\n### Test Organization\n\n```python\n# tests\u002F\n#   __init__.py\n#   conftest.py           # Shared fixtures\n#   test_unit\u002F            # Unit tests\n#     test_models.py\n#     test_utils.py\n#   test_integration\u002F     # Integration tests\n#     test_api.py\n#     test_database.py\n#   test_e2e\u002F            # End-to-end tests\n#     test_workflows.py\n```\n\n### Test Naming\n\n```python\n# Good test names\ndef test_user_creation_with_valid_data():\n    \"\"\"Clear name describes what is being tested.\"\"\"\n    pass\n\n\ndef test_login_fails_with_invalid_password():\n    \"\"\"Name describes expected behavior.\"\"\"\n    pass\n\n\ndef test_api_returns_404_for_missing_resource():\n    \"\"\"Specific about inputs and expected outcomes.\"\"\"\n    pass\n\n\n# Bad test names\ndef test_1():  # Not descriptive\n    pass\n\n\ndef test_user():  # Too vague\n    pass\n\n\ndef test_function():  # Doesn't explain what's tested\n    pass\n```\n\n### Test Markers\n\n```python\n# test_markers.py\nimport pytest\n\n@pytest.mark.slow\ndef test_slow_operation():\n    \"\"\"Mark slow tests.\"\"\"\n    import time\n    time.sleep(2)\n\n\n@pytest.mark.integration\ndef test_database_integration():\n    \"\"\"Mark integration tests.\"\"\"\n    pass\n\n\n@pytest.mark.skip(reason=\"Feature not implemented yet\")\ndef test_future_feature():\n    \"\"\"Skip tests temporarily.\"\"\"\n    pass\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Unix only test\")\ndef test_unix_specific():\n    \"\"\"Conditional skip.\"\"\"\n    pass\n\n\n@pytest.mark.xfail(reason=\"Known bug #123\")\ndef test_known_bug():\n    \"\"\"Mark expected failures.\"\"\"\n    assert False\n\n\n# Run with:\n# pytest -m slow          # Run only slow tests\n# pytest -m \"not slow\"    # Skip slow tests\n# pytest -m integration   # Run integration tests\n```\n\n### Coverage Reporting\n\n```bash\n# Install coverage\npip install pytest-cov\n\n# Run tests with coverage\npytest --cov=myapp tests\u002F\n\n# Generate HTML report\npytest --cov=myapp --cov-report=html tests\u002F\n\n# Fail if coverage below threshold\npytest --cov=myapp --cov-fail-under=80 tests\u002F\n\n# Show missing lines\npytest --cov=myapp --cov-report=term-missing tests\u002F\n```\n\n## Testing Database Code\n\n```python\n# test_database_models.py\nimport pytest\nfrom sqlalchemy import create_engine, Column, Integer, String\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm import sessionmaker, Session\n\nBase = declarative_base()\n\n\nclass User(Base):\n    \"\"\"User model.\"\"\"\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True)\n    name = Column(String(50))\n    email = Column(String(100), unique=True)\n\n\n@pytest.fixture(scope=\"function\")\ndef db_session() -> Session:\n    \"\"\"Create in-memory database for testing.\"\"\"\n    engine = create_engine(\"sqlite:\u002F\u002F\u002F:memory:\")\n    Base.metadata.create_all(engine)\n\n    SessionLocal = sessionmaker(bind=engine)\n    session = SessionLocal()\n\n    yield session\n\n    session.close()\n\n\ndef test_create_user(db_session):\n    \"\"\"Test creating a user.\"\"\"\n    user = User(name=\"Test User\", email=\"test@example.com\")\n    db_session.add(user)\n    db_session.commit()\n\n    assert user.id is not None\n    assert user.name == \"Test User\"\n\n\ndef test_query_user(db_session):\n    \"\"\"Test querying users.\"\"\"\n    user1 = User(name=\"User 1\", email=\"user1@example.com\")\n    user2 = User(name=\"User 2\", email=\"user2@example.com\")\n\n    db_session.add_all([user1, user2])\n    db_session.commit()\n\n    users = db_session.query(User).all()\n    assert len(users) == 2\n\n\ndef test_unique_email_constraint(db_session):\n    \"\"\"Test unique email constraint.\"\"\"\n    from sqlalchemy.exc import IntegrityError\n\n    user1 = User(name=\"User 1\", email=\"same@example.com\")\n    user2 = User(name=\"User 2\", email=\"same@example.com\")\n\n    db_session.add(user1)\n    db_session.commit()\n\n    db_session.add(user2)\n\n    with pytest.raises(IntegrityError):\n        db_session.commit()\n```\n\n## CI\u002FCD Integration\n\n```yaml\n# .github\u002Fworkflows\u002Ftest.yml\nname: Tests\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n\n    steps:\n      - uses: actions\u002Fcheckout@v3\n\n      - name: Set up Python\n        uses: actions\u002Fsetup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          pip install -e \".[dev]\"\n          pip install pytest pytest-cov\n\n      - name: Run tests\n        run: |\n          pytest --cov=myapp --cov-report=xml\n\n      - name: Upload coverage\n        uses: codecov\u002Fcodecov-action@v3\n        with:\n          file: .\u002Fcoverage.xml\n```\n\n## Configuration Files\n\n```ini\n# pytest.ini\n[pytest]\ntestpaths = tests\npython_files = test_*.py\npython_classes = Test*\npython_functions = test_*\naddopts =\n    -v\n    --strict-markers\n    --tb=short\n    --cov=myapp\n    --cov-report=term-missing\nmarkers =\n    slow: marks tests as slow\n    integration: marks integration tests\n    unit: marks unit tests\n    e2e: marks end-to-end tests\n```\n\n```toml\n# pyproject.toml\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\naddopts = [\n    \"-v\",\n    \"--cov=myapp\",\n    \"--cov-report=term-missing\",\n]\n\n[tool.coverage.run]\nsource = [\"myapp\"]\nomit = [\"*\u002Ftests\u002F*\", \"*\u002Fmigrations\u002F*\"]\n\n[tool.coverage.report]\nexclude_lines = [\n    \"pragma: no cover\",\n    \"def __repr__\",\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n]\n```\n\n## Resources\n\n- **pytest documentation**: https:\u002F\u002Fdocs.pytest.org\u002F\n- **unittest.mock**: https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Funittest.mock.html\n- **hypothesis**: Property-based testing\n- **pytest-asyncio**: Testing async code\n- **pytest-cov**: Coverage reporting\n- **pytest-mock**: pytest wrapper for mock\n\n## Best Practices Summary\n\n1. **Write tests first** (TDD) or alongside code\n2. **One assertion per test** when possible\n3. **Use descriptive test names** that explain behavior\n4. **Keep tests independent** and isolated\n5. **Use fixtures** for setup and teardown\n6. **Mock external dependencies** appropriately\n7. **Parametrize tests** to reduce duplication\n8. **Test edge cases** and error conditions\n9. **Measure coverage** but focus on quality\n10. **Run tests in CI\u002FCD** on every commit\n",{"name":5,"description":7},"main","7850740bd837adb32f0a109ab2af82482196c6f2","skills\u002F_collections\u002Fwshobson-agents\u002Fplugins\u002Fpython-development\u002Fskills\u002Fpython-testing-patterns",1,"https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills","https:\u002F\u002Fzturnsgo.com","https:\u002F\u002Fgithub.com\u002Fshengdabai","https:\u002F\u002Fzturnsgo.com\u002F","AI × education creator — producer, not consumer. Taught 6,000+ learners; shipping learning tools & daily essays with Claude Code, practicing @xiaolai's way 🥋","2026-11-01 21:28:23",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add shengdabai\u002FTony-Claude-Code-Skills --skill python-testing-patterns",{"tool":65,"command":66},"bunx","bunx skills add shengdabai\u002FTony-Claude-Code-Skills --skill python-testing-patterns",{"tool":68,"command":69},"pnpm","pnpm dlx skills add shengdabai\u002FTony-Claude-Code-Skills --skill python-testing-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002F_collections\u002Fwshobson-agents\u002Fplugins\u002Fpython-development\u002Fskills\u002Fpython-testing-patterns\u002FSKILL.md",true,[],[74,90,124,149,171,191],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":83,"publishedAt":44},"01M70P17DAD4C1C47NPH6A96EM","xiaolai-methodology","以李笑来(Li Xiaolai)的思考方式、方法论与表达风格,为用户做全方位 1-on-1 教练指导,覆盖创业、赚钱、投资、学习、自学、读书写作、用 AI、以及人生心态与自我管理。当用户提到 \"李笑来\"、\"笑来\"、\"笑来老师\"、\"扮演李笑来\"、\"找笑来聊聊\"、\"笑来怎么看\"、\"xiaolai\"、\"把时间当作朋友\"、\"通往财富自由之路\"、\"定投改变命运\"、\"韭菜的自我修养\"、\"自学是门手艺\"、\"让时间陪你慢慢变富\"、\"七年就是一辈子\"、\"元认知\"、\"活在未来\"、\"复利\u002F长期主义\"、\"践行\"、\"痴迷改进\"、\"个人商业模式\u002F出售时间\"、\"睡后收入\"、\"定投\"、\"财富自由\"、\"操作系统升级\"、\"最少必要知识\"、\"北极星提示词\"、\"用 AI 该怎么学\u002F思考\" 等任一触发词,或当用户带着关于创业\u002F赚钱\u002F投资\u002F学习\u002F职业\u002F重大人生决策的困惑、希望被人用笑来式的「逻辑 + 大白话 + 类比 + 重新定义 + 践行导向」来点醒和指导时,自动进入李笑来教练模式。","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fxiaolai-methodology\u002FSKILL.md",[80],{"categoryNo":81,"categoryName":82,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",[84,85,86,87,88,39,89],"methodology","xiaolai","xiaolai methodology","朋友","投资","收入",{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":104,"publishedAt":44},"01M70P13NB9YP7ACJR1S861CDS","zread","Produce and consume a wiki-style knowledge base for a code repository via the `zread` CLI and its on-disk output under `.\u002F.zread\u002Fwiki\u002F`. Use this skill whenever the user wants to understand, onboard onto, explore, summarize, map, or get an overview of an unfamiliar codebase; asks for architecture docs, a project wiki, a repo walkthrough, module\u002Fpackage explanations, or \"what does this repo do\"; wants to generate, regenerate, resume, browse, or serve code documentation locally; or mentions zread \u002F zread.ai directly. Also use it proactively before diving into a large unknown repo — if `.\u002F.zread\u002Fwiki\u002Fcurrent` exists, read the generated pages instead of crawling source file-by-file; if it doesn't, consider offering to run `zread generate`. The trigger is the intent (understand a codebase through generated docs), not the literal word \"zread\".","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fzread\u002FSKILL.md",[96,99],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[92,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],"knowledge","wiki","explore","walkthrough","project","get","knowledge base","run","generate","intent","cli","map","documentation","project wiki","disk","architecture","code","unknown","generated",{"skillNo":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":128,"categories":129,"keywords":134,"publishedAt":44},"01M70P10S4SWMZQ8BJAFBFDRW3","youtube","Launch the YouTube Creator Agent to auto-generate videos.\nUse when: user types \u002Fyoutube, wants to create YouTube videos,\ngenerate video content, or upload to YouTube.\nKeywords: youtube, video, 视频, 创作视频, 发布视频","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fyoutube\u002FSKILL.md",[130,131],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":132,"categoryName":133,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000036","内容创作者",[126,135,136,137,138,139,140,141,113,142,143,144,38,30,145,146,147,148],"user","content","视频","creator","generate video","videos","launch","video content","create","upload","auto","types","Video","创作",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":156,"publishedAt":44},"01M70P0YE95D8BACZCGV8BK90K","youtube-script-sync","Sync, update, or optimize Tony's GetNote knowledge base `Youtube视频逐字稿` by turning each parent recording or first-person idea note into an idempotent child note containing a directly recordable YouTube long-video or Shorts script. Use when the user says “更新\u002F同步\u002F优化 Youtube视频逐字稿知识库”, or says “更新\u002F同步\u002F优化该知识库” while this knowledge base is the active context.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fyoutube-script-sync\u002FSKILL.md",[155],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},[157,158,126,159,160,147,161,162,163,164,165,166,167,168,111,169,38,30,105,170],"sync","script","youtube script sync","recording","shorts","first","base","person","update","active","child","optimize","idempotent","long",{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":181,"publishedAt":44},"01M70P0WMV5XT0EEC0N445PW07","writing-skills","How to write SKILL.md files that trigger reliably and teach effectively. Use when creating, improving, or reviewing Claude Code skills.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fxiaolai\u002Fnlpm\u002Fskills\u002Fnlpm\u002Fwriting-skills\u002FSKILL.md",[177,178,179,180],{"categoryNo":81,"categoryName":82,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},{"categoryNo":132,"categoryName":133,"taxonomyType":102,"taxonomyTypeDesc":103},[182,39,183,184,185,186,121,38,30,187,188,189,190],"writing skills","how","teach","write skill","write","claude code skills","files","how to","trigger",{"skillNo":192,"name":193,"nameZh":6,"description":194,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":195,"categories":196,"keywords":199,"publishedAt":44},"01M70P0SJFR4YVJQBGMHKGSKXT","writing-rules","How to write .claude\u002Frules\u002F files that Claude actually follows. Use when creating, improving, or reviewing project rules.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fxiaolai\u002Fnlpm\u002Fskills\u002Fnlpm\u002Fwriting-rules\u002FSKILL.md",[197,198],{"categoryNo":81,"categoryName":82,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":132,"categoryName":133,"taxonomyType":102,"taxonomyTypeDesc":103},[200,39,201,109,202,188,38,183,186,30,189],"Rules","writing rules","project rules"]