Модуль 7 · Урок 27 із 58

Test doubles: mock, patch і dependency seams

Test double замінює collaborator, а не саму логіку, яку треба перевірити. Найстійкіший seam — explicit dependency parameter або Protocol. Mock корисний для interaction contract, але надмірна кількість mocks прив’язує suite до implementation order.

Mockpatchautospecdependency seam

Безпечна практика модуля 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.

OutcomeЩо отримує caller?
InteractionЯкий external effect обов’язковий?
SeamЧи dependency явна?
RealityДе integration test із real adapter?

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.

Практична перевірка · урок 27 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. ReportService використовує Sender collaborator. Коли Fake або Mock є правильним seam замість реального email transport?
2. Що дає explicit dependency injection constructor-ом порівняно з creation network client усередині method?
3. src.service імпортує utc_now через from src.clock import utc_now. Який patch target правильний для test service?