[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EAVFBBG7BFDNTHXAAEGQPE":3,"skill-same:01M6EAVFBBG7BFDNTHXAAEGQPE":106},{"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":6,"ownerDescription":56,"updatedAt":57,"install":58,"resources":71},"01M6EAVFBBG7BFDNTHXAAEGQPE","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.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FHarmeet10000\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],"python","python testing","python testing patterns","testing patterns","testing","patterns","testing best practices","driven","tests","fixtures","strategies","writing python","pytest","up","use","best practices","development","test","implement","writing","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\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\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\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\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## Test Design Principles\n\n### One Behavior Per Test\n\nEach test should verify exactly one behavior. This makes failures easy to diagnose and tests easy to maintain.\n\n```python\n# BAD - testing multiple behaviors\ndef test_user_service():\n    user = service.create_user(data)\n    assert user.id is not None\n    assert user.email == data[\"email\"]\n    updated = service.update_user(user.id, {\"name\": \"New\"})\n    assert updated.name == \"New\"\n\n# GOOD - focused tests\ndef test_create_user_assigns_id():\n    user = service.create_user(data)\n    assert user.id is not None\n\ndef test_create_user_stores_email():\n    user = service.create_user(data)\n    assert user.email == data[\"email\"]\n\ndef test_update_user_changes_name():\n    user = service.create_user(data)\n    updated = service.update_user(user.id, {\"name\": \"New\"})\n    assert updated.name == \"New\"\n```\n\n### Test Error Paths\n\nAlways test failure cases, not just happy paths.\n\n```python\ndef test_get_user_raises_not_found():\n    with pytest.raises(UserNotFoundError) as exc_info:\n        service.get_user(\"nonexistent-id\")\n\n    assert \"nonexistent-id\" in str(exc_info.value)\n\ndef test_create_user_rejects_invalid_email():\n    with pytest.raises(ValueError, match=\"Invalid email format\"):\n        service.create_user({\"email\": \"not-an-email\"})\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 Convention\n\nA common pattern: `test_\u003Cunit>_\u003Cscenario>_\u003Cexpected_outcome>`. Adapt to your team's preferences.\n\n```python\n# Pattern: test_\u003Cunit>_\u003Cscenario>_\u003Cexpected>\ndef test_create_user_with_valid_data_returns_user():\n    ...\n\ndef test_create_user_with_duplicate_email_raises_conflict():\n    ...\n\ndef test_get_user_with_unknown_id_returns_none():\n    ...\n\n# Good test names - clear and descriptive\ndef test_user_creation_with_valid_data():\n    \"\"\"Clear name describes what is being tested.\"\"\"\n    pass\n\ndef test_login_fails_with_invalid_password():\n    \"\"\"Name describes expected behavior.\"\"\"\n    pass\n\ndef test_api_returns_404_for_missing_resource():\n    \"\"\"Specific about inputs and expected outcomes.\"\"\"\n    pass\n\n# Bad test names - avoid these\ndef test_1():  # Not descriptive\n    pass\n\ndef test_user():  # Too vague\n    pass\n\ndef test_function():  # Doesn't explain what's tested\n    pass\n```\n\n### Testing Retry Behavior\n\nVerify that retry logic works correctly using mock side effects.\n\n```python\nfrom unittest.mock import Mock\n\ndef test_retries_on_transient_error():\n    \"\"\"Test that service retries on transient failures.\"\"\"\n    client = Mock()\n    # Fail twice, then succeed\n    client.request.side_effect = [\n        ConnectionError(\"Failed\"),\n        ConnectionError(\"Failed\"),\n        {\"status\": \"ok\"},\n    ]\n\n    service = ServiceWithRetry(client, max_retries=3)\n    result = service.fetch()\n\n    assert result == {\"status\": \"ok\"}\n    assert client.request.call_count == 3\n\ndef test_gives_up_after_max_retries():\n    \"\"\"Test that service stops retrying after max attempts.\"\"\"\n    client = Mock()\n    client.request.side_effect = ConnectionError(\"Failed\")\n\n    service = ServiceWithRetry(client, max_retries=3)\n\n    with pytest.raises(ConnectionError):\n        service.fetch()\n\n    assert client.request.call_count == 3\n\ndef test_does_not_retry_on_permanent_error():\n    \"\"\"Test that permanent errors are not retried.\"\"\"\n    client = Mock()\n    client.request.side_effect = ValueError(\"Invalid input\")\n\n    service = ServiceWithRetry(client, max_retries=3)\n\n    with pytest.raises(ValueError):\n        service.fetch()\n\n    # Only called once - no retry for ValueError\n    assert client.request.call_count == 1\n```\n\n### Mocking Time with Freezegun\n\nUse freezegun to control time in tests for predictable time-dependent behavior.\n\n```python\nfrom freezegun import freeze_time\nfrom datetime import datetime, timedelta\n\n@freeze_time(\"2026-01-15 10:00:00\")\ndef test_token_expiry():\n    \"\"\"Test token expires at correct time.\"\"\"\n    token = create_token(expires_in_seconds=3600)\n    assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0)\n\n@freeze_time(\"2026-01-15 10:00:00\")\ndef test_is_expired_returns_false_before_expiry():\n    \"\"\"Test token is not expired when within validity period.\"\"\"\n    token = create_token(expires_in_seconds=3600)\n    assert not token.is_expired()\n\n@freeze_time(\"2026-01-15 12:00:00\")\ndef test_is_expired_returns_true_after_expiry():\n    \"\"\"Test token is expired after validity period.\"\"\"\n    token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0))\n    assert token.is_expired()\n\ndef test_with_time_travel():\n    \"\"\"Test behavior across time using freeze_time context.\"\"\"\n    with freeze_time(\"2026-01-01\") as frozen_time:\n        item = create_item()\n        assert item.created_at == datetime(2026, 1, 1)\n\n        # Move forward in time\n        frozen_time.move_to(\"2026-01-15\")\n        assert item.age_days == 14\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\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\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\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\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## Test Design Principles\n\n### One Behavior Per Test\n\nEach test should verify exactly one behavior. This makes failures easy to diagnose and tests easy to maintain.\n\n```python\n# BAD - testing multiple behaviors\ndef test_user_service():\n    user = service.create_user(data)\n    assert user.id is not None\n    assert user.email == data[\"email\"]\n    updated = service.update_user(user.id, {\"name\": \"New\"})\n    assert updated.name == \"New\"\n\n# GOOD - focused tests\ndef test_create_user_assigns_id():\n    user = service.create_user(data)\n    assert user.id is not None\n\ndef test_create_user_stores_email():\n    user = service.create_user(data)\n    assert user.email == data[\"email\"]\n\ndef test_update_user_changes_name():\n    user = service.create_user(data)\n    updated = service.update_user(user.id, {\"name\": \"New\"})\n    assert updated.name == \"New\"\n```\n\n### Test Error Paths\n\nAlways test failure cases, not just happy paths.\n\n```python\ndef test_get_user_raises_not_found():\n    with pytest.raises(UserNotFoundError) as exc_info:\n        service.get_user(\"nonexistent-id\")\n\n    assert \"nonexistent-id\" in str(exc_info.value)\n\ndef test_create_user_rejects_invalid_email():\n    with pytest.raises(ValueError, match=\"Invalid email format\"):\n        service.create_user({\"email\": \"not-an-email\"})\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 Convention\n\nA common pattern: `test_\u003Cunit>_\u003Cscenario>_\u003Cexpected_outcome>`. Adapt to your team's preferences.\n\n```python\n# Pattern: test_\u003Cunit>_\u003Cscenario>_\u003Cexpected>\ndef test_create_user_with_valid_data_returns_user():\n    ...\n\ndef test_create_user_with_duplicate_email_raises_conflict():\n    ...\n\ndef test_get_user_with_unknown_id_returns_none():\n    ...\n\n# Good test names - clear and descriptive\ndef test_user_creation_with_valid_data():\n    \"\"\"Clear name describes what is being tested.\"\"\"\n    pass\n\ndef test_login_fails_with_invalid_password():\n    \"\"\"Name describes expected behavior.\"\"\"\n    pass\n\ndef test_api_returns_404_for_missing_resource():\n    \"\"\"Specific about inputs and expected outcomes.\"\"\"\n    pass\n\n# Bad test names - avoid these\ndef test_1():  # Not descriptive\n    pass\n\ndef test_user():  # Too vague\n    pass\n\ndef test_function():  # Doesn't explain what's tested\n    pass\n```\n\n### Testing Retry Behavior\n\nVerify that retry logic works correctly using mock side effects.\n\n```python\nfrom unittest.mock import Mock\n\ndef test_retries_on_transient_error():\n    \"\"\"Test that service retries on transient failures.\"\"\"\n    client = Mock()\n    # Fail twice, then succeed\n    client.request.side_effect = [\n        ConnectionError(\"Failed\"),\n        ConnectionError(\"Failed\"),\n        {\"status\": \"ok\"},\n    ]\n\n    service = ServiceWithRetry(client, max_retries=3)\n    result = service.fetch()\n\n    assert result == {\"status\": \"ok\"}\n    assert client.request.call_count == 3\n\ndef test_gives_up_after_max_retries():\n    \"\"\"Test that service stops retrying after max attempts.\"\"\"\n    client = Mock()\n    client.request.side_effect = ConnectionError(\"Failed\")\n\n    service = ServiceWithRetry(client, max_retries=3)\n\n    with pytest.raises(ConnectionError):\n        service.fetch()\n\n    assert client.request.call_count == 3\n\ndef test_does_not_retry_on_permanent_error():\n    \"\"\"Test that permanent errors are not retried.\"\"\"\n    client = Mock()\n    client.request.side_effect = ValueError(\"Invalid input\")\n\n    service = ServiceWithRetry(client, max_retries=3)\n\n    with pytest.raises(ValueError):\n        service.fetch()\n\n    # Only called once - no retry for ValueError\n    assert client.request.call_count == 1\n```\n\n### Mocking Time with Freezegun\n\nUse freezegun to control time in tests for predictable time-dependent behavior.\n\n```python\nfrom freezegun import freeze_time\nfrom datetime import datetime, timedelta\n\n@freeze_time(\"2026-01-15 10:00:00\")\ndef test_token_expiry():\n    \"\"\"Test token expires at correct time.\"\"\"\n    token = create_token(expires_in_seconds=3600)\n    assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0)\n\n@freeze_time(\"2026-01-15 10:00:00\")\ndef test_is_expired_returns_false_before_expiry():\n    \"\"\"Test token is not expired when within validity period.\"\"\"\n    token = create_token(expires_in_seconds=3600)\n    assert not token.is_expired()\n\n@freeze_time(\"2026-01-15 12:00:00\")\ndef test_is_expired_returns_true_after_expiry():\n    \"\"\"Test token is expired after validity period.\"\"\"\n    token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0))\n    assert token.is_expired()\n\ndef test_with_time_travel():\n    \"\"\"Test behavior across time using freeze_time context.\"\"\"\n    with freeze_time(\"2026-01-01\") as frozen_time:\n        item = create_item()\n        assert item.created_at == datetime(2026, 1, 1)\n\n        # Move forward in time\n        frozen_time.move_to(\"2026-01-15\")\n        assert item.age_days == 14\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","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FHarmeet10000\u002Fskills\u002Fpython-testing-patterns",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill python-testing-patterns",{"tool":64,"command":65},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill python-testing-patterns",{"tool":67,"command":68},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill python-testing-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FHarmeet10000\u002Fskills\u002Fpython-testing-patterns\u002FSKILL.md",true,[72,78,82,86,90,94,98,102],{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"research.template.md","REFERENCE","参考文档",581,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Fresearch.template.md",{"path":79,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":80,"sourceHtmlUrl":81},"technical-steps\u002Fstep-01-init.md",5581,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-01-init.md",{"path":83,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":84,"sourceHtmlUrl":85},"technical-steps\u002Fstep-02-technical-overview.md",9447,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-02-technical-overview.md",{"path":87,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":88,"sourceHtmlUrl":89},"technical-steps\u002Fstep-03-integration-patterns.md",9995,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-03-integration-patterns.md",{"path":91,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":92,"sourceHtmlUrl":93},"technical-steps\u002Fstep-04-architectural-patterns.md",7212,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-04-architectural-patterns.md",{"path":95,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":96,"sourceHtmlUrl":97},"technical-steps\u002Fstep-05-implementation-research.md",7774,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-05-implementation-research.md",{"path":99,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":100,"sourceHtmlUrl":101},"technical-steps\u002Fstep-06-research-synthesis.md",21056,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Ftechnical-steps\u002Fstep-06-research-synthesis.md",{"path":103,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":104,"sourceHtmlUrl":105},"workflow.md",2422,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fbmad-technical-research\u002Fworkflow.md",[107,119,129,138,144,151],{"skillNo":108,"name":109,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":115,"publishedAt":44},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[112],{"categoryNo":113,"categoryName":114,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[116,117,118],"model","model hierarchy","model hierarchy skill",{"skillNo":120,"name":121,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":125,"publishedAt":44},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[124],{"categoryNo":113,"categoryName":114,"taxonomyType":21,"taxonomyTypeDesc":22},[126,127,128],"MCP","zoom","zoom mcp",{"skillNo":130,"name":131,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":44},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[135,136,137,127],"sdk","zoom apps sdk","apps",{"skillNo":139,"name":140,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":143,"publishedAt":44},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[140],{"skillNo":145,"name":146,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":150,"publishedAt":44},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[149],{"categoryNo":113,"categoryName":114,"taxonomyType":21,"taxonomyTypeDesc":22},[146],{"skillNo":152,"name":153,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":156,"publishedAt":44},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[153]]