Ответ
Для создания легко тестируемых функций следует придерживаться нескольких ключевых принципов:
-
Чистота (Purity): Функция не должна иметь побочных эффектов (не изменять глобальные переменные, не писать в файлы/БД). Её результат должен зависеть только от входных аргументов.
-
Принцип единственной ответственности (Single Responsibility): Функция должна выполнять одну, чётко определённую задачу. Это упрощает её понимание и тестирование.
-
Явные зависимости (Explicit Dependencies): Всё, что нужно функции для работы (данные, сервисы, конфигурации), должно передаваться ей в качестве аргументов. Этот подход называется Dependency Injection.
Сравнение подходов
Плохой пример (неявная зависимость от БД): Функция жестко связана с внешним источником данных, что делает её тестирование сложным. Для проверки логики потребуется реальное или тестовое подключение к базе.
# Трудно тестировать, так как функция связана с БД
def get_user_report():
db_connection = connect_to_database()
user_data = db_connection.fetch_user_data()
# ...сложная логика формирования отчета
return f"Report for {user_data['name']}"
Хороший пример (зависимость передается явно): Функция получает уже готовые данные. Её можно протестировать в полной изоляции, передав любой тестовый словарь.
# Легко тестировать, передавая любые данные
def generate_user_report(user_data: dict) -> str:
# ...сложная логика формирования отчета
return f"Report for {user_data['name']}"
Пример теста для хорошей функции
import unittest
def test_generate_user_report():
mock_user = {"name": "Alice", "id": 1}
report = generate_user_report(mock_user)
assert "Report for Alice" in report
Такой подход позволяет в тестах подменять реальные зависимости (БД, API) на тестовые заглушки (моки), изолируя и проверяя только логику самой функции.
Ответ 18+ 🔞
А, ну ты смотри, какие тут умные мысли про тестирование функций подъехали! Слушай, а ведь реально, если подумать — всё это про то, чтобы не наступать на одни и те же грабли, на которые мы все уже сто раз наступали, блядь.
Вот смотри, чтобы твои функции не превращались в непролазное говно, которое потом тестировать — только волосы на жопе рвать, есть несколько простых, как три копейки, правил.
Первое — чистота, ёпта! Это когда твоя функция ведёт себя как примерный школьник: не гадит в глобальные переменные, не пишет тайком в базу данных, и её результат зависит только от того, что ты ей в рот сунул. Как калькулятор, блядь. Дал 2 и 2 — получи 4. Всегда. А не «сегодня 4, а завтра, потому что глобальная переменная is_friday поменялась, будет 5». Это пиздец, а не функция.
Второе — одна задача на рыло! Не надо делать из функции швейцарский нож, который и бутерброд намажет, и в космос слетает. Сделал одну чёткую вещь — и свободен. И тестировать её — раз плюнуть.
И третье, самое важное, блядь — явные зависимости! Это вообще святое. Если твоей функции для работы нужны данные или какой-нибудь сервис — ты их ей передаёшь, а не заставляешь её самой лезть в тёмный чулан мировой базы данных и там на ощупь искать. Этот подход умные дяди называют «инъекцией зависимостей», звучит страшно, а суть — проще пареной репы.
Давай на живых примерах, а то нихуя не понятно
Вот смотри, как делать НЕ НАДО (пиздец как не надо):
# Функция-одиночка, которая сама всё знает и умеет. И тестировать её — сплошное геморройное веселье.
def get_user_report():
db_connection = connect_to_database() # Опа, полезла в базу! А если её нет?
user_data = db_connection.fetch_user_data() # А если тут исключение?
# ...тут ещё наверняка куча сложной логики
return f"Report for {user_data['name']}"
Представляешь, чтобы проверить, правильно ли она имя в отчёт подставляет, тебе надо разворачивать целую базу данных, накатывать туда фикстуры… Да ебать её в сраку, проще новую функцию написать! Это же пиздопроебищно!
А вот как надо — красиво и правильно:
# Функция-интроверт. Ей похуй на весь мир. Дал данные — она отработала.
def generate_user_report(user_data: dict) -> str:
# ...вся та же сложная логика, но теперь она работает с тем, что дали
return f"Report for {user_data['name']}"
Вот это — красота! Ей вообще по барабану, откуда эти user_data взялись. Из базы, из файла, с потолка, из твоей больной фантазии — ей всё равно. Она свою работу сделала. И протестировать её — ебушки-воробушки!
Пример теста, который пишешь за две секунды и он просто работает
import unittest
def test_generate_user_report():
# Подсовываем функции фейковые, тестовые данные
mock_user = {"name": "Alice", "id": 1} # Вот тебе, функция, «Алиса»! Работай!
report = generate_user_report(mock_user)
# Проверяем, не обосралась ли она
assert "Report for Alice" in report
И всё! Никаких баз, никаких сетей, никакой магии. Всё под контролем. Такой подход позволяет подменять реальные, тяжёлые штуки (типа БД или внешних API) на лёгкие, как пух, заглушки (моки) и проверять только логику функции. А не то, как она там коннектится к чёрту на рога.
Короче, суть в чём: пиши функции так, будто завтра их будет тестировать твой злейший враг. И тогда даже он нихуя не придерется. Ну, почти.