HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#Руководства</a></p>
1 <p><a>#Руководства</a></p>
2 <ul><li>11 май 2023</li>
2 <ul><li>11 май 2023</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Гайд по самому популярному среди Python-разработчиков фреймворку для тестирования.</p>
4 </ul><p>Гайд по самому популярному среди Python-разработчиков фреймворку для тестирования.</p>
5 <p>Иллюстрация: Катя Павловская для Skillbox Media</p>
5 <p>Иллюстрация: Катя Павловская для Skillbox Media</p>
6 <p>Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.</p>
6 <p>Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.</p>
7 <p>Написать рабочий код - полдела: нужно добиться, чтобы он выдавал корректные результаты.</p>
7 <p>Написать рабочий код - полдела: нужно добиться, чтобы он выдавал корректные результаты.</p>
8 <p>Можно тестировать программу вручную: запускать её вновь и вновь в разных условиях и проверять, правильно ли всё работает. Но лучше, конечно, автоматизировать процесс и написать код, который будет проверять другой код. Чтобы упростить эту задачу, и придумали Pytest.</p>
8 <p>Можно тестировать программу вручную: запускать её вновь и вновь в разных условиях и проверять, правильно ли всё работает. Но лучше, конечно, автоматизировать процесс и написать код, который будет проверять другой код. Чтобы упростить эту задачу, и придумали Pytest.</p>
9 <p>Из этой статьи вы узнаете:</p>
9 <p>Из этой статьи вы узнаете:</p>
10 <ul><li><a>Что такое Pytest</a></li>
10 <ul><li><a>Что такое Pytest</a></li>
11 <li><a>Какие у него преимущества и недостатки</a></li>
11 <li><a>Какие у него преимущества и недостатки</a></li>
12 <li><a>Как установить Pytest</a></li>
12 <li><a>Как установить Pytest</a></li>
13 <li><a>Как писать тесты</a></li>
13 <li><a>Как писать тесты</a></li>
14 <li><a>Что такое фикстуры в Pytest</a></li>
14 <li><a>Что такое фикстуры в Pytest</a></li>
15 <li><a>Какие в Pytest есть тестовые метки</a></li>
15 <li><a>Какие в Pytest есть тестовые метки</a></li>
16 </ul><p><strong>Pytest</strong> - это фреймворк для тестирования кода на Python. Он был разработан в 2004 году, но до сих пор регулярно обновляется и позволяет не только писать тесты, но и создавать для них окружение, а также настраивать параметры запуска.</p>
16 </ul><p><strong>Pytest</strong> - это фреймворк для тестирования кода на Python. Он был разработан в 2004 году, но до сих пор регулярно обновляется и позволяет не только писать тесты, но и создавать для них окружение, а также настраивать параметры запуска.</p>
17 <p>Согласно<a>исследованию JetBrains</a>, Pytest использует каждый второй питонист.</p>
17 <p>Согласно<a>исследованию JetBrains</a>, Pytest использует каждый второй питонист.</p>
18 Популярность фреймворков для тестирования (по статистике<a>JetBrains</a>)<em>Инфографика: Skillbox Media</em><p>Успешность Pytest в сравнении с конкурентами (например, Unittest) легко объясняется его<strong>преимуществами</strong>:</p>
18 Популярность фреймворков для тестирования (по статистике<a>JetBrains</a>)<em>Инфографика: Skillbox Media</em><p>Успешность Pytest в сравнении с конкурентами (например, Unittest) легко объясняется его<strong>преимуществами</strong>:</p>
19 <ul><li><strong>Лаконичный код.</strong>В синтаксисе Pytest нет громоздких конструкций, как в том же Unittest. Простой тест может состоять всего из двух строк.</li>
19 <ul><li><strong>Лаконичный код.</strong>В синтаксисе Pytest нет громоздких конструкций, как в том же Unittest. Простой тест может состоять всего из двух строк.</li>
20 <li><strong>Подробные отчёты об ошибках.</strong>Если тест работает неправильно, Pytest сам объяснит, в чём дело.</li>
20 <li><strong>Подробные отчёты об ошибках.</strong>Если тест работает неправильно, Pytest сам объяснит, в чём дело.</li>
21 <li><strong>Универсальный оператор</strong><strong>assert</strong><strong>.</strong>Не нужно запоминать разные его виды, как в Unittest.</li>
21 <li><strong>Универсальный оператор</strong><strong>assert</strong><strong>.</strong>Не нужно запоминать разные его виды, как в Unittest.</li>
22 <li><strong>Фикстуры.</strong>Позволяют создавать контекст сразу для группы тестов.</li>
22 <li><strong>Фикстуры.</strong>Позволяют создавать контекст сразу для группы тестов.</li>
23 <li><strong>Метки.</strong>Можно настраивать поведение тестов: задавать условия запуска, передавать одному и тому же тесту разные входные данные и так далее.</li>
23 <li><strong>Метки.</strong>Можно настраивать поведение тестов: задавать условия запуска, передавать одному и тому же тесту разные входные данные и так далее.</li>
24 <li><strong>Умеет запускать тесты других фреймворков.</strong>С Pytest совместимы Unittest, Doctest и Nose.</li>
24 <li><strong>Умеет запускать тесты других фреймворков.</strong>С Pytest совместимы Unittest, Doctest и Nose.</li>
25 <li><strong>Множество плагинов.</strong>Если какой-то функции "из коробки" не хватает, для Pytest написано больше тысячи плагинов. Причём 180 из них обновлялись в 2023 году, ещё 360 - в 2022-м.</li>
25 <li><strong>Множество плагинов.</strong>Если какой-то функции "из коробки" не хватает, для Pytest написано больше тысячи плагинов. Причём 180 из них обновлялись в 2023 году, ещё 360 - в 2022-м.</li>
26 </ul><p>Тем не менее<strong>недостатки</strong>у фреймворка тоже есть:</p>
26 </ul><p>Тем не менее<strong>недостатки</strong>у фреймворка тоже есть:</p>
27 <ul><li><strong>Неявность и магия.</strong>Обратная сторона простоты и лаконичности есть: многие процессы происходят "под капотом". Чтобы разобраться в них детально, придётся штудировать документацию.</li>
27 <ul><li><strong>Неявность и магия.</strong>Обратная сторона простоты и лаконичности есть: многие процессы происходят "под капотом". Чтобы разобраться в них детально, придётся штудировать документацию.</li>
28 <li><strong>Не входит в стандартную библиотеку.</strong>Pytest нужно устанавливать отдельно. Если у вас старая (ниже 3.7) версия Python, то нужно будет подключать соответствующую версию фреймворка. Найти их список можно<a>здесь</a>.</li>
28 <li><strong>Не входит в стандартную библиотеку.</strong>Pytest нужно устанавливать отдельно. Если у вас старая (ниже 3.7) версия Python, то нужно будет подключать соответствующую версию фреймворка. Найти их список можно<a>здесь</a>.</li>
29 <li><strong>Другие фреймворки несовместимы с Pytest.</strong>Неизбежное следствие лидерского, практически королевского, статуса:<strong></strong>Pytest может запускать тесты других фреймворков, но ни один из других фреймворков не может запускать тесты Pytest. Вот такая современная трактовка древнеримской пословицы "Что позволено Юпитеру, не позволено быку".</li>
29 <li><strong>Другие фреймворки несовместимы с Pytest.</strong>Неизбежное следствие лидерского, практически королевского, статуса:<strong></strong>Pytest может запускать тесты других фреймворков, но ни один из других фреймворков не может запускать тесты Pytest. Вот такая современная трактовка древнеримской пословицы "Что позволено Юпитеру, не позволено быку".</li>
30 </ul><p>Pytest входит в большинство пакетов Python. Его последняя версия доступна для Python 3.7+ и PyPy 3. Чтобы установить его в свою виртуальную среду, используйте команду:</p>
30 </ul><p>Pytest входит в большинство пакетов Python. Его последняя версия доступна для Python 3.7+ и PyPy 3. Чтобы установить его в свою виртуальную среду, используйте команду:</p>
31 <p>pip install -U pytest</p>
31 <p>pip install -U pytest</p>
32 <p>Другой способ - пакетный менеджер вашей IDE. Найдите в нём модуль с названием pytest и загрузите его.</p>
32 <p>Другой способ - пакетный менеджер вашей IDE. Найдите в нём модуль с названием pytest и загрузите его.</p>
33 <p>Для начала нужен код, который мы будем тестировать. Создадим файл main.py и функцию sum2. Она будет принимать на вход два аргумента и возвращать их сумму:</p>
33 <p>Для начала нужен код, который мы будем тестировать. Создадим файл main.py и функцию sum2. Она будет принимать на вход два аргумента и возвращать их сумму:</p>
34 def sum2(x, y): return x + y<p>Теперь проверим, корректно ли она работает. Для этого создадим файл tests.py, импортируем в него sum2 и напишем test_sum2:</p>
34 def sum2(x, y): return x + y<p>Теперь проверим, корректно ли она работает. Для этого создадим файл tests.py, импортируем в него sum2 и напишем test_sum2:</p>
35 from main import sum2 def test_sum2(): assert sum2(15, 8) == 23<p>Чтобы запустить тесты, введём в консоль команду pytest. Альтернативный вариант - использовать интерфейс вашей IDE. Например,<a>PyCharm</a>позволяет запустить файл целиком или тестовую функцию в отдельности.</p>
35 from main import sum2 def test_sum2(): assert sum2(15, 8) == 23<p>Чтобы запустить тесты, введём в консоль команду pytest. Альтернативный вариант - использовать интерфейс вашей IDE. Например,<a>PyCharm</a>позволяет запустить файл целиком или тестовую функцию в отдельности.</p>
36 <p>Получаем вот такой результат:</p>
36 <p>Получаем вот такой результат:</p>
37 tests.py::test_sum2 PASSED [100%]<p>Теперь изменим наш тест: пусть он ожидает получить не 23, а 0:</p>
37 tests.py::test_sum2 PASSED [100%]<p>Теперь изменим наш тест: пусть он ожидает получить не 23, а 0:</p>
38 def test_sum2(): assert sum2(15, 8) == 0<p>Получим сообщение о том, что тест не пройден:</p>
38 def test_sum2(): assert sum2(15, 8) == 0<p>Получим сообщение о том, что тест не пройден:</p>
39 FAILED [100%] tests.py:3 (test_sum2) 23 != 0 Expected :0 Actual :23 &lt;Click to see difference&gt; def test_sum2(): &gt; assert sum2(15, 8) == 0 E assert 23 == 0 E + where 23 = sum2(15, 8) tests.py:5: AssertionError<p>Чтобы Pytest воспринимал функции тестовыми, файлы и сами тесты должны быть названы определённым образом:</p>
39 FAILED [100%] tests.py:3 (test_sum2) 23 != 0 Expected :0 Actual :23 &lt;Click to see difference&gt; def test_sum2(): &gt; assert sum2(15, 8) == 0 E assert 23 == 0 E + where 23 = sum2(15, 8) tests.py:5: AssertionError<p>Чтобы Pytest воспринимал функции тестовыми, файлы и сами тесты должны быть названы определённым образом:</p>
40 <ul><li><strong>название файла</strong>должно начинаться на test или заканчиваться на test.py;</li>
40 <ul><li><strong>название файла</strong>должно начинаться на test или заканчиваться на test.py;</li>
41 <li><strong>название функции</strong>должно быть написано в нижнем регистре и начинаться с test_.</li>
41 <li><strong>название функции</strong>должно быть написано в нижнем регистре и начинаться с test_.</li>
42 </ul><p>Ключевому слову assert можно передать любое условие. Если оно правдиво (результат True) - тест пройден, если ложно (результат False) - не пройден.</p>
42 </ul><p>Ключевому слову assert можно передать любое условие. Если оно правдиво (результат True) - тест пройден, если ложно (результат False) - не пройден.</p>
43 <p>Таким образом можно писать минимальные тесты:</p>
43 <p>Таким образом можно писать минимальные тесты:</p>
44 def test_true(): assert True def test_false(): assert False<p>Результат:</p>
44 def test_true(): assert True def test_false(): assert False<p>Результат:</p>
45 tests.py::test_true PASSED [50%] tests.py::test_false FAILED [100%] tests.py:9 (test_false) def test_false(): &gt; assert False E assert False tests.py:11: AssertionError<p>Через запятую после условия можно написать отладочное сообщение. Pytest выведет его, если тест провалится:</p>
45 tests.py::test_true PASSED [50%] tests.py::test_false FAILED [100%] tests.py:9 (test_false) def test_false(): &gt; assert False E assert False tests.py:11: AssertionError<p>Через запятую после условия можно написать отладочное сообщение. Pytest выведет его, если тест провалится:</p>
46 def test_message(): assert False, 'Тест всегда провален'<p>Результат:</p>
46 def test_message(): assert False, 'Тест всегда провален'<p>Результат:</p>
47 tests.py::test_message FAILED [100%] tests.py:0 (test_message) def test_message(): &gt; assert False, 'Тест всегда провален' E AssertionError: Тест всегда провален E assert False tests.py:2: AssertionError<p>Если в тесте нет assert, он считается пройденным:</p>
47 tests.py::test_message FAILED [100%] tests.py:0 (test_message) def test_message(): &gt; assert False, 'Тест всегда провален' E AssertionError: Тест всегда провален E assert False tests.py:2: AssertionError<p>Если в тесте нет assert, он считается пройденным:</p>
48 def test_pass(): pass # оператор-заглушка, не делает ничего<p>Результат:</p>
48 def test_pass(): pass # оператор-заглушка, не делает ничего<p>Результат:</p>
49 tests.py::test_pass PASSED [100%]<p><strong>Примечание</strong></p>
49 tests.py::test_pass PASSED [100%]<p><strong>Примечание</strong></p>
50 <p>В одном тесте может быть сразу несколько операторов assert, но делать так мы не рекомендуем. Лучше руководствоваться правилом "Один тест - одна сущность, одна функция - один assert".</p>
50 <p>В одном тесте может быть сразу несколько операторов assert, но делать так мы не рекомендуем. Лучше руководствоваться правилом "Один тест - одна сущность, одна функция - один assert".</p>
51 <p>Команда терминала pytest запускает все тесты текущего каталога. Чтобы управлять условиями запуска, укажите после неё путь до файла или даже отдельной функции.</p>
51 <p>Команда терминала pytest запускает все тесты текущего каталога. Чтобы управлять условиями запуска, укажите после неё путь до файла или даже отдельной функции.</p>
52 <ul><li>Команда для запуска файла tests.py: pytest tests.py.</li>
52 <ul><li>Команда для запуска файла tests.py: pytest tests.py.</li>
53 <li>Команда для запуска функции test_sum2 и только её: pytest tests.py: test_sum2.</li>
53 <li>Команда для запуска функции test_sum2 и только её: pytest tests.py: test_sum2.</li>
54 </ul><p>Для более гибкого запуска можно дополнительно добавлять флаги. Их список есть в <a>документации</a>Pytest.</p>
54 </ul><p>Для более гибкого запуска можно дополнительно добавлять флаги. Их список есть в <a>документации</a>Pytest.</p>
55 <p>Помимо команд терминала можно использовать графический интерфейс вашей IDE. Описанные в этой статье тесты мы запускаем через инструменты<a>PyCharm</a>.</p>
55 <p>Помимо команд терминала можно использовать графический интерфейс вашей IDE. Описанные в этой статье тесты мы запускаем через инструменты<a>PyCharm</a>.</p>
56 <p>Фикстуры - это функции, которые создают окружение вокруг тестов. Они удобны, когда нужно передать одни и те же входные данные нескольким тестам.</p>
56 <p>Фикстуры - это функции, которые создают окружение вокруг тестов. Они удобны, когда нужно передать одни и те же входные данные нескольким тестам.</p>
57 <p>Допустим, у нас есть несколько функций в main.py:</p>
57 <p>Допустим, у нас есть несколько функций в main.py:</p>
58 # прибавляет 2 к каждому элементу коллекции def plus2(nums): result = [] for num in nums: result.append(num + 2) return result # умножает на 2 каждый элемент коллекции def multiply2(nums): result = [] for num in nums: result.append(num * 2) return result # возводит в степень 2 каждый элемент коллекции def exponent2(nums): result = [] for num in nums: result.append(num ** 2) return result<p>Напишем для каждой из них по тесту в файле tests.py. В качестве тестового<a>массива</a>возьмём список простых чисел от 1 до 50. Создавать его будем с помощью<a>цикла for-else</a>:</p>
58 # прибавляет 2 к каждому элементу коллекции def plus2(nums): result = [] for num in nums: result.append(num + 2) return result # умножает на 2 каждый элемент коллекции def multiply2(nums): result = [] for num in nums: result.append(num * 2) return result # возводит в степень 2 каждый элемент коллекции def exponent2(nums): result = [] for num in nums: result.append(num ** 2) return result<p>Напишем для каждой из них по тесту в файле tests.py. В качестве тестового<a>массива</a>возьмём список простых чисел от 1 до 50. Создавать его будем с помощью<a>цикла for-else</a>:</p>
59 from main import * def test_plus2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert plus2(prime_nums) == [3, 4, 5, 7, 9, 13, 15, 19, 21, 25, 31, 33, 39, 43, 45, 49] def test_multiply2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert multiply2(prime_nums) == [2, 4, 6, 10, 14, 22, 26, 34, 38, 46, 58, 62, 74, 82, 86, 94] def test_exponent2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert exponent2(prime_nums) == [1, 4, 9, 25, 49, 121, 169, 289, 361, 529, 841, 961, 1369, 1681, 1849, 2209]<p>Пока во всех тестовых функциях мы используем одну и ту же громоздкую конструкцию, создающую список простых чисел. Вынесем её в отдельную фикстуру. Для этого явно импортируем модуль pytest:</p>
59 from main import * def test_plus2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert plus2(prime_nums) == [3, 4, 5, 7, 9, 13, 15, 19, 21, 25, 31, 33, 39, 43, 45, 49] def test_multiply2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert multiply2(prime_nums) == [2, 4, 6, 10, 14, 22, 26, 34, 38, 46, 58, 62, 74, 82, 86, 94] def test_exponent2(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) assert exponent2(prime_nums) == [1, 4, 9, 25, 49, 121, 169, 289, 361, 529, 841, 961, 1369, 1681, 1849, 2209]<p>Пока во всех тестовых функциях мы используем одну и ту же громоздкую конструкцию, создающую список простых чисел. Вынесем её в отдельную фикстуру. Для этого явно импортируем модуль pytest:</p>
60 import pytest<p>Чтобы объявить функцию фикстурой, используем перед ней декоратор @pytest.fixture():</p>
60 import pytest<p>Чтобы объявить функцию фикстурой, используем перед ней декоратор @pytest.fixture():</p>
61 @pytest.fixture() def get_prime_nums(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) return prime_nums<p>Теперь передадим эту фикстуру во все тесты, где она нужна. Обращаясь к фикстуре, у неё не нужно писать круглые скобки: как будто это не функция, а переменная.</p>
61 @pytest.fixture() def get_prime_nums(): prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) return prime_nums<p>Теперь передадим эту фикстуру во все тесты, где она нужна. Обращаясь к фикстуре, у неё не нужно писать круглые скобки: как будто это не функция, а переменная.</p>
62 <p>Сами тесты в итоге получаются такие:</p>
62 <p>Сами тесты в итоге получаются такие:</p>
63 def test_plus2(get_prime_nums): prime_nums = get_prime_nums assert plus2(prime_nums) == [3, 4, 5, 7, 9, 13, 15, 19, 21, 25, 31, 33, 39, 43, 45, 49] def test_multiply2(get_prime_nums): prime_nums = get_prime_nums assert multiply2(prime_nums) == [2, 4, 6, 10, 14, 22, 26, 34, 38, 46, 58, 62, 74, 82, 86, 94] def test_exponent2(get_prime_nums): prime_nums = get_prime_nums assert exponent2(prime_nums) == [1, 4, 9, 25, 49, 121, 169, 289, 361, 529, 841, 961, 1369, 1681, 1849, 2209]<p>Если хотите, чтобы после запуска теста выполнялся ещё какой-то скрипт, это также можно сделать через фикстуры. Для этого вместо ключевого слова return используйте yield. Код, написанный после yield, и будет выполняться по завершении теста.</p>
63 def test_plus2(get_prime_nums): prime_nums = get_prime_nums assert plus2(prime_nums) == [3, 4, 5, 7, 9, 13, 15, 19, 21, 25, 31, 33, 39, 43, 45, 49] def test_multiply2(get_prime_nums): prime_nums = get_prime_nums assert multiply2(prime_nums) == [2, 4, 6, 10, 14, 22, 26, 34, 38, 46, 58, 62, 74, 82, 86, 94] def test_exponent2(get_prime_nums): prime_nums = get_prime_nums assert exponent2(prime_nums) == [1, 4, 9, 25, 49, 121, 169, 289, 361, 529, 841, 961, 1369, 1681, 1849, 2209]<p>Если хотите, чтобы после запуска теста выполнялся ещё какой-то скрипт, это также можно сделать через фикстуры. Для этого вместо ключевого слова return используйте yield. Код, написанный после yield, и будет выполняться по завершении теста.</p>
64 <p>Изменим в нашем примере с простыми числами фикстуру get_prime_nums() и добавим в неё финализатор:</p>
64 <p>Изменим в нашем примере с простыми числами фикстуру get_prime_nums() и добавим в неё финализатор:</p>
65 @pytest.fixture() def get_prime_nums(): print('\nРабота фикстуры') prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) yield prime_nums print('\nРабота финализатора')<p>При запуске тестов получаем такой результат:</p>
65 @pytest.fixture() def get_prime_nums(): print('\nРабота фикстуры') prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) yield prime_nums print('\nРабота финализатора')<p>При запуске тестов получаем такой результат:</p>
66 tests.py::test_plus2 Работа фикстуры PASSED [ 33%] Работа финализатора tests.py::test_multiply2 Работа фикстуры PASSED [ 66%] Работа финализатора tests.py::test_exponent2 Работа фикстуры PASSED [100%] Работа финализатора<p>Если тест не был пройден (то есть assert получил False), код из финализатора всё равно выполняется.</p>
66 tests.py::test_plus2 Работа фикстуры PASSED [ 33%] Работа финализатора tests.py::test_multiply2 Работа фикстуры PASSED [ 66%] Работа финализатора tests.py::test_exponent2 Работа фикстуры PASSED [100%] Работа финализатора<p>Если тест не был пройден (то есть assert получил False), код из финализатора всё равно выполняется.</p>
67 <p>У фикстур можно настраивать область действия, в которой они существуют. По умолчанию она равна функции. Это значит, что, когда тестовая функция прекращает свою работу, фикстура финализируется и уничтожается. При следующем вызове фикстура создаётся заново. Это хорошо видно по прошлому примеру с финализатором.</p>
67 <p>У фикстур можно настраивать область действия, в которой они существуют. По умолчанию она равна функции. Это значит, что, когда тестовая функция прекращает свою работу, фикстура финализируется и уничтожается. При следующем вызове фикстура создаётся заново. Это хорошо видно по прошлому примеру с финализатором.</p>
68 <p>Область действия фикстуры указывается в её декораторе аргументом scope='<em>область действия'</em>. Всего есть пять уровней:</p>
68 <p>Область действия фикстуры указывается в её декораторе аргументом scope='<em>область действия'</em>. Всего есть пять уровней:</p>
69 <ul><li>'function' - для функции;</li>
69 <ul><li>'function' - для функции;</li>
70 <li>'class' - для класса;</li>
70 <li>'class' - для класса;</li>
71 <li>'module' - для модуля (то есть py-файла);</li>
71 <li>'module' - для модуля (то есть py-файла);</li>
72 <li>'package' - для пакета;</li>
72 <li>'package' - для пакета;</li>
73 <li>'session' - для всей сессии тестирования.</li>
73 <li>'session' - для всей сессии тестирования.</li>
74 </ul><p>Изменим у фикстуры get_prime_nums область действия на module.</p>
74 </ul><p>Изменим у фикстуры get_prime_nums область действия на module.</p>
75 @pytest.fixture(scope='module') def get_prime_nums(): print('\nРабота фикстуры') prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) yield prime_nums print('\nРабота финализатора')<p>Посмотрим, как изменится работа тестов:</p>
75 @pytest.fixture(scope='module') def get_prime_nums(): print('\nРабота фикстуры') prime_nums = [] for num in range(1, 50): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) yield prime_nums print('\nРабота финализатора')<p>Посмотрим, как изменится работа тестов:</p>
76 tests.py::test_plus2 Работа фикстуры PASSED [ 33%] tests.py::test_multiply2 PASSED [ 66%] tests.py::test_exponent2 PASSED [100%] Работа финализатора<p>В отличие от прошлого примера, фикстура здесь вызывается только один раз - в первой функции, которая её использует. Затем результат работы кэшируется. Финализатор тоже срабатывает только единожды, когда заканчивается файл.</p>
76 tests.py::test_plus2 Работа фикстуры PASSED [ 33%] tests.py::test_multiply2 PASSED [ 66%] tests.py::test_exponent2 PASSED [100%] Работа финализатора<p>В отличие от прошлого примера, фикстура здесь вызывается только один раз - в первой функции, которая её использует. Затем результат работы кэшируется. Финализатор тоже срабатывает только единожды, когда заканчивается файл.</p>
77 <p>Одному тесту можно передать сколько угодно фикстур, указывая их через запятую. Их можно передавать и другим фикстурам - тоже в любом количестве.</p>
77 <p>Одному тесту можно передать сколько угодно фикстур, указывая их через запятую. Их можно передавать и другим фикстурам - тоже в любом количестве.</p>
78 <p>Например, фикстуру get_prime_nums можно разбить на несколько (хотя в нашем случае в этом нет практического смысла):</p>
78 <p>Например, фикстуру get_prime_nums можно разбить на несколько (хотя в нашем случае в этом нет практического смысла):</p>
79 @pytest.fixture() def get_min_num(): return 1 @pytest.fixture() def get_max_num(): return 50 @pytest.fixture() def get_prime_nums(get_min_num, get_max_num): prime_nums = [] for num in range(get_min_num, get_max_num): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) return prime_nums<p>Вы всегда явно указываете, какие фикстуры используете в функции или другой фикстуре. Это позволяет удобно отслеживать зависимости данных и управлять ими.</p>
79 @pytest.fixture() def get_min_num(): return 1 @pytest.fixture() def get_max_num(): return 50 @pytest.fixture() def get_prime_nums(get_min_num, get_max_num): prime_nums = [] for num in range(get_min_num, get_max_num): for div in range(2, num): if num % div == 0: break else: prime_nums.append(num) return prime_nums<p>Вы всегда явно указываете, какие фикстуры используете в функции или другой фикстуре. Это позволяет удобно отслеживать зависимости данных и управлять ими.</p>
80 <p><strong>Примечание</strong></p>
80 <p><strong>Примечание</strong></p>
81 <p>Фикстуры с более широкой областью действия нельзя встраивать с фикстуры меньшего уровня.</p>
81 <p>Фикстуры с более широкой областью действия нельзя встраивать с фикстуры меньшего уровня.</p>
82 <p>Иногда может быть полезным, чтобы фикстура запускалась всегда, даже если функция её не вызывает. Например, когда перед выполнением тестов нужно залогиниться в системе.</p>
82 <p>Иногда может быть полезным, чтобы фикстура запускалась всегда, даже если функция её не вызывает. Например, когда перед выполнением тестов нужно залогиниться в системе.</p>
83 <p>В таких случаях можно указать параметр autouse в декораторе: @pytest.fixture(autouse=True).</p>
83 <p>В таких случаях можно указать параметр autouse в декораторе: @pytest.fixture(autouse=True).</p>
84 <p>Будьте осторожны при использовании autouse. Такие фикстуры могут создавать неявные зависимости и менять данные непредсказуемым для вас путём. Особенно если их много и они находятся в сложной иерархии.</p>
84 <p>Будьте осторожны при использовании autouse. Такие фикстуры могут создавать неявные зависимости и менять данные непредсказуемым для вас путём. Особенно если их много и они находятся в сложной иерархии.</p>
85 <p>Pytest позволяет настраивать запуск тестов, применяя к ним метки. Использовать их можно не только с тестовыми функциями, но и с целыми классами. Чтобы добавить метку, нужно написать декоратор: @pytest.mark.*название метки*.</p>
85 <p>Pytest позволяет настраивать запуск тестов, применяя к ним метки. Использовать их можно не только с тестовыми функциями, но и с целыми классами. Чтобы добавить метку, нужно написать декоратор: @pytest.mark.*название метки*.</p>
86 <p>В Pytest можно сделать так, чтобы запускались только помеченные тесты. Для этого используют команду терминала с аргументом -m: pytest -m *<em>название метки*</em>. Можно и наоборот: запустить все тесты, кроме помеченных. В таком случае команда выглядит так: pytest -m 'not<em>название метки'</em>.</p>
86 <p>В Pytest можно сделать так, чтобы запускались только помеченные тесты. Для этого используют команду терминала с аргументом -m: pytest -m *<em>название метки*</em>. Можно и наоборот: запустить все тесты, кроме помеченных. В таком случае команда выглядит так: pytest -m 'not<em>название метки'</em>.</p>
87 <p>У одного теста или класса может быть сколько угодно меток. Посмотреть их список можно командой pytest --markers и в <a>документации</a>. Мы расскажем об основных.</p>
87 <p>У одного теста или класса может быть сколько угодно меток. Посмотреть их список можно командой pytest --markers и в <a>документации</a>. Мы расскажем об основных.</p>
88 <p>Чтобы пропустить тест, поставьте метку skip. В качестве аргумента ей можно передать необязательный параметр reason='<em>причина пропуска'</em>. Например:</p>
88 <p>Чтобы пропустить тест, поставьте метку skip. В качестве аргумента ей можно передать необязательный параметр reason='<em>причина пропуска'</em>. Например:</p>
89 @pytest.mark.skip(reason='Тестовый пропуск') def test_skipped(): pass<p>Результат:</p>
89 @pytest.mark.skip(reason='Тестовый пропуск') def test_skipped(): pass<p>Результат:</p>
90 SKIPPED (Тестовый пропуск) [100%] Skipped: Тестовый пропуск<p>Метка skipif получает два аргумента. Первый - это условие. Если оно выполняется (результат True) - тест пропускается, если нет (результат False) - тест выполняется как обычно. Во втором аргументе, как и в случае со skip, можно передать строку с причиной пропуска:</p>
90 SKIPPED (Тестовый пропуск) [100%] Skipped: Тестовый пропуск<p>Метка skipif получает два аргумента. Первый - это условие. Если оно выполняется (результат True) - тест пропускается, если нет (результат False) - тест выполняется как обычно. Во втором аргументе, как и в случае со skip, можно передать строку с причиной пропуска:</p>
91 x = 1 @pytest.mark.skipif(x &gt; 0, reason='Тестовый пропуск') def test_skipped_if(): pass<p>Результат тот же самый, что и в прошлом случае:</p>
91 x = 1 @pytest.mark.skipif(x &gt; 0, reason='Тестовый пропуск') def test_skipped_if(): pass<p>Результат тот же самый, что и в прошлом случае:</p>
92 <p>Тест под меткой xfail может выдать два результата. Если тест будет пройден, Pytest пометит его XPASS, если ожидаемо провален - XFAIL. Ни один из вариантов не вызовет провала общего набора тестов:</p>
92 <p>Тест под меткой xfail может выдать два результата. Если тест будет пройден, Pytest пометит его XPASS, если ожидаемо провален - XFAIL. Ни один из вариантов не вызовет провала общего набора тестов:</p>
93 @pytest.mark.xfail(reason='Намеренный провал') def test_xfailed(): assert False<p>Результат:</p>
93 @pytest.mark.xfail(reason='Намеренный провал') def test_xfailed(): assert False<p>Результат:</p>
94 XFAIL (Намеренный провал) [100%] @pytest.mark.xfail(reason='Намеренный провал') def test_xfailed(): &gt; assert False E assert False tests.py:49: AssertionError<p>У xfail есть несколько аргументов. Как в skipif, вы можете указать условие (и ожидать провал только при нём) и передать параметр reason. Дополнительно к этому xfail позволяет:</p>
94 XFAIL (Намеренный провал) [100%] @pytest.mark.xfail(reason='Намеренный провал') def test_xfailed(): &gt; assert False E assert False tests.py:49: AssertionError<p>У xfail есть несколько аргументов. Как в skipif, вы можете указать условие (и ожидать провал только при нём) и передать параметр reason. Дополнительно к этому xfail позволяет:</p>
95 <ul><li>добавить исключение в raises=<em>*название исключения*</em>;</li>
95 <ul><li>добавить исключение в raises=<em>*название исключения*</em>;</li>
96 <li>вообще не выполнять тест в run=False (тогда он автоматически будет засчитан как XFAIL);</li>
96 <li>вообще не выполнять тест в run=False (тогда он автоматически будет засчитан как XFAIL);</li>
97 <li>сделать, чтобы провал теста вызывал провал всего тестового набора в strict=True.</li>
97 <li>сделать, чтобы провал теста вызывал провал всего тестового набора в strict=True.</li>
98 </ul><p>Подробнее о возможностях xfail -<a>в документации</a>.</p>
98 </ul><p>Подробнее о возможностях xfail -<a>в документации</a>.</p>
99 <p>Метка parametrize позволяет вызывать один и тот же тест с разными входными данными. Это полезно, когда мы хотим проверить несколько случаев.</p>
99 <p>Метка parametrize позволяет вызывать один и тот же тест с разными входными данными. Это полезно, когда мы хотим проверить несколько случаев.</p>
100 <p>Например, у нас есть функция, которая пишет, положительное число или отрицательное:</p>
100 <p>Например, у нас есть функция, которая пишет, положительное число или отрицательное:</p>
101 def positive_or_negative(x): if x &gt; 0: return 'positive' elif x &lt; 0: return 'negative' else: return 'zero'<p>Сначала проверим, правильно ли она обрабатывает положительные числа: целые, дробные и очень маленькие. Без параметризации нам пришлось бы писать сразу три однотипных теста:</p>
101 def positive_or_negative(x): if x &gt; 0: return 'positive' elif x &lt; 0: return 'negative' else: return 'zero'<p>Сначала проверим, правильно ли она обрабатывает положительные числа: целые, дробные и очень маленькие. Без параметризации нам пришлось бы писать сразу три однотипных теста:</p>
102 from main import positive_or_negative def test_positive_or_negative_if_positive_int(): assert positive_or_negative(165) == 'positive' def test_positive_or_negative_if_positive_float(): assert positive_or_negative(1.2) == 'positive' def test_positive_or_negative_if_positive_small(): assert positive_or_negative(0.0000001) == 'positive'<p>Результат:</p>
102 from main import positive_or_negative def test_positive_or_negative_if_positive_int(): assert positive_or_negative(165) == 'positive' def test_positive_or_negative_if_positive_float(): assert positive_or_negative(1.2) == 'positive' def test_positive_or_negative_if_positive_small(): assert positive_or_negative(0.0000001) == 'positive'<p>Результат:</p>
103 tests.py::test_positive_or_negative_if_positive_int PASSED [ 33%] tests.py::test_positive_or_negative_if_positive_float PASSED [ 66%] tests.py::test_positive_or_negative_if_positive_small PASSED [100%]<p>А потом нужно будет писать такие же три теста для отрицательных чисел и ещё один для нуля. Итого семь тестов для одной маленькой функции. Нерационально. Тут-то на помощь и приходит параметризация.</p>
103 tests.py::test_positive_or_negative_if_positive_int PASSED [ 33%] tests.py::test_positive_or_negative_if_positive_float PASSED [ 66%] tests.py::test_positive_or_negative_if_positive_small PASSED [100%]<p>А потом нужно будет писать такие же три теста для отрицательных чисел и ещё один для нуля. Итого семь тестов для одной маленькой функции. Нерационально. Тут-то на помощь и приходит параметризация.</p>
104 <p>Метка parametrize получает два аргумента: название переменной и список её значений. Название переменной передаём тесту (точно так же, как фикстуру). Получается вот так:</p>
104 <p>Метка parametrize получает два аргумента: название переменной и список её значений. Название переменной передаём тесту (точно так же, как фикстуру). Получается вот так:</p>
105 import pytest from main import positive_or_negative @pytest.mark.parametrize('x', [165, 1.2, 0.0000001]) def test_positive_or_negative_if_positive(x): assert positive_or_negative(x) == 'positive'<p>Результат:</p>
105 import pytest from main import positive_or_negative @pytest.mark.parametrize('x', [165, 1.2, 0.0000001]) def test_positive_or_negative_if_positive(x): assert positive_or_negative(x) == 'positive'<p>Результат:</p>
106 tests.py::test_positive_or_negative_if_positive[165] tests.py::test_positive_or_negative_if_positive[1.2] tests.py::test_positive_or_negative_if_positive[1e-07] PASSED [33%] PASSED [66%] PASSED [100%]<p>Можно в одной строке через запятую передать сразу несколько переменных. Тогда каждый элемент списка задаётся кортежем, в котором мы по очереди перечисляем значения этих переменных:</p>
106 tests.py::test_positive_or_negative_if_positive[165] tests.py::test_positive_or_negative_if_positive[1.2] tests.py::test_positive_or_negative_if_positive[1e-07] PASSED [33%] PASSED [66%] PASSED [100%]<p>Можно в одной строке через запятую передать сразу несколько переменных. Тогда каждый элемент списка задаётся кортежем, в котором мы по очереди перечисляем значения этих переменных:</p>
107 import pytest from main import positive_or_negative @pytest.mark.parametrize('x, expected_result', [(165, 'positive'), (1.2, 'positive'), (0.0000001, 'positive'), (-165, 'negative'), (-1.2, 'negative'), (-0.0000001, 'negative'), (0, 'zero')]) def test_positive_or_negative(x, expected_result): assert positive_or_negative(x) == expected_result<p>Результат:</p>
107 import pytest from main import positive_or_negative @pytest.mark.parametrize('x, expected_result', [(165, 'positive'), (1.2, 'positive'), (0.0000001, 'positive'), (-165, 'negative'), (-1.2, 'negative'), (-0.0000001, 'negative'), (0, 'zero')]) def test_positive_or_negative(x, expected_result): assert positive_or_negative(x) == expected_result<p>Результат:</p>
108 tests.py::test_positive_or_negative[165-positive] tests.py::test_positive_or_negative[1.2-positive] tests.py::test_positive_or_negative[1e-07-positive] tests.py::test_positive_or_negative[-165-negative] tests.py::test_positive_or_negative[-1.2-negative] tests.py::test_positive_or_negative[-1e-07-negative] tests.py::test_positive_or_negative[0-zero] PASSED [14%] PASSED [28%] PASSED [42%] PASSED [57%] PASSED [71%] PASSED [85%] PASSED [100%]<p>Если одному тесту передать сразу несколько меток parametrize, он запустит все их возможные комбинации. Пример для наглядности:</p>
108 tests.py::test_positive_or_negative[165-positive] tests.py::test_positive_or_negative[1.2-positive] tests.py::test_positive_or_negative[1e-07-positive] tests.py::test_positive_or_negative[-165-negative] tests.py::test_positive_or_negative[-1.2-negative] tests.py::test_positive_or_negative[-1e-07-negative] tests.py::test_positive_or_negative[0-zero] PASSED [14%] PASSED [28%] PASSED [42%] PASSED [57%] PASSED [71%] PASSED [85%] PASSED [100%]<p>Если одному тесту передать сразу несколько меток parametrize, он запустит все их возможные комбинации. Пример для наглядности:</p>
109 import pytest @pytest.mark.parametrize('x', [1, 0, -1]) @pytest.mark.parametrize('y', [1, 0, -1]) def test_coordinate_zone(x, y): print(x, y)<p>Результат:</p>
109 import pytest @pytest.mark.parametrize('x', [1, 0, -1]) @pytest.mark.parametrize('y', [1, 0, -1]) def test_coordinate_zone(x, y): print(x, y)<p>Результат:</p>
110 tests.py::test_coordinate_zone[1-1] tests.py::test_coordinate_zone[1-0] tests.py::test_coordinate_zone[1--1] tests.py::test_coordinate_zone[0-1] tests.py::test_coordinate_zone[0-0] tests.py::test_coordinate_zone[0--1] tests.py::test_coordinate_zone[-1-1] tests.py::test_coordinate_zone[-1-0] tests.py::test_coordinate_zone[-1--1] PASSED [11%]1 1 PASSED [22%]0 1 PASSED [33%]-1 1 PASSED [44%]1 0 PASSED [55%]0 0 PASSED [66%]-1 0 PASSED [77%]1 -1 PASSED [88%]0 -1 PASSED [100%]-1 -1<p>Помимо встроенных меток вы можете использовать свои собственные. Это удобно, когда нужно разбить все тесты по нескольким группам и запускать отдельно друг от друга.</p>
110 tests.py::test_coordinate_zone[1-1] tests.py::test_coordinate_zone[1-0] tests.py::test_coordinate_zone[1--1] tests.py::test_coordinate_zone[0-1] tests.py::test_coordinate_zone[0-0] tests.py::test_coordinate_zone[0--1] tests.py::test_coordinate_zone[-1-1] tests.py::test_coordinate_zone[-1-0] tests.py::test_coordinate_zone[-1--1] PASSED [11%]1 1 PASSED [22%]0 1 PASSED [33%]-1 1 PASSED [44%]1 0 PASSED [55%]0 0 PASSED [66%]-1 0 PASSED [77%]1 -1 PASSED [88%]0 -1 PASSED [100%]-1 -1<p>Помимо встроенных меток вы можете использовать свои собственные. Это удобно, когда нужно разбить все тесты по нескольким группам и запускать отдельно друг от друга.</p>
111 <p>Чтобы создать собственную метку, просто используйте её имя:</p>
111 <p>Чтобы создать собственную метку, просто используйте её имя:</p>
112 import pytest @pytest.mark.my_mark def test_1(): pass def test_2(): pass @pytest.mark.my_mark def test_3(): pass @pytest.mark.my_mark def test_4(): pass def test_5(): pass<p>Используем команду pytest --no-summary -m my_mark tests.py, чтобы запустить только тесты с меткой my_mark. Получим такой результат:</p>
112 import pytest @pytest.mark.my_mark def test_1(): pass def test_2(): pass @pytest.mark.my_mark def test_3(): pass @pytest.mark.my_mark def test_4(): pass def test_5(): pass<p>Используем команду pytest --no-summary -m my_mark tests.py, чтобы запустить только тесты с меткой my_mark. Получим такой результат:</p>
113 collected 5 items / 2 deselected / 3 selected tests.py .. [100%] ===== 3 passed, 2 deselected, 3 warnings in 0.02s =====<p>Как видим, из пяти файлов было запущено только три - на которых стояла нужная метка.</p>
113 collected 5 items / 2 deselected / 3 selected tests.py .. [100%] ===== 3 passed, 2 deselected, 3 warnings in 0.02s =====<p>Как видим, из пяти файлов было запущено только три - на которых стояла нужная метка.</p>
114 <p>Также мы получили три предупреждения. Это Pytest обращает наше внимание на то, что метка my_mark не зарегистрирована в файле pytest.ini. Он уточняет: вы точно воспользовались собственной меткой, а не опечатались при написании встроенной?</p>
114 <p>Также мы получили три предупреждения. Это Pytest обращает наше внимание на то, что метка my_mark не зарегистрирована в файле pytest.ini. Он уточняет: вы точно воспользовались собственной меткой, а не опечатались при написании встроенной?</p>
115 <p>О том, как зарегистрировать собственную метку, тоже можно прочитать в <a>документации</a>.</p>
115 <p>О том, как зарегистрировать собственную метку, тоже можно прочитать в <a>документации</a>.</p>
116 <p><strong>Pytest</strong>- самый популярный среди питонистов<strong>фреймворк для тестирования</strong>. Он позволяет писать меньше однотипного кода, чем встроенный Unittest, и может работать без тестовых классов. Вот его базовые инструменты:</p>
116 <p><strong>Pytest</strong>- самый популярный среди питонистов<strong>фреймворк для тестирования</strong>. Он позволяет писать меньше однотипного кода, чем встроенный Unittest, и может работать без тестовых классов. Вот его базовые инструменты:</p>
117 <ul><li><strong>Ключевое слово</strong><strong>assert</strong>отвечает за результат тестирования. Если заданное после него условие правдиво - тест пройден, если оно ложно - провален.</li>
117 <ul><li><strong>Ключевое слово</strong><strong>assert</strong>отвечает за результат тестирования. Если заданное после него условие правдиво - тест пройден, если оно ложно - провален.</li>
118 <li><strong>Фикстуры</strong>- дополнительные функции, в которых можно задавать окружение тестов. Они могут использовать другие фикстуры, создавая целые иерархии.</li>
118 <li><strong>Фикстуры</strong>- дополнительные функции, в которых можно задавать окружение тестов. Они могут использовать другие фикстуры, создавая целые иерархии.</li>
119 <li><strong>Метки</strong>- декораторы, которые позволяют корректировать поведение тестов: пропускать их, ожидать определённых результатов, передавать разные входные данные и так далее. Можно создавать свои пользовательские метки.</li>
119 <li><strong>Метки</strong>- декораторы, которые позволяют корректировать поведение тестов: пропускать их, ожидать определённых результатов, передавать разные входные данные и так далее. Можно создавать свои пользовательские метки.</li>
120 </ul><p>Python для всех</p>
120 </ul><p>Python для всех</p>
121 <p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
121 <p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
122 <p><a>Пройти бесплатно</a></p>
122 <p><a>Пройти бесплатно</a></p>
123 <a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>
123 <a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>