{"id":"b16de1b5-f5c5-456a-b390-6bfa50a6f5f0","shortId":"SybSe2","kind":"skill","title":"python-testing","tagline":"Stub-Driven TDD and layer boundary testing with pytest. Use when writing tests, deciding what to test, testing at component boundaries, or implementing test-driven development.","description":"# Testing with pytest\n\nStub-Driven TDD and layer boundary testing patterns for Python applications.\n\n## Core Principle: Stub-Driven TDD\n\nTest at component boundaries, not internal implementation:\n\n```\nRouter → Service → Repository → Entity → Database\n   ↓        ↓           ↓          ↓\n Test    Test        Test       Test\n```\n\nFollow the **Stub → Test → Implement → Refactor** workflow:\n\n1. **Stub** - Create function signature with `pass`\n2. **Test** - Write test for expected behavior\n3. **Implement** - Make test pass\n4. **Refactor** - Clean up code\n\n```python\n# 1. Stub\ndef calculate_discount(total: Decimal) -> Decimal:\n    pass\n\n# 2. Test\ndef test_discount_for_large_order():\n    result = calculate_discount(Decimal(\"150\"))\n    assert result == Decimal(\"15\")\n\n# 3. Implement\ndef calculate_discount(total: Decimal) -> Decimal:\n    if total > 100:\n        return total * Decimal(\"0.1\")\n    return Decimal(\"0\")\n```\n\n## Layer Boundary Testing Overview\n\nTest **what crosses layer boundaries**, not internal implementation:\n\n- **Entity Layer**: Domain logic, validation, transformations (from_request, to_response, to_record)\n- **Service Layer**: Business workflows, error handling, dependency orchestration\n- **Repository Layer**: CRUD operations, query logic, entity ↔ record transformations\n- **Router Layer**: Request validation, response serialization, status codes\n\nSee references/boundaries.md for comprehensive layer-specific examples.\n\n## Entity Testing Example\n\nTest transformations and business logic:\n\n```python\ndef test_product_from_request():\n    \"\"\"Test creation from request\"\"\"\n    request = CreateProductRequest(name=\"Widget\", price=Decimal(\"9.99\"))\n    product = Product.from_request(request)\n\n    assert product.name == \"Widget\"\n    assert product.price == Decimal(\"9.99\")\n    assert isinstance(product.id, UUID)\n\ndef test_product_apply_discount():\n    \"\"\"Test business logic\"\"\"\n    product = Product(id=uuid4(), name=\"Widget\", price=Decimal(\"100\"))\n    discounted = product.apply_discount(Decimal(\"0.1\"))\n\n    assert discounted.price == Decimal(\"90\")\n```\n\n## Service Testing Example\n\nTest orchestration with stubbed dependencies:\n\n```python\nfrom unittest.mock import Mock\n\ndef test_create_product_service():\n    \"\"\"Test with mocked repository\"\"\"\n    mock_repo = Mock()\n    mock_repo.save.return_value = Product(id=uuid4(), name=\"Widget\")\n\n    service = ProductService(repo=mock_repo)\n    result = service.create(CreateProductRequest(name=\"Widget\", price=Decimal(\"9.99\")))\n\n    mock_repo.save.assert_called_once()\n    assert result.name == \"Widget\"\n```\n\n## Repository Testing Example\n\nTest data access with real test database:\n\n```python\n@pytest.fixture\ndef test_db():\n    \"\"\"In-memory test database\"\"\"\n    engine = create_engine(\"sqlite:///:memory:\")\n    Base.metadata.create_all(engine)\n    return sessionmaker(bind=engine)()\n\ndef test_repository_save(test_db):\n    \"\"\"Test database operations\"\"\"\n    repo = ProductRepository(test_db)\n    product = Product(id=uuid4(), name=\"Widget\", price=Decimal(\"9.99\"))\n\n    saved = repo.save(product)\n\n    assert saved.id == product.id\n    assert test_db.query(ProductRecord).count() == 1\n```\n\n## Router Testing Example\n\nTest HTTP layer with TestClient:\n\n```python\nfrom fastapi.testclient import TestClient\n\ndef test_create_product_endpoint():\n    \"\"\"Test POST endpoint\"\"\"\n    client = TestClient(app)\n\n    response = client.post(\n        \"/products\",\n        json={\"name\": \"Widget\", \"price\": 9.99},\n    )\n\n    assert response.status_code == 201\n    assert response.json()[\"name\"] == \"Widget\"\n```\n\n## Test Organization Basics\n\n```\ntests/\n├── unit/\n│   ├── test_entities.py      # Entity + Value object tests\n│   └── test_services.py      # Service tests (with mocks)\n├── integration/\n│   ├── test_repositories.py  # Repository tests (with DB)\n│   └── test_endpoints.py     # Router tests (with client)\n└── conftest.py               # Shared fixtures\n```\n\n## Reference Documentation\n\nFor comprehensive patterns and examples, see:\n\n- **references/boundaries.md** - Layer boundary testing patterns with complete examples for each layer\n- **references/mocking.md** - Mock strategies, verification methods, and anti-patterns\n- **references/pytest.md** - Configuration, fixtures, markers, parametrization, and debugging\n\nProgressive disclosure: SKILL.md provides quick reference, references/ contain full details.","tags":["python","testing","atelier","martinffx","agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex","codex-skill"],"capabilities":["skill","source-martinffx","skill-python-testing","topic-agent-skills","topic-agentic-coding","topic-anthropic","topic-claude-code","topic-claude-skills","topic-code-review","topic-codex","topic-codex-skill","topic-opencode","topic-prompt-engineering","topic-sdd","topic-spec-driven-development"],"categories":["atelier"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/martinffx/atelier/python-testing","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add martinffx/atelier","source_repo":"https://github.com/martinffx/atelier","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 23 github stars · SKILL.md body (4,369 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:05:24.013Z","embedding":null,"createdAt":"2026-05-10T07:03:12.595Z","updatedAt":"2026-05-18T19:05:24.013Z","lastSeenAt":"2026-05-18T19:05:24.013Z","tsv":"'/products':409 '0':144 '0.1':141,263 '1':76,101,382 '100':137,258 '15':126 '150':122 '2':83,110 '201':418 '3':90,127 '4':95 '9.99':226,237,312,371,414 '90':267 'access':324 'anti':478 'anti-pattern':477 'app':406 'appli':245 'applic':46 'assert':123,231,234,238,264,316,375,378,415,419 'base.metadata.create':343 'basic':425 'behavior':89 'bind':348 'boundari':10,25,41,56,146,153,462 'busi':171,208,248 'calcul':104,119,130 'call':314 'clean':97 'client':404,448 'client.post':408 'code':99,193,417 'complet':466 'compon':24,55 'comprehens':197,455 'configur':481 'conftest.py':449 'contain':494 'core':47 'count':381 'creat':78,283,340,398 'createproductrequest':221,307 'creation':217 'cross':151 'crud':179 'data':323 'databas':64,328,338,357 'db':333,355,362,443 'debug':486 'decid':18 'decim':107,108,121,125,133,134,140,143,225,236,257,262,266,311,370 'def':103,112,129,211,242,281,331,350,396 'depend':175,275 'detail':496 'develop':31 'disclosur':488 'discount':105,114,120,131,246,259,261 'discounted.price':265 'document':453 'domain':159 'driven':6,30,37,51 'endpoint':400,403 'engin':339,341,345,349 'entiti':63,157,183,202,429 'error':173 'exampl':201,204,270,321,385,458,467 'expect':88 'fastapi.testclient':393 'fixtur':451,482 'follow':69 'full':495 'function':79 'handl':174 'http':387 'id':252,296,365 'implement':27,59,73,91,128,156 'import':279,394 'in-memori':334 'integr':438 'intern':58,155 'isinst':239 'json':410 'larg':116 'layer':9,40,145,152,158,170,178,187,199,388,461,470 'layer-specif':198 'logic':160,182,209,249 'make':92 'marker':483 'memori':336,342 'method':475 'mock':280,288,290,292,303,437,472 'mock_repo.save.assert':313 'mock_repo.save.return':293 'name':222,254,298,308,367,411,421 'object':431 'oper':180,358 'orchestr':176,272 'order':117 'organ':424 'overview':148 'parametr':484 'pass':82,94,109 'pattern':43,456,464,479 'post':402 'price':224,256,310,369,413 'principl':48 'product':213,227,244,250,251,284,295,363,364,374,399 'product.apply':260 'product.from':228 'product.id':240,377 'product.name':232 'product.price':235 'productrecord':380 'productrepositori':360 'productservic':301 'progress':487 'provid':490 'pytest':13,34 'pytest.fixture':330 'python':2,45,100,210,276,329,391 'python-test':1 'queri':181 'quick':491 'real':326 'record':168,184 'refactor':74,96 'refer':452,492,493 'references/boundaries.md':195,460 'references/mocking.md':471 'references/pytest.md':480 'repo':291,302,304,359 'repo.save':373 'repositori':62,177,289,319,352,440 'request':164,188,215,219,220,229,230 'respons':166,190,407 'response.json':420 'response.status':416 'result':118,124,305 'result.name':317 'return':138,142,346 'router':60,186,383,445 'save':353,372 'saved.id':376 'see':194,459 'serial':191 'servic':61,169,268,285,300,434 'service.create':306 'sessionmak':347 'share':450 'signatur':80 'skill' 'skill-python-testing' 'skill.md':489 'source-martinffx' 'specif':200 'status':192 'strategi':473 'stub':5,36,50,71,77,102,274 'stub-driven':4,35,49 'tdd':7,38,52 'test':3,11,17,21,22,29,32,42,53,65,66,67,68,72,84,86,93,111,113,147,149,203,205,212,216,243,247,269,271,282,286,320,322,327,332,337,351,354,356,361,384,386,397,401,423,426,432,435,441,446,463 'test-driven':28 'test_db.query':379 'test_endpoints.py':444 'test_entities.py':428 'test_repositories.py':439 'test_services.py':433 'testclient':390,395,405 'topic-agent-skills' 'topic-agentic-coding' 'topic-anthropic' 'topic-claude-code' 'topic-claude-skills' 'topic-code-review' 'topic-codex' 'topic-codex-skill' 'topic-opencode' 'topic-prompt-engineering' 'topic-sdd' 'topic-spec-driven-development' 'total':106,132,136,139 'transform':162,185,206 'unit':427 'unittest.mock':278 'use':14 'uuid':241 'uuid4':253,297,366 'valid':161,189 'valu':294,430 'verif':474 'widget':223,233,255,299,309,318,368,412,422 'workflow':75,172 'write':16,85","prices":[{"id":"8eda6d22-2b77-496e-aa20-a6833e5a8244","listingId":"b16de1b5-f5c5-456a-b390-6bfa50a6f5f0","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"martinffx","category":"atelier","install_from":"skills.sh"},"createdAt":"2026-05-10T07:03:12.595Z"}],"sources":[{"listingId":"b16de1b5-f5c5-456a-b390-6bfa50a6f5f0","source":"github","sourceId":"martinffx/atelier/python-testing","sourceUrl":"https://github.com/martinffx/atelier/tree/main/skills/python-testing","isPrimary":false,"firstSeenAt":"2026-05-10T07:03:12.595Z","lastSeenAt":"2026-05-18T19:05:24.013Z"}],"details":{"listingId":"b16de1b5-f5c5-456a-b390-6bfa50a6f5f0","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"martinffx","slug":"python-testing","github":{"repo":"martinffx/atelier","stars":23,"topics":["agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex","codex-skill","opencode","prompt-engineering","sdd","spec-driven-development"],"license":"mit","html_url":"https://github.com/martinffx/atelier","pushed_at":"2026-05-18T06:56:45Z","description":"An atelier for Opencode, Claude Code, and other coding agents: spec-driven workflows, deep thinking, and code quality.","skill_md_sha":"b080e8e15f2b5ff3623f883ea6e3ea2ea278d585","skill_md_path":"skills/python-testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/martinffx/atelier/tree/main/skills/python-testing"},"layout":"multi","source":"github","category":"atelier","frontmatter":{"name":"python-testing","description":"Stub-Driven TDD and layer boundary testing with pytest. Use when writing tests, deciding what to test, testing at component boundaries, or implementing test-driven development."},"skills_sh_url":"https://skills.sh/martinffx/atelier/python-testing"},"updatedAt":"2026-05-18T19:05:24.013Z"}}