HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Какую главную задачу должны решать тесты? Этот вопрос невероятно важен. Ответ на него дает понимание, как правильно писать тесты. Именно на этот вопрос мы ответим в этом уроке.</p>
1 <p>Какую главную задачу должны решать тесты? Этот вопрос невероятно важен. Ответ на него дает понимание, как правильно писать тесты. Именно на этот вопрос мы ответим в этом уроке.</p>
2 <p>Представьте, что вы написали функцию capitalize(text), которая делает заглавной первую букву переданной строки:</p>
2 <p>Представьте, что вы написали функцию capitalize(text), которая делает заглавной первую букву переданной строки:</p>
3 <p>Вот один из вариантов ее реализации:</p>
3 <p>Вот один из вариантов ее реализации:</p>
4 <p>Мы создали функцию - дальше нужно проверить, как она работает. Например, можно открыть REPL и вызвать функцию с разными аргументами:</p>
4 <p>Мы создали функцию - дальше нужно проверить, как она работает. Например, можно открыть REPL и вызвать функцию с разными аргументами:</p>
5 <p>Мы убедились, что функция работает - по крайней мере для тех аргументов, которые мы передали в нее. Если во время проверки заметили ошибки, то исправляем функцию и повторяем все заново.</p>
5 <p>Мы убедились, что функция работает - по крайней мере для тех аргументов, которые мы передали в нее. Если во время проверки заметили ошибки, то исправляем функцию и повторяем все заново.</p>
6 <p>Фактически, весь этот процесс - это<strong>ручное тестирование</strong>. Его задача - убедиться, что код работает как надо. И нам совершенно без разницы, как конкретно реализована эта функция.</p>
6 <p>Фактически, весь этот процесс - это<strong>ручное тестирование</strong>. Его задача - убедиться, что код работает как надо. И нам совершенно без разницы, как конкретно реализована эта функция.</p>
7 <p>Это и есть ответ на вопрос, заданный в начале урока: тесты проверяют, что код или приложение работает корректно, при этом не имеет значения, как конкретно написан этот код.</p>
7 <p>Это и есть ответ на вопрос, заданный в начале урока: тесты проверяют, что код или приложение работает корректно, при этом не имеет значения, как конкретно написан этот код.</p>
8 <h2>Как работают автоматические тесты</h2>
8 <h2>Как работают автоматические тесты</h2>
9 <p>Все, что требуется от автоматических тестов - повторить проверки, которые мы выполняли во время ручного тестирования. Для этого достаточно старого доброго if и исключений. Даже если вы не знакомы с исключениями, ничего страшного. В этом курсе нам хватит двух вещей: для чего нужны исключения и какой у них синтаксис.</p>
9 <p>Все, что требуется от автоматических тестов - повторить проверки, которые мы выполняли во время ручного тестирования. Для этого достаточно старого доброго if и исключений. Даже если вы не знакомы с исключениями, ничего страшного. В этом курсе нам хватит двух вещей: для чего нужны исключения и какой у них синтаксис.</p>
10 <p>До сих пор в курсах Хекслета вы встречались с ошибками, которые возникают непроизвольно: вызов несуществующей функции, обращение к несуществующей константе и так далее. Но ошибки можно порождать самостоятельно с помощью исключений, что необходимо для нашей ситуации. Ошибки порождаются с помощью механизма исключений:</p>
10 <p>До сих пор в курсах Хекслета вы встречались с ошибками, которые возникают непроизвольно: вызов несуществующей функции, обращение к несуществующей константе и так далее. Но ошибки можно порождать самостоятельно с помощью исключений, что необходимо для нашей ситуации. Ошибки порождаются с помощью механизма исключений:</p>
11 <p>А теперь рассмотрим пример теста:</p>
11 <p>А теперь рассмотрим пример теста:</p>
12 <p>Из примера выше видно, что тесты - это точно такой же код, как и любой другой. Он работает в том же окружении и подчиняется тем же правилам и стандартам кодирования. А еще он может содержать ошибки, но это не значит, что надо писать тесты на тесты. Избежать всех ошибок невозможно, да и не нужно - иначе стоимость разработки стала бы неоправданно высокой.</p>
12 <p>Из примера выше видно, что тесты - это точно такой же код, как и любой другой. Он работает в том же окружении и подчиняется тем же правилам и стандартам кодирования. А еще он может содержать ошибки, но это не значит, что надо писать тесты на тесты. Избежать всех ошибок невозможно, да и не нужно - иначе стоимость разработки стала бы неоправданно высокой.</p>
13 <p>Обычно тесты складывают в специальную директорию в корне проекта. Чаще всего она называется<em>tests</em>, хотя встречаются и другие варианты:</p>
13 <p>Обычно тесты складывают в специальную директорию в корне проекта. Чаще всего она называется<em>tests</em>, хотя встречаются и другие варианты:</p>
14 <p>Обычно структура этой директории отражает структуру исходного кода. Например, если наша функция capitalize(text) определена в файле<em>src/capitalize.py</em>, то ее тест лучше поместить в файл<em>tests/test_capitalize.py</em>.</p>
14 <p>Обычно структура этой директории отражает структуру исходного кода. Например, если наша функция capitalize(text) определена в файле<em>src/capitalize.py</em>, то ее тест лучше поместить в файл<em>tests/test_capitalize.py</em>.</p>
15 <p>Представим, что дальше мы будем вносить изменения в код, связанный с этой функцией. В таком случае важно не забывать запускать тесты:</p>
15 <p>Представим, что дальше мы будем вносить изменения в код, связанный с этой функцией. В таком случае важно не забывать запускать тесты:</p>
16 <h2>Как пишутся тесты</h2>
16 <h2>Как пишутся тесты</h2>
17 <p>Разработчики самостоятельно импортируют тестируемые функции, вызывают их с необходимыми аргументами и проверяют, что функции возвращают ожидаемые значения. Представим, что мы отредактировали код и поменяли<strong>сигнатуру функции</strong>- входные или выходные параметры, ее имя. В таком случае придется переписывать тесты.</p>
17 <p>Разработчики самостоятельно импортируют тестируемые функции, вызывают их с необходимыми аргументами и проверяют, что функции возвращают ожидаемые значения. Представим, что мы отредактировали код и поменяли<strong>сигнатуру функции</strong>- входные или выходные параметры, ее имя. В таком случае придется переписывать тесты.</p>
18 <p>А теперь рассмотрим другой пример. Тесты должны продолжать работать без изменений, если сигнатура осталась той же, но поменялись внутренности функции. Так происходит в этом коде:</p>
18 <p>А теперь рассмотрим другой пример. Тесты должны продолжать работать без изменений, если сигнатура осталась той же, но поменялись внутренности функции. Так происходит в этом коде:</p>
19 <p>Хорошие тесты ничего не знают о внутреннем устройстве проверяемого кода. Это делает их более универсальными и надежными.</p>
19 <p>Хорошие тесты ничего не знают о внутреннем устройстве проверяемого кода. Это делает их более универсальными и надежными.</p>
20 <h2>Сколько проверок нужно писать</h2>
20 <h2>Сколько проверок нужно писать</h2>
21 <p>Нельзя написать тесты, которые гарантируют стопроцентную работоспособность кода. Для этого потребовалось бы проверить все возможные аргументы, а это физически невозможно. С другой стороны, без тестов вообще нет никаких гарантий, что программа сработает.</p>
21 <p>Нельзя написать тесты, которые гарантируют стопроцентную работоспособность кода. Для этого потребовалось бы проверить все возможные аргументы, а это физически невозможно. С другой стороны, без тестов вообще нет никаких гарантий, что программа сработает.</p>
22 <p>В работе с тестами нужно ориентироваться на разнообразие входных данных, ведь у любой функции не так много основных сценариев использования.</p>
22 <p>В работе с тестами нужно ориентироваться на разнообразие входных данных, ведь у любой функции не так много основных сценариев использования.</p>
23 <p>Например, в случае capitalize() можно взять любое слово и написать одну проверку. Такой тест покроет большую часть возможных сценариев.</p>
23 <p>Например, в случае capitalize() можно взять любое слово и написать одну проверку. Такой тест покроет большую часть возможных сценариев.</p>
24 <p>Дальше мы смотрим на<strong>пограничные случаи</strong>- ситуации, в которых код может вести себя по-особенному:</p>
24 <p>Дальше мы смотрим на<strong>пограничные случаи</strong>- ситуации, в которых код может вести себя по-особенному:</p>
25 <ul><li>Работа с пустой строкой</li>
25 <ul><li>Работа с пустой строкой</li>
26 <li>Обработка None</li>
26 <li>Обработка None</li>
27 <li>Деление на ноль (в большинстве языков вызывает ошибку)</li>
27 <li>Деление на ноль (в большинстве языков вызывает ошибку)</li>
28 <li>Специфические ситуации для конкретных алгоритмов</li>
28 <li>Специфические ситуации для конкретных алгоритмов</li>
29 </ul><p>Для функции capitalize() пограничным случаем будет пустая строка:</p>
29 </ul><p>Для функции capitalize() пограничным случаем будет пустая строка:</p>
30 <p>Добавив тест на пустую строку, мы увидим, что вызов функции capitalize() завершается с ошибкой. Внутри нее идет обращение к первому индексу строки без проверки его существования. Нужно исправить код таким образом:</p>
30 <p>Добавив тест на пустую строку, мы увидим, что вызов функции capitalize() завершается с ошибкой. Внутри нее идет обращение к первому индексу строки без проверки его существования. Нужно исправить код таким образом:</p>
31 <p>В большом числе ситуаций пограничные случаи требуют отдельной обработки, наличия условных конструкций. Тесты должны быть построены так, чтобы они затрагивали каждую такую конструкцию.</p>
31 <p>В большом числе ситуаций пограничные случаи требуют отдельной обработки, наличия условных конструкций. Тесты должны быть построены так, чтобы они затрагивали каждую такую конструкцию.</p>
32 <p>Не забывайте, что условные конструкции могут порождать неочевидные связи. Например, два независимых условных блока порождают четыре возможных сценария:</p>
32 <p>Не забывайте, что условные конструкции могут порождать неочевидные связи. Например, два независимых условных блока порождают четыре возможных сценария:</p>
33 <ul><li>Функция выполнилась так, что не был выполнен ни один условный блок</li>
33 <ul><li>Функция выполнилась так, что не был выполнен ни один условный блок</li>
34 <li>Функция выполнилась так, что был выполнен только первый условный блок</li>
34 <li>Функция выполнилась так, что был выполнен только первый условный блок</li>
35 <li>Функция выполнилась так, что был выполнен только второй условный блок</li>
35 <li>Функция выполнилась так, что был выполнен только второй условный блок</li>
36 <li>Функция выполнилась так, что были выполнены оба условных блока</li>
36 <li>Функция выполнилась так, что были выполнены оба условных блока</li>
37 </ul><p>Комбинация всех возможных вариантов поведения функции называется<strong>цикломатической сложностью</strong>. Это число, которое показывает все возможные пути кода внутри функции. Цикломатическая сложность - хороший ориентир для понимания того, сколько и какие тесты нужно написать.</p>
37 </ul><p>Комбинация всех возможных вариантов поведения функции называется<strong>цикломатической сложностью</strong>. Это число, которое показывает все возможные пути кода внутри функции. Цикломатическая сложность - хороший ориентир для понимания того, сколько и какие тесты нужно написать.</p>
38 <p>Иногда пограничные случаи не связаны с условными конструкциями. Особенно часто такие ситуации встречаются там, где есть вычисления границ слов или массивов. Такой код может работать в большинстве ситуаций, и только в некоторых может давать сбой:</p>
38 <p>Иногда пограничные случаи не связаны с условными конструкциями. Особенно часто такие ситуации встречаются там, где есть вычисления границ слов или массивов. Такой код может работать в большинстве ситуаций, и только в некоторых может давать сбой:</p>
39 <h2>Как проверять входные данные</h2>
39 <h2>Как проверять входные данные</h2>
40 <p>Отдельно рассмотрим ошибки типов входных данных. Например, в функцию capitalize() можно передать число вместо строки. Как она должна себя вести в таком случае? Нужно ли писать такой тест?</p>
40 <p>Отдельно рассмотрим ошибки типов входных данных. Например, в функцию capitalize() можно передать число вместо строки. Как она должна себя вести в таком случае? Нужно ли писать такой тест?</p>
41 <p>Еще один интересный вопрос: нужно ли внутри capitalize() обрабатывать такие ситуации? Не стоит этого делать, ведь пользы от этого будет мало. Все равно должны быть тесты, которые проверяют, что система работает в целом, а они обычно выявляют проблемы кода на более нижних уровнях.</p>
41 <p>Еще один интересный вопрос: нужно ли внутри capitalize() обрабатывать такие ситуации? Не стоит этого делать, ведь пользы от этого будет мало. Все равно должны быть тесты, которые проверяют, что система работает в целом, а они обычно выявляют проблемы кода на более нижних уровнях.</p>
42 <p>Ответственность за передачу правильных данных в функцию capitalize() лежит не на самой функции, а на коде, который вызывает эту функцию. И если он хорошо протестирован, то подобная ошибка либо обнаружится, либо вообще не возникнет.</p>
42 <p>Ответственность за передачу правильных данных в функцию capitalize() лежит не на самой функции, а на коде, который вызывает эту функцию. И если он хорошо протестирован, то подобная ошибка либо обнаружится, либо вообще не возникнет.</p>
43 <p>Но даже если ошибка обрабатывается внутри функции, не надо пытаться написать тесты, покрывающие каждую ошибку. Это выливается в огромное число тестов, которые требуют поддержки и времени на написание. Нужно уметь вовремя остановиться и двигаться дальше, к покрытию другого кода.</p>
43 <p>Но даже если ошибка обрабатывается внутри функции, не надо пытаться написать тесты, покрывающие каждую ошибку. Это выливается в огромное число тестов, которые требуют поддержки и времени на написание. Нужно уметь вовремя остановиться и двигаться дальше, к покрытию другого кода.</p>
44 <h2>Выводы</h2>
44 <h2>Выводы</h2>
45 <p>В этом уроке мы ближе познакомились с автоматическими тестами. В итоге мы получили такую структуру директорий:</p>
45 <p>В этом уроке мы ближе познакомились с автоматическими тестами. В итоге мы получили такую структуру директорий:</p>
46 <p>Сам тест будет выглядеть так:</p>
46 <p>Сам тест будет выглядеть так:</p>
47 <p>Этот тест запускается такой командой:</p>
47 <p>Этот тест запускается такой командой:</p>
48 <p>Если все написано правильно, то запуск тестов завершится с выводом строки "Все тесты пройдены!". Если в тестах или в коде есть ошибка, то сработает исключение и мы увидим сообщение, указывающее на это.</p>
48 <p>Если все написано правильно, то запуск тестов завершится с выводом строки "Все тесты пройдены!". Если в тестах или в коде есть ошибка, то сработает исключение и мы увидим сообщение, указывающее на это.</p>