Pytest - Краткое руководство

Pytest - это среда тестирования на основе Python, которая используется для написания и выполнения тестовых кодов. В настоящее время службы REST pytest в основном используется для тестирования API, хотя мы можем использовать pytest для написания простых и сложных тестов, то есть мы можем писать коды для тестирования API, базы данных, пользовательского интерфейса и т. Д.

Преимущества Pytest

Преимущества Pytest следующие:

  • Pytest может запускать несколько тестов параллельно, что сокращает время выполнения набора тестов.

  • Pytest имеет свой собственный способ автоматического обнаружения тестового файла и тестовых функций, если это не указано явно.

  • Pytest позволяет пропускать часть тестов во время выполнения.

  • Pytest позволяет нам запускать подмножество всего набора тестов.

  • Pytest является бесплатным и открытым исходным кодом.

  • Благодаря простому синтаксису с pytest очень легко начать.

В этом руководстве мы объясним основы pytest с помощью примеров программ.

В этой главе мы узнаем, как установить pytest.

Чтобы начать установку, выполните следующую команду -

pip install pytest == 2.9.1

Мы можем установить любую версию pytest. Здесь 2.9.1 - это версия, которую мы устанавливаем.

Чтобы установить последнюю версию pytest, выполните следующую команду -

pip install pytest

Подтвердите установку, используя следующую команду, чтобы отобразить раздел справки pytest.

pytest -h

Запуск pytest без указания имени файла запустит все файлы формата test_*.py или же *_test.pyв текущем каталоге и подкаталогах. Pytest автоматически определяет эти файлы как тестовые. Мыcan заставить pytest запускать другие имена файлов, явно указав их.

Pytest требует, чтобы имена тестовых функций начинались с test. Неформатные имена функцийtest*не рассматриваются pytest как тестовые функции. Мыcannot явно заставить pytest рассматривать любую функцию, не начинающуюся с test в качестве тестовой функции.

Мы поймем выполнение тестов в наших последующих главах.

Теперь мы начнем с нашей первой программы pytest. Сначала мы создадим каталог и тем самым создадим в нем наши тестовые файлы.

Давайте выполним шаги, показанные ниже -

  • Создайте новый каталог с именем automation и перейдите в каталог в командной строке.

  • Создайте файл с именем test_square.py и добавьте в этот файл приведенный ниже код.

import math

def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

def testsquare():
   num = 7
   assert 7*7 == 40

def tesequality():
   assert 10 == 11

Запустите тест, используя следующую команду -

pytest

Приведенная выше команда сгенерирует следующий вывод -

test_square.py .F
============================================== FAILURES 
==============================================
______________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num=7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.06 seconds 
=================================

Смотрите первую строку результата. Он отображает имя файла и результаты. F означает сбой теста, а точка (.) Означает успешное выполнение теста.

Ниже мы можем увидеть детали неудавшихся тестов. Он покажет, в каком утверждении тест не прошел. В нашем примере 7 * 7 сравнивается с равенством 40, что неверно. В итоге мы видим сводку выполнения теста, 1 неудачный и 1 пройденный.

Функция tesequality не выполняется, потому что pytest не будет рассматривать ее как тест, поскольку ее имя не в формате test*.

Теперь выполните команду ниже и снова посмотрите результат -

pytest -v

-v увеличивает детализацию.

test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
============================================== FAILURES 
==============================================
_____________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.04 seconds 
=================================

Теперь результат более подробно описывает неудачный и пройденный тест.

Note - команда pytest выполнит все файлы формата test_* или же *_test в текущем каталоге и подкаталогах.

В этой главе мы узнаем, как выполнить один тестовый файл и несколько тестовых файлов. У нас уже есть тестовый файлtest_square.pyсоздан. Создайте новый тестовый файлtest_compare.py со следующим кодом -

def test_greater():
   num = 100
   assert num > 100

def test_greater_equal():
   num = 100
   assert num >= 100

def test_less():
   num = 100
   assert num < 200

Теперь, чтобы запустить все тесты из всех файлов (здесь 2 файла), нам нужно выполнить следующую команду -

pytest -v

Вышеупомянутая команда запустит тесты из обоих test_square.py и test_compare.py. Результат будет сгенерирован следующим образом -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
================================================ FAILURES 
================================================
______________________________________________ test_greater 
______________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100

test_compare.py:3: AssertionError
_______________________________________________ testsquare 
_______________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40

test_square.py:9: AssertionError
=================================== 2 failed, 3 passed in 0.07 seconds 
===================================

Чтобы выполнить тесты из определенного файла, используйте следующий синтаксис -

pytest <filename> -v

Теперь выполните следующую команду -

pytest test_compare.py -v

Вышеупомянутая команда выполнит тесты только из файла test_compare.py. Наш результат будет -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
================================= 1 failed, 2 passed in 0.04 seconds 
=================================

В реальном сценарии у нас будет несколько тестовых файлов, и каждый файл будет содержать несколько тестов. Тесты будут охватывать различные модули и функции. Предположим, мы хотим запустить только определенный набор тестов; как мы это делаем?

Pytest предоставляет два способа запустить подмножество набора тестов.

  • Выберите тесты для запуска на основе совпадения подстрок в именах тестов.
  • Выберите группы тестов для запуска на основе примененных маркеров.

Мы объясним эти два на примерах в следующих главах.

Чтобы выполнить тесты, содержащие строку в своем имени, мы можем использовать следующий синтаксис -

pytest -k <substring> -v

-k <подстрока> представляет подстроку для поиска в именах тестов.

Теперь выполните следующую команду -

pytest -k great -v

Это выполнит все имена тестов, содержащие слово ‘great’в его имени. В этом случае ониtest_greater() и test_greater_equal(). Смотрите результат ниже.

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
def test_greater():
num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds 
==========================

Здесь в результате мы видим, что 3 теста не выделены. Это потому, что эти имена тестов не содержат словаgreat в них.

Note - Имя тестовой функции должно начинаться с «test».

В этой главе мы узнаем, как группировать тесты с помощью маркеров.

Pytest позволяет нам использовать маркеры в тестовых функциях. Маркеры используются для установки различных функций / атрибутов для тестирования функций. Pytest предоставляет множество встроенных маркеров, таких как xfail, skip и параметризация. Кроме того, пользователи могут создавать свои собственные имена маркеров. Маркеры применяются к тестам с использованием синтаксиса, приведенного ниже -

@pytest.mark.<markername>

Чтобы использовать маркеры, мы должны import pytestмодуль в тестовом файле. Мы можем определять собственные имена маркеров для тестов и запускать тесты с этими именами маркеров.

Чтобы запустить отмеченные тесты, мы можем использовать следующий синтаксис -

pytest -m <markername> -v

-m <markername> представляет имя маркера тестов, которые нужно выполнить.

Обновите наши тестовые файлы test_compare.py и test_square.pyсо следующим кодом. Определяем 3 маркера– great, square, others.

test_compare.py

import pytest
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

test_square.py

import pytest
import math

@pytest.mark.square
def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

@pytest.mark.square
def testsquare():
   num = 7
   assert 7*7 == 40

@pytest.mark.others
   def test_equality():
   assert 10 == 11

Теперь, чтобы запустить тесты, отмеченные как others, выполните следующую команду -

pytest -m others -v

Смотрите результат ниже. Он провел 2 теста, помеченных какothers.

test_compare.py::test_less PASSED
test_square.py::test_equality FAILED
============================================== FAILURES
==============================================
___________________________________________ test_equality
____________________________________________
   @pytest.mark.others
   def test_equality():
>  assert 10 == 11
E  assert 10 == 11
test_square.py:16: AssertionError
========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds
==========================

Точно так же мы можем запускать тесты и с другими маркерами - отлично, сравните

Приспособления - это функции, которые запускаются перед каждой тестовой функцией, к которой они применяются. Приспособления используются для подачи некоторых данных в тесты, таких как соединения с базой данных, URL-адреса для тестирования и некоторые виды входных данных. Поэтому вместо того, чтобы запускать один и тот же код для каждого теста, мы можем прикрепить к тестам функцию фиксации, и она будет запускаться и возвращать данные в тест перед выполнением каждого теста.

Функция отмечена как приспособление -

@pytest.fixture

Тестовая функция может использовать прибор, указав имя прибора как входной параметр.

Создать файл test_div_by_3_6.py и добавьте к нему приведенный ниже код

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Здесь у нас есть функция фикстуры с именем input_value, который предоставляет входные данные для тестов. Чтобы получить доступ к функции прибора, тесты должны указать имя прибора как входной параметр.

Pytest во время выполнения теста увидит имя прибора как входной параметр. Затем он выполняет функцию фиксации, и возвращаемое значение сохраняется во входном параметре, который может использоваться тестом.

Выполните тест, используя следующую команду -

pytest -k divisible -v

Приведенная выше команда сгенерирует следующий результат -

test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:12: AssertionError
========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds
==========================

Однако у этого подхода есть свои ограничения. Функция фикстуры, определенная внутри тестового файла, имеет область видимости только в тестовом файле. Мы не можем использовать этот прибор в другом тестовом файле. Чтобы сделать прибор доступным для нескольких тестовых файлов, мы должны определить функцию прибора в файле с именем conftest.py.conftest.py объясняется в следующей главе.

Мы можем определить функции фикстуры в этом файле, чтобы сделать их доступными для нескольких тестовых файлов.

Создать новый файл conftest.py и добавьте в него приведенный ниже код -

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

Отредактируйте test_div_by_3_6.py чтобы удалить функцию фиксации -

import pytest

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Создать новый файл test_div_by_13.py -

import pytest

def test_divisible_by_13(input_value):
   assert input_value % 13 == 0

Теперь у нас есть файлы test_div_by_3_6.py и test_div_by_13.py используя приспособление, определенное в conftest.py.

Запустите тесты, выполнив следующую команду -

pytest -k divisible -v

Приведенная выше команда сгенерирует следующий результат -

test_div_by_13.py::test_divisible_by_13 PASSED
test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:7: AssertionError
========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds
==========================

Тесты будут искать приспособление в том же файле. Поскольку прибор не найден в файле, он проверит его в файле conftest.py. При его нахождении вызывается метод фикстуры, и результат возвращается во входной аргумент теста.

Параметризация теста выполняется для запуска теста с несколькими наборами входных данных. Мы можем сделать это с помощью следующего маркера -

@pytest.mark.parametrize

Скопируйте приведенный ниже код в файл с именем test_multiplication.py -

import pytest

@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
def test_multiplication_11(num, output):
   assert 11*num == output

Здесь тест умножает входные данные на 11 и сравнивает результат с ожидаемым выходом. Тест имеет 4 набора входных данных, каждый из которых имеет 2 значения: одно - это число, которое нужно умножить на 11, а другое - ожидаемый результат.

Выполните тест, выполнив следующую команду -

Pytest -k multiplication -v

Приведенная выше команда сгенерирует следующий вывод -

test_multiplication.py::test_multiplication_11[1-11] PASSED
test_multiplication.py::test_multiplication_11[2-22] PASSED
test_multiplication.py::test_multiplication_11[3-35] FAILED
test_multiplication.py::test_multiplication_11[4-44] PASSED
============================================== FAILURES
==============================================
_________________ test_multiplication_11[3-35] __________________
num = 3, output = 35
   @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
   def test_multiplication_11(num, output):
>  assert 11*num == output
E  assert (11 * 3) == 35
test_multiplication.py:5: AssertionError
============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds
==============================

В этой главе мы узнаем о тестах Skip и Xfail в Pytest.

Теперь рассмотрим следующие ситуации -

  • Тест какое-то время не актуален по каким-то причинам.
  • Внедряется новая функция, и мы уже добавили тест для этой функции.

В этих ситуациях у нас есть возможность выполнить тест xfail или пропустить тесты.

Pytest выполнит тест xfailed, но он не будет считаться частичным отказом или пройденным тестом. Детали этих тестов не будут напечатаны, даже если тест не пройден (помните, что pytest обычно печатает детали неудачного теста). Мы можем провести тесты xfail, используя следующий маркер -

@pytest.mark.xfail

Пропуск теста означает, что тест не будет выполнен. Мы можем пропустить тесты, используя следующий маркер -

@pytest.mark.skip

Позже, когда тест станет актуальным, мы можем удалить маркеры.

Отредактируйте test_compare.py мы уже должны включить маркеры xfail и skip -

import pytest
@pytest.mark.xfail
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.xfail
@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.skip
@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

Выполните тест, используя следующую команду -

pytest test_compare.py -v

После выполнения вышеуказанная команда сгенерирует следующий результат -

test_compare.py::test_greater xfail
test_compare.py::test_greater_equal XPASS
test_compare.py::test_less SKIPPED
============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds
============================

В реальном сценарии, как только новая версия кода готова к развертыванию, она сначала развертывается в среде pre-prod / staging. Затем на нем запускается набор тестов.

Код подходит для развертывания в производственной среде только в том случае, если набор тестов проходит успешно. Если происходит сбой теста, будь то один или несколько, код не готов к работе.

Следовательно, что, если мы хотим остановить выполнение набора тестов вскоре после того, как n тестов не пройдут. Это можно сделать в pytest с помощью maxfail.

Синтаксис для остановки выполнения набора тестов вскоре после n неудачных тестов выглядит следующим образом:

pytest --maxfail = <num>

Создайте файл test_failure.py со следующим кодом.

import pytest
import math

def test_sqrt_failure():
   num = 25
   assert math.sqrt(num) == 6

def test_square_failure():
   num = 7
   assert 7*7 == 40

def test_equality_failure():
   assert 10 == 11

При выполнении этого тестового файла все 3 теста не пройдут. Здесь мы собираемся остановить выполнение теста после одного сбоя:

pytest test_failure.py -v --maxfail = 1
test_failure.py::test_sqrt_failure FAILED
=================================== FAILURES
=================================== _______________________________________
test_sqrt_failure __________________________________________
   def test_sqrt_failure():
   num = 25
>  assert math.sqrt(num) == 6
E  assert 5.0 == 6
E  + where 5.0 = <built-in function sqrt>(25)
E  + where <built-in function sqrt>= math.sqrt
test_failure.py:6: AssertionError
=============================== 1 failed in 0.04 seconds
===============================

В приведенном выше результате мы видим, что выполнение останавливается при одной ошибке.

По умолчанию pytest запускает тесты в последовательном порядке. В реальном сценарии набор тестов будет содержать несколько файлов тестов, и каждый файл будет содержать набор тестов. Это приведет к большому времени выполнения. Чтобы преодолеть это, pytest предоставляет нам возможность запускать тесты параллельно.

Для этого нам нужно сначала установить плагин pytest-xdist.

Установите pytest-xdist, выполнив следующую команду -

pip install pytest-xdist

Теперь мы можем запускать тесты, используя синтаксис pytest -n <num>

pytest -n 3

-n <num> запускает тесты с использованием нескольких рабочих, здесь 3.

У нас не будет большой разницы во времени, когда нужно запустить всего несколько тестов. Однако это имеет значение, когда набор тестов большой.

Мы можем генерировать детали выполнения теста в XML-файле. Этот xml-файл в основном полезен в тех случаях, когда у нас есть панель мониторинга, которая проецирует результаты теста. В таких случаях XML может быть проанализирован, чтобы получить подробную информацию о выполнении.

Теперь мы выполним тесты из test_multiplcation.py и сгенерируем xml, запустив

pytest test_multiplication.py -v --junitxml="result.xml"

Теперь мы видим, что result.xml сгенерирован со следующими данными:

<?xml version = "1.0" encoding = "utf-8"?>
<testsuite errors = "0" failures = "1"
name = "pytest" skips = "0" tests = "4" time = "0.061">
   <testcase classname = "test_multiplication"          
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[1-11]"
      time = "0.00117516517639>
   </testcase>
   
   <testcase classname = "test_multiplication"    
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[2-22]"
      time = "0.00155973434448">
   </testcase>

   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072">
      failure message = "assert (11 * 3) == 35">num = 3, output = 35

         @pytest.mark.parametrize("num,
         output",[(1,11),(2,22),(3,35),(4,44)])
            
         def test_multiplication_11(num, output):> 
         assert 11*num == output
         E assert (11 * 3) == 35

         test_multiplication.py:5: AssertionErro
      </failure>
   </testcase>
   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[4-44]"
      time = "0.000945091247559">
   </testcase>
</testsuite>

Здесь тег <testsuit> суммирует, что было 4 теста и количество отказов 1.

  • Тег <testcase> дает подробную информацию о каждом выполненном тесте.

  • Тег <failure> дает подробную информацию о коде неудачного теста.

В этом руководстве по pytest мы рассмотрели следующие области:

  • Установка pytest ..
  • Определение тестовых файлов и тестовых функций.
  • Выполнение всех тестовых файлов с помощью pytest –v.
  • Выполнение определенного файла с использованием pytest <filename> -v.
  • Выполните тесты, сопоставив подстроку pytest -k <substring> -v.
  • Выполнять тесты на основе маркеров pytest -m <marker_name> -v.
  • Создание фикстур с использованием @ pytest.fixture.
  • conftest.py позволяет получить доступ к фикстурам из нескольких файлов.
  • Параметризация тестов с помощью @ pytest.mark.parametrize.
  • Xfailing тесты с использованием @ pytest.mark.xfail.
  • Пропуск тестов с использованием @ pytest.mark.skip.
  • Остановить выполнение теста при n сбоях, используя pytest --maxfail = <num>.
  • Параллельное выполнение тестов с использованием pytest -n <num>.
  • Генерация результатов xml с использованием pytest -v --junitxml = "result.xml".

Это руководство познакомило вас с фреймворком pytest. Теперь вы можете начать писать тесты с помощью pytest.

В качестве хорошей практики -

  • Создайте разные тестовые файлы на основе тестируемой функциональности / модуля.
  • Дайте содержательные имена тестовым файлам и методам.
  • Имейте достаточно маркеров, чтобы сгруппировать тесты по различным критериям.
  • При необходимости используйте приспособления.