[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CXS32D5S0B0KSMYCEWN6YM":3,"skill-same:01M6CXS32D5S0B0KSMYCEWN6YM":56},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":36,"commitSha":37,"skillDirectory":5,"forksCount":38,"repositoryHtmlUrl":39,"repositoryHomepageUrl":6,"ownerHtmlUrl":40,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":41,"install":42,"resources":55},"01M6CXS32D5S0B0KSMYCEWN6YM","unit-testing",null,"Unit testing best practices and patterns","ffsshhttiikk\u002Fopencode-agents-skills","ffsshhttiikk","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F226346827?v=4","01M6CWD9RR3Y7VRQX3MXYC8FYH","01M6CWD9WVPYAZW0J82G1VKGHY",3,"MIT","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Funit-testing\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[23,24,25,26,27,28],"testing","unit testing","best practices","testing best practices","opencode","patterns","2026-11-10 10:12:30",false,"---\nname: unit-testing\ndescription: Unit testing best practices and patterns\nlicense: MIT\ncompatibility: opencode\nmetadata:\n  audience: developers\n  category: testing\n---\n## What I do\n- Write effective unit tests\n- Structure test suites\n- Use test doubles (mocks, stubs, fakes)\n- Test edge cases and errors\n- Achieve good coverage\n- Use parameterized tests\n- Write readable tests\n- Follow testing best practices\n\n## When to use me\nWhen writing unit tests or improving test coverage.\n\n## Unit Test Structure\n```python\nimport pytest\nfrom unittest.mock import Mock, patch, MagicMock\nfrom dataclasses import dataclass\n\n\n# Arrange-Act-Assert Pattern\nclass TestUserService:\n    \"\"\"Unit tests for UserService.\"\"\"\n    \n    @pytest.fixture\n    def user_repository(self):\n        \"\"\"Create mock user repository.\"\"\"\n        return Mock()\n    \n    @pytest.fixture\n    def email_service(self):\n        \"\"\"Create mock email service.\"\"\"\n        return Mock()\n    \n    @pytest.fixture\n    def service(self, user_repository, email_service):\n        \"\"\"Create service with dependencies.\"\"\"\n        return UserService(\n            user_repository=user_repository,\n            email_service=email_service,\n        )\n    \n    def test_create_user_saves_and_sends_welcome_email(\n        self,\n        service: UserService,\n        user_repository: Mock,\n        email_service: Mock\n    ):\n        \"\"\"Test creating a user saves to DB and sends email.\"\"\"\n        # Arrange\n        email = \"user@example.com\"\n        name = \"Test User\"\n        \n        user_repository.exists_by_email.return_value = False\n        \n        # Act\n        result = service.create_user(email, name)\n        \n        # Assert\n        # Verify repository calls\n        user_repository.exists_by_email.assert_called_once_with(email)\n        user_repository.save.assert_called_once()\n        \n        # Verify email was sent\n        email_service.send_welcome_email.assert_called_once_with(\n            email=email,\n            name=name,\n        )\n        \n        # Verify result\n        assert result.email == email\n        assert result.name == name\n        assert result.id is not None\n    \n    def test_create_user_raises_error_for_duplicate_email(\n        self,\n        service: UserService,\n        user_repository: Mock\n    ):\n        \"\"\"Test that duplicate email raises error.\"\"\"\n        # Arrange\n        email = \"existing@example.com\"\n        user_repository.exists_by_email.return_value = True\n        \n        # Act & Assert\n        with pytest.raises(DuplicateEmailError):\n            service.create_user(email, \"Test\")\n        \n        # Verify save was never called\n        user_repository.save.assert_not_called()\n```\n\n## Using Mocks Effectively\n```python\nfrom unittest.mock import Mock, patch, MagicMock\nimport pytest\n\n\nclass TestPaymentProcessor:\n    \"\"\"Test payment processor with mocks.\"\"\"\n    \n    @patch('app.services.payment_gateway.PaymentGateway')\n    def test_process_payment_success(\n        self,\n        mock_gateway_class\n    ):\n        \"\"\"Test successful payment processing.\"\"\"\n        # Arrange\n        mock_gateway = MagicMock()\n        mock_gateway.process_payment.return_value = {\n            \"status\": \"success\",\n            \"transaction_id\": \"tx_123\"\n        }\n        mock_gateway_class.return_value = mock_gateway\n        \n        processor = PaymentProcessor(gateway=mock_gateway)\n        \n        # Act\n        result = processor.process(\n            amount=100.00,\n            currency=\"USD\",\n            source=\"tok_visa\"\n        )\n        \n        # Assert\n        mock_gateway.process_payment.assert_called_once_with(\n            amount=100.00,\n            currency=\"USD\",\n            source=\"tok_visa\",\n        )\n        \n        assert result.status == \"success\"\n        assert result.transaction_id == \"tx_123\"\n    \n    @patch('app.services.payment_gateway.PaymentGateway')\n    def test_process_payment_handles_error(\n        self,\n        mock_gateway_class\n    ):\n        \"\"\"Test error handling in payment processing.\"\"\"\n        # Arrange\n        mock_gateway = MagicMock()\n        mock_gateway.process_payment.side_effect = PaymentError(\n            \"Card declined\"\n        )\n        mock_gateway_class.return_value = mock_gateway\n        \n        processor = PaymentProcessor(gateway=mock_gateway)\n        \n        # Act & Assert\n        with pytest.raises(PaymentProcessingError):\n            processor.process(\n                amount=100.00,\n                currency=\"USD\",\n                source=\"tok_visa\"\n            )\n```\n\n## Parameterized Tests\n```python\nimport pytest\nfrom hypothesis import given, strategies as st\n\n\nclass TestValidator:\n    \"\"\"Tests for input validator.\"\"\"\n    \n    @pytest.mark.parametrize(\"email,is_valid\", [\n        (\"user@example.com\", True),\n        (\"user.name@example.com\", True),\n        (\"user+tag@example.com\", True),\n        (\"invalid\", False),\n        (\"@example.com\", False),\n        (\"user@\", False),\n        (\"user@.com\", False),\n    ])\n    def test_email_validation(self, email: str, is_valid: bool):\n        \"\"\"Test email validation with various inputs.\"\"\"\n        validator = EmailValidator()\n        \n        result = validator.is_valid_email(email)\n        \n        assert result == is_valid\n    \n    @pytest.mark.parametrize(\"password,expected\", [\n        (\"Short1!\", False),  # Too short\n        (\"lowercase1!\", False),  # No uppercase\n        (\"UPPERCASE1!\", False),  # No lowercase\n        (\"NoNumbers!\", False),  # No numbers\n        (\"ValidPass1!\", True),\n    ])\n    def test_password_strength(\n        self,\n        password: str,\n        expected: bool\n    ):\n        \"\"\"Test password strength validation.\"\"\"\n        validator = PasswordValidator()\n        \n        result = validator.is_strong(password)\n        \n        assert result == expected\n\n\n# Property-based testing with Hypothesis\nclass TestMathOperations:\n    \"\"\"Property-based tests for math operations.\"\"\"\n    \n    @given(st.integers(), st.integers())\n    def test_addition_is_commutative(self, a: int, b: int):\n        \"\"\"Addition should be commutative: a + b == b + a.\"\"\"\n        assert a + b == b + a\n    \n    @given(st.integers(min_value=1))\n    def test_multiplication_by_one(self, a: int):\n        \"\"\"Multiplication by one should return same number.\"\"\"\n        assert a * 1 == a\n    \n    @given(st.lists(st.integers(), min_size=1))\n    def test_sum_is_at_least_max(self, numbers: list):\n        \"\"\"Sum of list should be at least the max value.\"\"\"\n        assert sum(numbers) >= max(numbers)\n    \n    @given(st.text(min_size=1))\n    def test_string_reversal_length(self, s: str):\n        \"\"\"Reversed string should have same length.\"\"\"\n        assert len(s[::-1]) == len(s)\n```\n\n## Testing Edge Cases\n```python\nimport pytest\nfrom unittest.mock import Mock, patch\n\n\nclass TestOrderProcessor:\n    \"\"\"Test order processor with edge cases.\"\"\"\n    \n    def test_empty_cart_raises_error(self):\n        \"\"\"Test that empty cart raises error.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(EmptyCartError):\n            processor.process_order(user_id=\"123\", cart_items=[])\n    \n    def test_single_item_order(self):\n        \"\"\"Test order with single item.\"\"\"\n        repository = Mock()\n        repository.save.return_value = Mock(id=\"order_123\")\n        \n        processor = OrderProcessor(repository)\n        \n        result = processor.process_order(\n            user_id=\"123\",\n            cart_items=[CartItem(product_id=\"p1\", quantity=1)]\n        )\n        \n        assert result.id == \"order_123\"\n        repository.save.assert_called_once()\n    \n    def test_multiple_items_order(self):\n        \"\"\"Test order with multiple items.\"\"\"\n        repository = Mock()\n        repository.save.return_value = Mock(id=\"order_123\")\n        \n        processor = OrderProcessor(repository)\n        \n        items = [\n            CartItem(product_id=\"p1\", quantity=2),\n            CartItem(product_id=\"p2\", quantity=1),\n        ]\n        \n        result = processor.process_order(\"123\", items)\n        \n        # Verify order was saved\n        assert repository.save.called\n        \n        # Get the order that was saved\n        saved_order = repository.save.call_args[0][0]\n        assert len(saved_order.items) == 2\n    \n    def test_handles_repository_error(self):\n        \"\"\"Test error handling when repository fails.\"\"\"\n        repository = Mock()\n        repository.save.side_effect = DatabaseConnectionError()\n        \n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(OrderProcessingError):\n            processor.process_order(\"123\", [CartItem(\"p1\", 1)])\n    \n    def test_calculates_correct_total(self):\n        \"\"\"Test that order total is calculated correctly.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        items = [\n            CartItem(product_id=\"p1\", quantity=2, price=10.00),\n            CartItem(product_id=\"p2\", quantity=3, price=15.00),\n        ]\n        \n        order = processor.process_order(\"123\", items)\n        \n        # Total: (2 * 10) + (3 * 15) = 20 + 45 = 65\n        assert order.total == 65.00\n    \n    @pytest.mark.parametrize(\"quantity\", [-1, 0])\n    def test_invalid_quantity_raises_error(self, quantity: int):\n        \"\"\"Test that invalid quantity raises error.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(InvalidQuantityError):\n            processor.process_order(\n                \"123\",\n                [CartItem(\"p1\", quantity)]\n            )\n```\n\n## Testing Async Code\n```python\nimport pytest\nimport asyncio\nfrom unittest.mock import AsyncMock, patch\n\n\nclass TestAsyncUserService:\n    \"\"\"Test async user service.\"\"\"\n    \n    @pytest.fixture\n    def mock_repository(self):\n        \"\"\"Create async mock repository.\"\"\"\n        return AsyncMock()\n    \n    @pytest.fixture\n    def mock_cache(self):\n        \"\"\"Create async mock cache.\"\"\"\n        return AsyncMock()\n    \n    @pytest.fixture\n    async def service(self, mock_repository, mock_cache):\n        \"\"\"Create async service.\"\"\"\n        return AsyncUserService(\n            repository=mock_repository,\n            cache=mock_cache,\n        )\n    \n    @pytest.mark.asyncio\n    async def test_get_user_caches_result(\n        self,\n        service: AsyncUserService,\n        mock_repository: AsyncMock,\n        mock_cache: AsyncMock\n    ):\n        \"\"\"Test that get_user caches results.\"\"\"\n        mock_cache.get.return_value = None\n        mock_repository.find_by_id.return_value = Mock(\n            id=\"123\",\n            name=\"Test User\"\n        )\n        \n        # Act\n        result = await service.get_user(\"123\")\n        \n        # Assert\n        mock_cache.get.assert_called_once_with(\"user_123\")\n        mock_repository.find_by_id.assert_called_once_with(\"123\")\n        mock_cache.set.assert_called_once()\n    \n    @pytest.mark.asyncio\n    async def test_returns_cached_user(\n        self,\n        service: AsyncUserService,\n        mock_repository: AsyncMock,\n        mock_cache: AsyncMock\n    ):\n        \"\"\"Test that cached user is returned.\"\"\"\n        cached_user = Mock(id=\"123\", name=\"Cached User\")\n        mock_cache.get.return_value = cached_user\n        \n        result = await service.get_user(\"123\")\n        \n        # Should not call repository\n        mock_repository.find_by_id.assert_not_called()\n        assert result == cached_user\n    \n    @pytest.mark.asyncio\n    async def test_handles_not_found(self):\n        \"\"\"Test handling of user not found.\"\"\"\n        mock_repository = AsyncMock()\n        mock_repository.find_by_id.return_value = None\n        mock_cache = AsyncMock()\n        \n        service = AsyncUserService(mock_repository, mock_cache)\n        \n        result = await service.get_user(\"nonexistent\")\n        \n        assert result is None\n```\n\n## Test Fixtures\n```python\nimport pytest\nfrom typing import Generator\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass TestUser:\n    \"\"\"Test user fixture.\"\"\"\n    email: str = \"test@example.com\"\n    name: str = \"Test User\"\n    password: str = \"SecurePass123!\"\n\n\n@pytest.fixture\ndef test_user() -> TestUser:\n    \"\"\"Create test user.\"\"\"\n    return TestUser()\n\n\n@pytest.fixture\ndef new_user(test_user) -> dict:\n    \"\"\"Create user data dict.\"\"\"\n    return {\n        \"email\": test_user.email,\n        \"name\": test_user.name,\n        \"password\": test_user.password,\n    }\n\n\n@pytest.fixture\ndef authenticated_client(client, test_user):\n    \"\"\"Create authenticated API client.\"\"\"\n    token = generate_test_token(test_user.email)\n    client.headers[\"Authorization\"] = f\"Bearer {token}\"\n    return client\n\n\n@pytest.fixture(scope=\"session\")\ndef database():\n    \"\"\"Create test database (session-scoped).\"\"\"\n    db = create_test_database()\n    yield db\n    db.close()\n\n\n@pytest.fixture\ndef db_session(database):\n    \"\"\"Create database session for test.\"\"\"\n    session = database.new_session()\n    yield session\n    session.rollback()\n    session.close()\n\n\n@pytest.fixture(autouse=True)\ndef cleanup_after_test(db_session):\n    \"\"\"Automatically clean up after each test.\"\"\"\n    yield\n    # Rollback any uncommitted changes\n    db_session.rollback()\n\n\n@pytest.fixture\ndef sample_orders() -> list:\n    \"\"\"Create sample orders for testing.\"\"\"\n    return [\n        {\"id\": \"1\", \"status\": \"pending\", \"total\": 100.00},\n        {\"id\": \"2\", \"status\": \"completed\", \"total\": 200.00},\n        {\"id\": \"3\", \"status\": \"pending\", \"total\": 50.00},\n    ]\n```\n\n## Best Practices\n```\n1. Test one thing per test\n   Each test should have one assertion\n\n2. Use descriptive names\n   test_user_creation_success\n   test_should_raise_error_for_negative_quantity\n\n3. Follow AAA pattern\n   Arrange, Act, Assert\n\n4. Don't test framework code\n   Test your business logic\n\n5. Use proper isolation\n   Mock external dependencies\n\n6. Test edge cases\n   Empty inputs, nulls, boundaries\n\n7. Parameterize repetitive tests\n   Use @pytest.mark.parametrize\n\n8. Use property-based testing\n   Find edge cases automatically\n\n9. Keep tests fast\n   Fast tests run frequently\n\n10. Make tests readable\n    Clear intent, good names\n```\n","## What I do\n- Write effective unit tests\n- Structure test suites\n- Use test doubles (mocks, stubs, fakes)\n- Test edge cases and errors\n- Achieve good coverage\n- Use parameterized tests\n- Write readable tests\n- Follow testing best practices\n\n## When to use me\nWhen writing unit tests or improving test coverage.\n\n## Unit Test Structure\n```python\nimport pytest\nfrom unittest.mock import Mock, patch, MagicMock\nfrom dataclasses import dataclass\n\n\n# Arrange-Act-Assert Pattern\nclass TestUserService:\n    \"\"\"Unit tests for UserService.\"\"\"\n    \n    @pytest.fixture\n    def user_repository(self):\n        \"\"\"Create mock user repository.\"\"\"\n        return Mock()\n    \n    @pytest.fixture\n    def email_service(self):\n        \"\"\"Create mock email service.\"\"\"\n        return Mock()\n    \n    @pytest.fixture\n    def service(self, user_repository, email_service):\n        \"\"\"Create service with dependencies.\"\"\"\n        return UserService(\n            user_repository=user_repository,\n            email_service=email_service,\n        )\n    \n    def test_create_user_saves_and_sends_welcome_email(\n        self,\n        service: UserService,\n        user_repository: Mock,\n        email_service: Mock\n    ):\n        \"\"\"Test creating a user saves to DB and sends email.\"\"\"\n        # Arrange\n        email = \"user@example.com\"\n        name = \"Test User\"\n        \n        user_repository.exists_by_email.return_value = False\n        \n        # Act\n        result = service.create_user(email, name)\n        \n        # Assert\n        # Verify repository calls\n        user_repository.exists_by_email.assert_called_once_with(email)\n        user_repository.save.assert_called_once()\n        \n        # Verify email was sent\n        email_service.send_welcome_email.assert_called_once_with(\n            email=email,\n            name=name,\n        )\n        \n        # Verify result\n        assert result.email == email\n        assert result.name == name\n        assert result.id is not None\n    \n    def test_create_user_raises_error_for_duplicate_email(\n        self,\n        service: UserService,\n        user_repository: Mock\n    ):\n        \"\"\"Test that duplicate email raises error.\"\"\"\n        # Arrange\n        email = \"existing@example.com\"\n        user_repository.exists_by_email.return_value = True\n        \n        # Act & Assert\n        with pytest.raises(DuplicateEmailError):\n            service.create_user(email, \"Test\")\n        \n        # Verify save was never called\n        user_repository.save.assert_not_called()\n```\n\n## Using Mocks Effectively\n```python\nfrom unittest.mock import Mock, patch, MagicMock\nimport pytest\n\n\nclass TestPaymentProcessor:\n    \"\"\"Test payment processor with mocks.\"\"\"\n    \n    @patch('app.services.payment_gateway.PaymentGateway')\n    def test_process_payment_success(\n        self,\n        mock_gateway_class\n    ):\n        \"\"\"Test successful payment processing.\"\"\"\n        # Arrange\n        mock_gateway = MagicMock()\n        mock_gateway.process_payment.return_value = {\n            \"status\": \"success\",\n            \"transaction_id\": \"tx_123\"\n        }\n        mock_gateway_class.return_value = mock_gateway\n        \n        processor = PaymentProcessor(gateway=mock_gateway)\n        \n        # Act\n        result = processor.process(\n            amount=100.00,\n            currency=\"USD\",\n            source=\"tok_visa\"\n        )\n        \n        # Assert\n        mock_gateway.process_payment.assert_called_once_with(\n            amount=100.00,\n            currency=\"USD\",\n            source=\"tok_visa\",\n        )\n        \n        assert result.status == \"success\"\n        assert result.transaction_id == \"tx_123\"\n    \n    @patch('app.services.payment_gateway.PaymentGateway')\n    def test_process_payment_handles_error(\n        self,\n        mock_gateway_class\n    ):\n        \"\"\"Test error handling in payment processing.\"\"\"\n        # Arrange\n        mock_gateway = MagicMock()\n        mock_gateway.process_payment.side_effect = PaymentError(\n            \"Card declined\"\n        )\n        mock_gateway_class.return_value = mock_gateway\n        \n        processor = PaymentProcessor(gateway=mock_gateway)\n        \n        # Act & Assert\n        with pytest.raises(PaymentProcessingError):\n            processor.process(\n                amount=100.00,\n                currency=\"USD\",\n                source=\"tok_visa\"\n            )\n```\n\n## Parameterized Tests\n```python\nimport pytest\nfrom hypothesis import given, strategies as st\n\n\nclass TestValidator:\n    \"\"\"Tests for input validator.\"\"\"\n    \n    @pytest.mark.parametrize(\"email,is_valid\", [\n        (\"user@example.com\", True),\n        (\"user.name@example.com\", True),\n        (\"user+tag@example.com\", True),\n        (\"invalid\", False),\n        (\"@example.com\", False),\n        (\"user@\", False),\n        (\"user@.com\", False),\n    ])\n    def test_email_validation(self, email: str, is_valid: bool):\n        \"\"\"Test email validation with various inputs.\"\"\"\n        validator = EmailValidator()\n        \n        result = validator.is_valid_email(email)\n        \n        assert result == is_valid\n    \n    @pytest.mark.parametrize(\"password,expected\", [\n        (\"Short1!\", False),  # Too short\n        (\"lowercase1!\", False),  # No uppercase\n        (\"UPPERCASE1!\", False),  # No lowercase\n        (\"NoNumbers!\", False),  # No numbers\n        (\"ValidPass1!\", True),\n    ])\n    def test_password_strength(\n        self,\n        password: str,\n        expected: bool\n    ):\n        \"\"\"Test password strength validation.\"\"\"\n        validator = PasswordValidator()\n        \n        result = validator.is_strong(password)\n        \n        assert result == expected\n\n\n# Property-based testing with Hypothesis\nclass TestMathOperations:\n    \"\"\"Property-based tests for math operations.\"\"\"\n    \n    @given(st.integers(), st.integers())\n    def test_addition_is_commutative(self, a: int, b: int):\n        \"\"\"Addition should be commutative: a + b == b + a.\"\"\"\n        assert a + b == b + a\n    \n    @given(st.integers(min_value=1))\n    def test_multiplication_by_one(self, a: int):\n        \"\"\"Multiplication by one should return same number.\"\"\"\n        assert a * 1 == a\n    \n    @given(st.lists(st.integers(), min_size=1))\n    def test_sum_is_at_least_max(self, numbers: list):\n        \"\"\"Sum of list should be at least the max value.\"\"\"\n        assert sum(numbers) >= max(numbers)\n    \n    @given(st.text(min_size=1))\n    def test_string_reversal_length(self, s: str):\n        \"\"\"Reversed string should have same length.\"\"\"\n        assert len(s[::-1]) == len(s)\n```\n\n## Testing Edge Cases\n```python\nimport pytest\nfrom unittest.mock import Mock, patch\n\n\nclass TestOrderProcessor:\n    \"\"\"Test order processor with edge cases.\"\"\"\n    \n    def test_empty_cart_raises_error(self):\n        \"\"\"Test that empty cart raises error.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(EmptyCartError):\n            processor.process_order(user_id=\"123\", cart_items=[])\n    \n    def test_single_item_order(self):\n        \"\"\"Test order with single item.\"\"\"\n        repository = Mock()\n        repository.save.return_value = Mock(id=\"order_123\")\n        \n        processor = OrderProcessor(repository)\n        \n        result = processor.process_order(\n            user_id=\"123\",\n            cart_items=[CartItem(product_id=\"p1\", quantity=1)]\n        )\n        \n        assert result.id == \"order_123\"\n        repository.save.assert_called_once()\n    \n    def test_multiple_items_order(self):\n        \"\"\"Test order with multiple items.\"\"\"\n        repository = Mock()\n        repository.save.return_value = Mock(id=\"order_123\")\n        \n        processor = OrderProcessor(repository)\n        \n        items = [\n            CartItem(product_id=\"p1\", quantity=2),\n            CartItem(product_id=\"p2\", quantity=1),\n        ]\n        \n        result = processor.process_order(\"123\", items)\n        \n        # Verify order was saved\n        assert repository.save.called\n        \n        # Get the order that was saved\n        saved_order = repository.save.call_args[0][0]\n        assert len(saved_order.items) == 2\n    \n    def test_handles_repository_error(self):\n        \"\"\"Test error handling when repository fails.\"\"\"\n        repository = Mock()\n        repository.save.side_effect = DatabaseConnectionError()\n        \n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(OrderProcessingError):\n            processor.process_order(\"123\", [CartItem(\"p1\", 1)])\n    \n    def test_calculates_correct_total(self):\n        \"\"\"Test that order total is calculated correctly.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        items = [\n            CartItem(product_id=\"p1\", quantity=2, price=10.00),\n            CartItem(product_id=\"p2\", quantity=3, price=15.00),\n        ]\n        \n        order = processor.process_order(\"123\", items)\n        \n        # Total: (2 * 10) + (3 * 15) = 20 + 45 = 65\n        assert order.total == 65.00\n    \n    @pytest.mark.parametrize(\"quantity\", [-1, 0])\n    def test_invalid_quantity_raises_error(self, quantity: int):\n        \"\"\"Test that invalid quantity raises error.\"\"\"\n        repository = Mock()\n        processor = OrderProcessor(repository)\n        \n        with pytest.raises(InvalidQuantityError):\n            processor.process_order(\n                \"123\",\n                [CartItem(\"p1\", quantity)]\n            )\n```\n\n## Testing Async Code\n```python\nimport pytest\nimport asyncio\nfrom unittest.mock import AsyncMock, patch\n\n\nclass TestAsyncUserService:\n    \"\"\"Test async user service.\"\"\"\n    \n    @pytest.fixture\n    def mock_repository(self):\n        \"\"\"Create async mock repository.\"\"\"\n        return AsyncMock()\n    \n    @pytest.fixture\n    def mock_cache(self):\n        \"\"\"Create async mock cache.\"\"\"\n        return AsyncMock()\n    \n    @pytest.fixture\n    async def service(self, mock_repository, mock_cache):\n        \"\"\"Create async service.\"\"\"\n        return AsyncUserService(\n            repository=mock_repository,\n            cache=mock_cache,\n        )\n    \n    @pytest.mark.asyncio\n    async def test_get_user_caches_result(\n        self,\n        service: AsyncUserService,\n        mock_repository: AsyncMock,\n        mock_cache: AsyncMock\n    ):\n        \"\"\"Test that get_user caches results.\"\"\"\n        mock_cache.get.return_value = None\n        mock_repository.find_by_id.return_value = Mock(\n            id=\"123\",\n            name=\"Test User\"\n        )\n        \n        # Act\n        result = await service.get_user(\"123\")\n        \n        # Assert\n        mock_cache.get.assert_called_once_with(\"user_123\")\n        mock_repository.find_by_id.assert_called_once_with(\"123\")\n        mock_cache.set.assert_called_once()\n    \n    @pytest.mark.asyncio\n    async def test_returns_cached_user(\n        self,\n        service: AsyncUserService,\n        mock_repository: AsyncMock,\n        mock_cache: AsyncMock\n    ):\n        \"\"\"Test that cached user is returned.\"\"\"\n        cached_user = Mock(id=\"123\", name=\"Cached User\")\n        mock_cache.get.return_value = cached_user\n        \n        result = await service.get_user(\"123\")\n        \n        # Should not call repository\n        mock_repository.find_by_id.assert_not_called()\n        assert result == cached_user\n    \n    @pytest.mark.asyncio\n    async def test_handles_not_found(self):\n        \"\"\"Test handling of user not found.\"\"\"\n        mock_repository = AsyncMock()\n        mock_repository.find_by_id.return_value = None\n        mock_cache = AsyncMock()\n        \n        service = AsyncUserService(mock_repository, mock_cache)\n        \n        result = await service.get_user(\"nonexistent\")\n        \n        assert result is None\n```\n\n## Test Fixtures\n```python\nimport pytest\nfrom typing import Generator\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass TestUser:\n    \"\"\"Test user fixture.\"\"\"\n    email: str = \"test@example.com\"\n    name: str = \"Test User\"\n    password: str = \"SecurePass123!\"\n\n\n@pytest.fixture\ndef test_user() -> TestUser:\n    \"\"\"Create test user.\"\"\"\n    return TestUser()\n\n\n@pytest.fixture\ndef new_user(test_user) -> dict:\n    \"\"\"Create user data dict.\"\"\"\n    return {\n        \"email\": test_user.email,\n        \"name\": test_user.name,\n        \"password\": test_user.password,\n    }\n\n\n@pytest.fixture\ndef authenticated_client(client, test_user):\n    \"\"\"Create authenticated API client.\"\"\"\n    token = generate_test_token(test_user.email)\n    client.headers[\"Authorization\"] = f\"Bearer {token}\"\n    return client\n\n\n@pytest.fixture(scope=\"session\")\ndef database():\n    \"\"\"Create test database (session-scoped).\"\"\"\n    db = create_test_database()\n    yield db\n    db.close()\n\n\n@pytest.fixture\ndef db_session(database):\n    \"\"\"Create database session for test.\"\"\"\n    session = database.new_session()\n    yield session\n    session.rollback()\n    session.close()\n\n\n@pytest.fixture(autouse=True)\ndef cleanup_after_test(db_session):\n    \"\"\"Automatically clean up after each test.\"\"\"\n    yield\n    # Rollback any uncommitted changes\n    db_session.rollback()\n\n\n@pytest.fixture\ndef sample_orders() -> list:\n    \"\"\"Create sample orders for testing.\"\"\"\n    return [\n        {\"id\": \"1\", \"status\": \"pending\", \"total\": 100.00},\n        {\"id\": \"2\", \"status\": \"completed\", \"total\": 200.00},\n        {\"id\": \"3\", \"status\": \"pending\", \"total\": 50.00},\n    ]\n```\n\n## Best Practices\n```\n1. Test one thing per test\n   Each test should have one assertion\n\n2. Use descriptive names\n   test_user_creation_success\n   test_should_raise_error_for_negative_quantity\n\n3. Follow AAA pattern\n   Arrange, Act, Assert\n\n4. Don't test framework code\n   Test your business logic\n\n5. Use proper isolation\n   Mock external dependencies\n\n6. Test edge cases\n   Empty inputs, nulls, boundaries\n\n7. Parameterize repetitive tests\n   Use @pytest.mark.parametrize\n\n8. Use property-based testing\n   Find edge cases automatically\n\n9. Keep tests fast\n   Fast tests run frequently\n\n10. Make tests readable\n    Clear intent, good names\n```\n",{"name":5,"license":14,"metadata":34,"description":7,"compatibility":27},{"audience":35,"category":23},"developers","main","5f7a3e453c98261f93d19a6a7de44ad8f7b72d41",2,"https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk","2026-03-01 06:55:40",{"source":8,"skillName":5,"commands":43,"prompt":53,"zipAvailable":54},[44,47,50],{"tool":45,"command":46},"npx","npx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill unit-testing",{"tool":48,"command":49},"bunx","bunx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill unit-testing",{"tool":51,"command":52},"pnpm","pnpm dlx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill unit-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Funit-testing\u002FSKILL.md",true,[],[57,68,78,87,97,106],{"skillNo":58,"name":59,"nameZh":6,"description":60,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":61,"categories":62,"keywords":63,"publishedAt":29},"01M6CXT9TXDF79BWJTMQAPB49G","xamarin","Xamarin cross-platform development","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fxamarin\u002FSKILL.md",[],[59,64,65,66,27,67],"platform","cross-platform","mobile","development",{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":74,"publishedAt":29},"01M6CXT8AD04MFYDA32VW3FKAG","wrangling","Data wrangling fundamentals","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwrangling\u002FSKILL.md",[],[70,75,27,76,77],"data wrangling","science","data",{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":29},"01M6CXT717GCN7SRRNPBDP4508","windows","Windows operating system administration and scripting","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwindows\u002FSKILL.md",[],[80,85,27,86],"system","scripting",{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":29},"01M6CXT54K794NMEM3XAPZZDKT","wide-column","Wide-column data model","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column\u002FSKILL.md",[],[94,95,27,96,77],"wide column","databases","model",{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":29},"01M6CXT28FMT2P4V9GZE67WA8E","wide-column-stores","Wide-column database stores","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column-stores\u002FSKILL.md",[],[104,95,27,105],"wide column stores","database",{"skillNo":107,"name":108,"nameZh":6,"description":109,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":29},"01M6CXT0D0M0Q4T2N7C2RX7DSN","websockets","WebSocket implementation and best practices","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwebsockets\u002FSKILL.md",[],[108,113,114,115,27,25,116],"websocket","real-time","time","implementation"]