Implementing the Feature Slice
Implementing the Feature Slice
Build one vertical slice end to end before widening:
# services/orders.py
import logging
from collections.abc import Callable
from datetime import datetime
from proj.domain import Item, Order, Pricing
from proj.repositories import OrderRepo
log = logging.getLogger(__name__)
class OrderService:
def __init__(self, repo: OrderRepo, clock: Callable[[], datetime]) -> None:
self._repo, self._clock = repo, clock
def place(self, items: list[Item]) -> Order:
order = Pricing.price(items, at=self._clock())
self._repo.save(order)
log.info("order.placed id=%s", order.id)
return order
Discipline per slice: full type hints, domain errors separate from infrastructure errors, raise ... from exc when wrapping, logging only at boundaries. Tests construct the service with fakes and a fixed clock.
Comprehensive Tests
Comprehensive Tests
# tests/unit/test_pricing.py
import pytest
from proj.domain import pricing
@pytest.mark.parametrize("qty,unit,expected", [
(1, 100, 100), (3, 100, 270), (0, 100, 0),
])
def test_bulk_discount(qty, unit, expected):
assert pricing.total(qty, unit) == expected
def test_rejects_negative_qty():
with pytest.raises(pricing.PricingError, match="negative"):
pricing.total(-1, 100)
Layered suite:
- Unit: pure logic,
monkeypatchfor env,tmp_pathfor files — milliseconds - Integration: one real dependency (SQLite file, HTTP against a stub) — seconds
- Contract: public API shape and error formats so refactors cannot silently break callers
Measure with pytest --cov=src --cov-fail-under=80; treat uncovered branches as the goal, not the percentage. Mutation tools (mutmut) audit whether assertions actually bite.
Docs and Release
Docs and Release
Documentation ships with the code, not after it:
- README: install (
pip install -e ".[dev]"), run, test — in that order, copy-pasteable - Docstrings: Google or NumPy style on every public function;
Args/Returns/Raisessections - CHANGELOG: Keep a Changelog format —
Added / Changed / Fixedper version - Type hints + mypy are living documentation IDEs consume
Release hygiene: semantic versioning (breaking → major), python -m build + twine check for sdist/wheel correctness, tags trigger CI publish with trusted credentials, and pre-commit run --all-files gates the commit that starts a release. Interview answer: correctness comes from tests + types + review working together; onboarding speed comes from a README that answers "how do I run this" in under a minute.
Quiz
1. Why inject a clock into OrderService instead of calling datetime.now() inside?
2. What does --cov-fail-under=80 do?
3. Which change requires a major version bump under semver?
Flashcards
Question
Vertical slice
Click to reveal answer
Answer
One feature through all layers (input → logic → storage → output) before starting the next.
Question
parametrize
Click to reveal answer
Answer
Expands one test function into many cases — tables of inputs/outputs without copy-paste.
Question
Coverage floor
Click to reveal answer
Answer
pytest --cov-fail-under=N enforces a minimum in CI; aim at missed branches, not the number.
Question
Keep a Changelog
Click to reveal answer
Answer
Structured Added/Changed/Fixed sections per version — readable by humans and release tools.
Revision Notes
Key Takeaways
- 1. Inject collaborators (repo, clock, client) so services unit-test without monkeypatching
- 2. Layer tests by cost — fast pure units, targeted integrations, thin end-to-end
- 3. Docs, changelog, and version discipline are part of done, not a post-launch task
Interview Tips
- • Walk a reviewer through one slice: types, errors, logging points, and the tests that pin each behavior
- • Explain how you decide what deserves coverage attention (branches and boundaries, not line count vanity)
Cheat Sheet
inject deps+clock; raise from exc; log at boundaries; unit/tmp_path/integration/contract tiers; pytest --cov-fail-under=80; semver + build + twine check.