Модуль 7 · Урок 27 із 58
Test doubles: mock, patch і dependency seams
Test double замінює collaborator, а не саму логіку, яку треба перевірити. Найстійкіший seam — explicit dependency parameter або Protocol. Mock корисний для interaction contract, але надмірна кількість mocks прив’язує suite до implementation order.
Безпечна практика модуля 7
Пакет містить лише synthetic fixtures, unittest, Mock/spec_set, TemporaryDirectory та локальний subprocess. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.
Double вибирають за потрібною evidence
- Stub повертає prepared value.
- Fake має спрощену working implementation, наприклад in-memory repository.
- Mock записує calls і дозволяє assert interaction.
- Spy спостерігає за real behavior або частково делегує.
Назви не настільки важливі, як contract: що double гарантує, які failures моделює і чи не маскує несумісність із real collaborator.
Dependency injection робить seam явним
class ReportService:
def __init__(self, sender: "Sender") -> None:
self._sender = sender
def deliver(self, report: str) -> None:
self._sender.send(report)
sender = Mock(spec_set=["send"])
ReportService(sender).deliver("summary")
sender.send.assert_called_once_with("summary")Service не створює network client усередині operation, тому test не patch-ить global state. spec обмежує readable attributes; spec_set також забороняє випадкові new attributes.
Patch там, де name lookup відбувається
# src/service.py
from src.clock import utc_now
# test_service.py
with patch("src.service.utc_now", return_value=fixed):
result = build_timestamped_report()Patch source module src.clock.utc_now не змінить уже imported name у src.service. Офіційне правило: patch object у namespace, де system under test його шукає. Context manager або decorator гарантує restore після test.
autospec ловить неправильні signatures
with patch("src.service.send_report", autospec=True) as send:
run_delivery()
send.assert_called_once_with("daily", retry=False)Без spec Mock створює attributes на demand і може прийняти typo або impossible arguments. autospec копіює API/signature настільки, щоб misuse швидко впав. Він не доводить semantic compatibility або real network behavior.
return_value і side_effect моделюють контрольовані paths
sender = Mock()
sender.send.side_effect = DeliveryError("temporary")
with self.assertRaisesRegex(ReportError, "delivery failed"):
ReportService(sender).deliver("summary")side_effect може кинути exception, викликати function або повертати sequence values. Не будуйте складну simulation framework із nested mocks: невеликий Fake часто читабельніший і ближчий до real state transitions.
Interaction assertion потрібна лише для observable collaboration
Перевіряйте, що email sender викликано один раз із approved payload, коли саме delivery є contract. Не перевіряйте кожний private helper call і exact internal order, якщо user outcome від нього не залежить. Інакше безпечний refactor ламає tests без зміни behavior.
Definition of Done
- Double замінює external collaborator, а не logic under test.
- Patch target відповідає namespace lookup.
- spec/spec_set/autospec зменшують permissive false positives.
- Assertions зосереджені на outcome й необхідних effects.
- Critical adapter окремо має integration або contract test.
Методичні джерела
Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.
Закріпіть матеріал уроку
Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.