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

unittest: fast feedback і executable contract

Тест — не доказ відсутності всіх bugs, а executable example конкретного contract. Він має падати з однієї зрозумілої причини, бути незалежним від порядку запуску й давати однаковий результат на локальній машині та в CI.

unittestTestCaseAAAisolation

Безпечна практика модуля 7

Пакет містить лише synthetic fixtures, unittest, Mock/spec_set, TemporaryDirectory та локальний subprocess. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

Test case перевіряє один observable contract

import unittest

from src.pricing import final_price


class FinalPriceTests(unittest.TestCase):
    def test_applies_percentage_discount(self) -> None:
        result = final_price(amount=100, discount_percent=15)
        self.assertEqual(result, 85)


if __name__ == "__main__":
    unittest.main()

TestCase групує related checks і надає assertions. Назва test method повинна описувати behavior, а не implementation: test_rejects_negative_amount корисніше за test_case_2. Якщо test падає, його ім’я разом із diff має підказати порушений contract.

Discovery імпортує test modules

python -m unittest discover -s tests -v

За default discovery шукає test*.py. Test files мають бути importable, а production modules — не виконувати CLI чи network calls під час import. Запускайте suite з project root і перевіряйте, що імпортується саме workspace copy, а не випадково installed package із тим самим name.

Import contract: definition під час import, виконання — лише через function або guarded entry point if __name__ == "__main__".

Arrange–Act–Assert робить причину failure видимою

def test_rejects_discount_above_one_hundred(self) -> None:
    # Arrange
    amount = 100

    # Act + Assert
    with self.assertRaisesRegex(ValueError, "0..100"):
        final_price(amount=amount, discount_percent=120)

Arrange готує мінімальний state, Act виконує одну operation, Assert перевіряє outcome. Коментарі не обов’язкові, якщо структура очевидна. Десять unrelated acts в одному test утруднюють diagnosis: перший failure приховає наступні.

Assertion має відповідати типу evidence

assertEqual дає корисні diffs для strings, lists і dicts; assertIsNone передає identity contract; assertRaises доводить expected failure; assertLogs — diagnostic event. Голий assert result часто надто слабкий: багато неправильних values є truthy.

self.assertEqual(report, {"accepted": 3, "rejected": 1})
self.assertIsNone(optional_owner)
self.assertNotIn("secret-marker", rendered_log)

Fixture належить test, а не сусідньому запуску

setUp() виконується перед кожним test method; tearDown() — після. Використовуйте їх лише для невеликого повторюваного state. Shared mutable class fixture або production database створюють order dependency. Cleanup треба реєструвати одразу після acquisition, щоб partial setup теж не залишив resource.

def setUp(self) -> None:
    self.records = [{"id": "case-01"}]

def test_does_not_mutate_input(self) -> None:
    snapshot = [row.copy() for row in self.records]
    build_report(self.records)
    self.assertEqual(self.records, snapshot)

Red–Green–Refactor має причинний порядок

  1. Напишіть test, що падає з очікуваної причини.
  2. Зробіть мінімальну production-зміну, яка реалізує contract.
  3. Запустіть весь relevant suite, а не лише новий test.
  4. Refactor без зміни observable behavior.

Test, який одразу green до реалізації, може перевіряти не ту гілку або бути надто слабким. Навмисне побачити red — важлива перевірка самого тесту.

Definition of Done

  • Tests знаходяться discovery з project root і не запускають side effects на import.
  • Кожна назва пояснює behavior та scenario.
  • Assertions точні й показують корисний diff.
  • Tests незалежні від order, clock, network і production state.
  • Новий test спочатку доведено red, потім весь relevant suite green.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

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

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

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

1. Яка назва unittest method найкраще повідомляє порушений business contract під час failure у pricing module?
2. Навіщо перед green result навмисно побачити, що новий test падає з очікуваної причини?
3. Test перевіряє exact dict report, але використовує лише self.assertTrue(report). Чому assertion недостатня?