While AI coding assistants boost productivity, they also amplify risks like code duplication and architectural decay. A modern testing strategy is the essential safety net.
Focus tests on the code's observable behavior, not its internal implementation.
# service.py
def add(a: int, b: int) -> int:
return a + b
# test_service.py
def test_add_positive_numbers():
assert add(2, 3) == 5
Use Dependency Injection to provide dependencies from the outside for easier testing.
class Notifier: ...
class OrderService:
def __init__(self, notifier: Notifier):
self._notifier = notifier
def place_order(self, order):
# ... logic ...
self._notifier.send("Order placed!")
Use fakes that honor the real object's contract to avoid silent breakages.
class FakeNotifier(Notifier):
def __init__(self):
self.sent_messages = []
def send(self, message: str):
self.sent_messages.append(message)
# In test:
notifier = FakeNotifier()
service = OrderService(notifier)
Use `typing.Protocol` to define explicit interfaces that type checkers can enforce.
from typing import Protocol
class CanNotify(Protocol):
def send(self, message: str) -> None: ...
# Mypy will error if send() is missing
class EmailNotifier:
def send(self, message: str): ...
Codify architectural rules as tests to prevent structural erosion over time.
from pytest_arch import archrule
def test_domain_is_isolated():
rule = (
archrule("Domain Layer")
.should_not_import("app.services")
.should_not_import("app.repositories")
)
rule.check("app.domain")
Test Runner
Linter & Formatter
Type Checker
Architecture Tests
Code Coverage