HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Далеко не всегда результат работы функции связан с побочным эффектом, как это было ранее в курсе. Иногда побочный эффект - это просто дополнительное действие, которое скорее мешает протестировать основную логику. В этом уроке мы рассмотрим такие примеры и обсудим, как работать с такими случаями.</p>
1 <p>Далеко не всегда результат работы функции связан с побочным эффектом, как это было ранее в курсе. Иногда побочный эффект - это просто дополнительное действие, которое скорее мешает протестировать основную логику. В этом уроке мы рассмотрим такие примеры и обсудим, как работать с такими случаями.</p>
2 <h2>Как избежать побочных эффектов в тестировании</h2>
2 <h2>Как избежать побочных эффектов в тестировании</h2>
3 <p>Представьте себе функцию, которая регистрирует пользователя. Она создает запись в базе данных и отправляет приветственное письмо:</p>
3 <p>Представьте себе функцию, которая регистрирует пользователя. Она создает запись в базе данных и отправляет приветственное письмо:</p>
4 <p>Эта функция делает много всего, но главное, что нас волнует - правильная регистрация пользователя. Типичная регистрация сводится к добавлению в базу данных записи о новом пользователе. Именно это и нужно проверять - наличие новой записи в базе данных с правильно заполненными данными. А вот возврат функции нам никак не поможет.</p>
4 <p>Эта функция делает много всего, но главное, что нас волнует - правильная регистрация пользователя. Типичная регистрация сводится к добавлению в базу данных записи о новом пользователе. Именно это и нужно проверять - наличие новой записи в базе данных с правильно заполненными данными. А вот возврат функции нам никак не поможет.</p>
5 <p>Как правило, базу данных в тестах не прячут. В веб-фреймворках она доступна в тестовой среде и работает как обычно. Идемпотентность в ней достигается за счет транзакций. Перед тестом транзакция начинается и после теста откатывается. Благодаря этому, каждый тест запускается в идентичном окружении и не важно как он его меняет:</p>
5 <p>Как правило, базу данных в тестах не прячут. В веб-фреймворках она доступна в тестовой среде и работает как обычно. Идемпотентность в ней достигается за счет транзакций. Перед тестом транзакция начинается и после теста откатывается. Благодаря этому, каждый тест запускается в идентичном окружении и не важно как он его меняет:</p>
6 <p>А вот с отправкой писем все сложнее. Ее точно делать нельзя, но как этого добиться? Посмотрите, как может выглядеть функция регистрации пользователя:</p>
6 <p>А вот с отправкой писем все сложнее. Ее точно делать нельзя, но как этого добиться? Посмотрите, как может выглядеть функция регистрации пользователя:</p>
7 <p>Существует несколько подходов, позволяющих отключить отправку в тестах.</p>
7 <p>Существует несколько подходов, позволяющих отключить отправку в тестах.</p>
8 <p>Самый простой способ - переменная окружения, которая показывает среду выполнения:</p>
8 <p>Самый простой способ - переменная окружения, которая показывает среду выполнения:</p>
9 <p>Несмотря на простоту использования, такой подход считается плохой практикой. Формально, из-за него происходит нарушение абстракции - код начинает знать, где он выполняется. Со временем таких проверок становится все больше, код становится грязнее. Более того, если нам все же надо убедиться, что письмо отправляется с правильными данными, то мы не сможем этого сделать.</p>
9 <p>Несмотря на простоту использования, такой подход считается плохой практикой. Формально, из-за него происходит нарушение абстракции - код начинает знать, где он выполняется. Со временем таких проверок становится все больше, код становится грязнее. Более того, если нам все же надо убедиться, что письмо отправляется с правильными данными, то мы не сможем этого сделать.</p>
10 <p>Следующий способ - поддержка режима тестирования внутри самой библиотеки. Например, где-нибудь на этапе инициализации тестов можно сделать так:</p>
10 <p>Следующий способ - поддержка режима тестирования внутри самой библиотеки. Например, где-нибудь на этапе инициализации тестов можно сделать так:</p>
11 <p>Теперь в любом другом месте, где импортируется и используется функция send_email(), реальная отправка происходить не будет:</p>
11 <p>Теперь в любом другом месте, где импортируется и используется функция send_email(), реальная отправка происходить не будет:</p>
12 <p>Это довольно популярное решение в некоторых языках. Обычно информация о том, как правильно включить режим тестирования, находится в официальной документации конкретной библиотеки. Но что делать, если используемая библиотека не поддерживает режим тестирования?</p>
12 <p>Это довольно популярное решение в некоторых языках. Обычно информация о том, как правильно включить режим тестирования, находится в официальной документации конкретной библиотеки. Но что делать, если используемая библиотека не поддерживает режим тестирования?</p>
13 <p>Существует еще один, наиболее универсальный способ. Он основан на применении<strong>инверсии зависимостей</strong>. Сперва, программу можно изменить так, чтобы она вызывала функцию send_email() не напрямую, а принимала ее как параметр:</p>
13 <p>Существует еще один, наиболее универсальный способ. Он основан на применении<strong>инверсии зависимостей</strong>. Сперва, программу можно изменить так, чтобы она вызывала функцию send_email() не напрямую, а принимала ее как параметр:</p>
14 <p>Затем в тестах мы создадим заглушку, функцию, которую передадим вместо реальной:</p>
14 <p>Затем в тестах мы создадим заглушку, функцию, которую передадим вместо реальной:</p>
15 <p>Такой способ сложнее в реализации, особенно если функция, требующая подмены, находится глубоко в стеке вызовов. Это значит, что придется прокидывать нужные зависимости через всю цепочку функций сверху вниз. И как результат переписывать и менять интерфейс всех функций на пути. Самих зависимостей может быть много, и чем больше используется инверсия, тем сложнее код. За гибкость приходится платить.</p>
15 <p>Такой способ сложнее в реализации, особенно если функция, требующая подмены, находится глубоко в стеке вызовов. Это значит, что придется прокидывать нужные зависимости через всю цепочку функций сверху вниз. И как результат переписывать и менять интерфейс всех функций на пути. Самих зависимостей может быть много, и чем больше используется инверсия, тем сложнее код. За гибкость приходится платить.</p>
16 <p>Теперь обсудим плюсы. Ни библиотека, ни код ничего не знают про тесты. Этот способ наиболее гибкий, он позволяет задавать конкретное поведение для конкретной ситуации. В некоторых экосистемах существуют системы инверсии зависимостей, как<a>контейнер зависимостей</a>, которые определяют процесс сборки приложения. Особенно в мире PHP, Java и C#.</p>
16 <p>Теперь обсудим плюсы. Ни библиотека, ни код ничего не знают про тесты. Этот способ наиболее гибкий, он позволяет задавать конкретное поведение для конкретной ситуации. В некоторых экосистемах существуют системы инверсии зависимостей, как<a>контейнер зависимостей</a>, которые определяют процесс сборки приложения. Особенно в мире PHP, Java и C#.</p>
17 <p>Кстати, pytest-фикстуры это пример контейнера зависимостей в экосистеме Python. Для использования фикстуры в тесте, она указывается в параметрах во время определения теста. А уже во время запуска тестов pytest самостоятельно выполняет фикстуры и прокидывает их в нужные нам функции.</p>
17 <p>Кстати, pytest-фикстуры это пример контейнера зависимостей в экосистеме Python. Для использования фикстуры в тесте, она указывается в параметрах во время определения теста. А уже во время запуска тестов pytest самостоятельно выполняет фикстуры и прокидывает их в нужные нам функции.</p>