Учебное пособие по PyTest: Что такое PyTest? (с примерами)
⚡ Умное резюме
Pytest — это Python Фреймворк для тестирования, помогающий писать простые, масштабируемые тесты для баз данных, API и пользовательских интерфейсов. Он поддерживает фикстуры, параметризацию, маркеры, параллельное выполнение и подробные отчеты об утверждениях, от базовых модульных тестов до сложных функциональных тестов.

Питест является одним из наиболее широко используемых фреймворков для тестирования. Python экосистема. Она обеспечивает краткость и читаемость тестового кода, автоматически обнаруживает тесты и развивается от первого утверждения до полноценного набора тестов для баз данных, API и пользовательских интерфейсов. В разделах ниже подробно описаны установка, утверждения, фикстуры, маркеры, параметризация и параллельное выполнение с практическими примерами.
Что такое Pytest?
Питест Это фреймворк для тестирования, который позволяет писать тестовый код, используя Python Язык программирования. Он помогает писать простые и масштабируемые тестовые сценарии для баз данных, API и даже пользовательских интерфейсов. Pytest используется в основном для написания тестов для API и масштабируется от простых модульных тестов до сложных функциональных тестов.
Почему стоит использовать Pytest?
К преимуществам pytest относятся, например, следующие:
- С ним очень легко начать работать благодаря простому и понятному синтаксису.
- Может запускать тесты параллельно.
- Можно запустить конкретный тест или подмножество тестов.
- Автоматически обнаруживает тесты.
- Можно пропускать тесты.
- Открытый исходный код.
Как установить Pytest
Ниже описан процесс установки pytest:
Шаг 1) Установить pytest можно с помощью приведенной ниже команды.
pip install pytest==2.9.1
После завершения установки вы можете подтвердить её с помощью следующей команды.
py.test -h
Это отобразит справку.
Первый базовый пример pytest
Теперь вы узнаете, как использовать pytest на простом примере.
Создайте папку study_pytest. В этой папке вы будете создавать тестовые файлы. Перейдите в эту папку в командной строке. Создайте в этой папке файл с именем test_sample1.py.
Вставьте приведенный ниже код и сохраните.
import pytest def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed" def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
Запустите тест, используя команду ниже.
py.test
В результате вы получите следующий результат.
test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed"
E AssertionError: test failed
E assert 5 == 6
test_sample1.py:6: AssertionError
В файле test_sample1.py буква F обозначает ошибку, а точка (.) — успех. В разделе failures вы можете увидеть неудачно выполненный метод (или методы) и строку ошибки. Здесь x==y означает 5==6, что неверно.
Далее вы узнаете об утверждениях в pytest.
Утверждения в pytest
Утверждения в pytest — это проверки, которые возвращают либо статус True, либо False. В pytest, если утверждение в тестовом методе не выполняется, выполнение этого метода останавливается. Оставшийся код в этом тестовом методе не выполняется, и pytest переходит к следующему тестовому методу.
Примеры утверждений pytest:
assert "hello" == "Hai" is an assertion failure. assert 4==4 is a successful assertion assert True is a successful assertion assert False is an assertion failure.
Обратите внимание на следующее.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Вместо приведенного ниже утверждения разместите этот код в функции test_file1_method1().
assert x == y,"test failed"
Запуск теста приведет к ошибке, поскольку AssertionError: тест не пройден x=5 y=6.
Как PyTest идентифицирует тестовые файлы и тестовые методы
По умолчанию pytest распознает только имена файлов, начинающиеся с определенного префикса. тестовое задание_ или заканчивая _контрольная работа в качестве тестовых файлов. Однако мы можем явно указать и другие имена файлов (объяснение позже). Pytest требует, чтобы имена тестовых методов начинались с тестВсе остальные имена методов игнорируются, даже если мы явно запрашиваем их выполнение.
Ознакомьтесь с примерами допустимых и недопустимых имен файлов pytest.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Примечание: Да, мы можем явно указать pytest выбрать файлы testlogin.py и logintest.py.
Ознакомьтесь с примерами допустимых и недопустимых методов тестирования pytest.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Примечание: Даже если мы явно укажем file1_method1(), pytest не выполнит этот метод.
Запуск нескольких тестов из определенного файла и нескольких файлов
В настоящее время в папке study_pytest находится файл test_sample1.py. Предположим, у нас есть несколько файлов, скажем, test_sample2.py и test_sample3.py. Чтобы запустить все тесты из всех файлов в папке и подпапках, нам нужно просто выполнить команду pytest.
py.test
Эта команда выполнит поиск по всем файлам, имена которых начинаются с test_ и заканчиваются на _test, как в этой папке, так и в подпапках внутри неё.
Для запуска тестов только из определенного файла можно использовать `py.test`. .
py.test test_sample1.py
Запустите подмножество всего теста с помощью pytest.
Иногда нам не нужно запускать весь набор тестов. PyTest позволяет запускать только определенные тесты. Это можно сделать двумя способами:
- Grouping названия тестов путем сопоставления подстрок.
- Grouping тестов с использованием маркеров.
У нас уже есть файл test_sample1.py. Создайте файл test_sample2.py и добавьте в него следующий код.
def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Таким образом, на данный момент у нас имеется следующая информация.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Вариант 1) Запуск тестов путем сопоставления подстрок.
Чтобы запустить все тесты, в названии которых содержится method1, необходимо выполнить следующую команду.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Таким образом, выполнение команды `py.test -k method1 -v` даст следующий результат.
test_sample2.py::test_file2_method1 FAILED
test_sample1.py::test_file1_method1 FAILED
============================================== FAILURES ==============================================
_________________________________________ test_file2_method1 _________________________________________
def test_file2_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample2.py:5: AssertionError
_________________________________________ test_file1_method1 _________________________________________
@pytest.mark.only
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample1.py:8: AssertionError
================================= 2 tests deselected by '-kmethod1' ==================================
=============================== 2 failed, 2 deselected in 0.02 seconds ===============================
Здесь, ближе к концу, вы можете увидеть... 2 теста отменены с помощью '-kmethod1', которыми являются test_file1_method2 и test_file2_method2.
Попробуйте запустить программу с различными комбинациями параметров, например, следующими.
py.test -k method -v - will run all the four methods py.test -k methods -v – will not run any test as there is no test name matches the substring 'methods'
Вариант 2) Запустить тесты по маркерам
PyTest позволяет задавать различные атрибуты для тестовых методов с помощью маркеров pytest, @pytest.mark. Чтобы использовать маркеры в тестовом файле, необходимо импортировать pytest в тестовые файлы. Здесь мы применим разные имена маркеров к тестовым методам и будем запускать определенные тесты в зависимости от этих имен. Мы можем определить маркеры для каждого имени теста, используя следующий код.
@pytest.mark.<name>.
Мы определяем маркеры set1 и set2 для тестовых методов и будем запускать тесты, используя имена этих маркеров. Обновите тестовые файлы следующим кодом.
test_sample1.py
import pytest @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set2 def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
test_sample2.py
import pytest @pytest.mark.set1 def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set1 def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Мы можем запустить отмеченный тест с помощью приведенной ниже команды.
py.test -m <name> -m <name> mentions the marker name
Запустите py.test -m set1. Это запустит методы test_file1_method1, test_file2_method1 и test_file2_method2. Запуск py.test -m set2 запустит метод test_file1_method2.
Запуск тестов параллельно с помощью pytest
Обычно набор тестов содержит множество тестовых файлов и сотни тестовых методов, выполнение которых занимает значительное количество времени. Pytest позволяет запускать тесты параллельно. Для этого необходимо сначала установить pytest-xdist, выполнив команду, указанную ниже.
pip install pytest-xdist
Теперь вы можете запустить тесты с помощью следующей команды.
py.test -n 4
Здесь, -н Тесты запускаются с использованием нескольких рабочих процессов. В приведенной выше команде для выполнения тестов будет задействовано четыре рабочих процесса.
Pytest Светильники
Фикстуры используются, когда мы хотим выполнить некоторый код перед каждым тестовым методом. Таким образом, вместо того, чтобы повторять один и тот же код в каждом тесте, мы определяем фикстуры. Обычно фикстуры используются для инициализации подключений к базе данных, передачи базовых данных. URLи так далее. Метод помечается как фикстура pytest путем добавления к нему следующей маркировки.
@pytest.fixture
Метод тестирования может использовать фикстуру pytest, указав её в качестве входного параметра. Создайте новый файл test_basic_fixture.py со следующим кодом.
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc] def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Вот:
- У нас есть фикстура с именем supply_AA_BB_CC. Этот метод возвращает список из трех значений.
- Мы используем три метода тестирования, сравнивая результаты с каждым из значений.
Каждая из тестовых функций имеет входной аргумент, имя которого соответствует доступному фикстурному файлу. Затем Pytest вызывает соответствующий метод фикстурного файла, и возвращаемые значения сохраняются во входном аргументе, в данном случае это список [25,35,45]. Элементы списка затем используются в тестовых методах для сравнения. Теперь запустите тест и посмотрите результат.
py.test test_basic_fixture
test_basic_fixture.py::test_comparewithAA FAILED
test_basic_fixture.py::test_comparewithBB PASSED
test_basic_fixture.py::test_comparewithCC FAILED
============================================== FAILURES ==============================================
_________________________________________ test_comparewithAA _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithAA(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"
E AssertionError: aa and zz comparison failed
E assert 25 == 35
test_basic_fixture.py:10: AssertionError
_________________________________________ test_comparewithCC _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithCC(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
E AssertionError: cc and zz comparison failed
E assert 45 == 35
test_basic_fixture.py:16: AssertionError
================================= 2 failed, 1 passed in 0.05 seconds =================================
Тест test_comparewithBB проходит успешно, поскольку zz=BB=35, а два оставшихся теста не проходят.
Метод fixture имеет область видимости только в пределах тестового файла, где он определен. Если мы попытаемся получить доступ к fixture в другом тестовом файле, мы получим ошибку, указывающую на наличие fixture. 'supply_AA_BB_CC' не найден для методов тестирования в других файлах.
Чтобы использовать один и тот же фикстурный набор данных для нескольких тестовых файлов, мы создаём методы фикстуры в файле с именем conftest.py. Рассмотрим это на примере pytest, приведённом ниже. Создайте три файла: conftest.py, test_basic_fixture.py и test_basic_fixture2.py, содержащие следующий код.
conftest.py
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc]
test_basic_fixture.py
import pytest def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
test_basic_fixture2.py
import pytest def test_comparewithAA_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Pytest сначала будет искать фикстуру в тестовом файле, и если она не будет найдена, то будет искать её в файле conftest.py. Запустите тест с помощью команды `py.test -k test_comparewith -v`, чтобы получить результат, показанный ниже.
test_basic_fixture.py::test_comparewithAA FAILED test_basic_fixture.py::test_comparewithBB PASSED test_basic_fixture.py::test_comparewithCC FAILED test_basic_fixture2.py::test_comparewithAA_file2 PASSED test_basic_fixture2.py::test_comparewithBB_file2 FAILED test_basic_fixture2.py::test_comparewithCC_file2 FAILED
Параметризованный тест Pytest
Цель параметризации теста — запустить тест с несколькими наборами аргументов. Это можно сделать с помощью `@pytest.mark.parametrize`. Мы увидим это на примере pytest ниже. Здесь мы передадим три аргумента методу теста. Этот метод теста сложит первые два аргумента и сравнит результат с третьим аргументом. Создайте файл теста test_addition.py со следующим кодом.
import pytest @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)]) def test_add(input1, input2, output): assert input1+input2 == output,"failed"
Здесь метод test принимает три аргумента: input1, input2 и output. Он складывает input1 и input2 и сравнивает сумму с output. Давайте запустим тест с помощью команды `py.test -k test_add -v` и посмотрим результат.
test_addition.py::test_add[5-5-10] PASSED
test_addition.py::test_add[3-5-12] FAILED
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add(input1, input2, output):
> assert input1+input2 == output,"failed"
E AssertionError: failed
E assert (3 + 5) == 12
test_addition.py:5: AssertionError
Как видите, тесты проводились дважды: один раз проверялось, равно ли 5+5 ==10, а другой — равно ли 3+5 ==12.
test_addition.py::test_add[5-5-10] ПРОЙДЕНО
test_addition.py::test_add[3-5-12] ОШИБКА
pytest Xfail и Skip Tests
В некоторых ситуациях мы не захотим выполнять тест, или же... прецедент Это не имеет значения в конкретный момент времени. В таких ситуациях у нас есть возможность либо провалить тест, либо пропустить тесты. Проваленный тест будет выполнен, но он не будет учитываться как среди пройденных или проваленных тестов. Не будет никаких дополнительных действий. tracЕсли тест не пройден, отобразится сообщение eback. Мы можем отключать тесты с помощью @pytest.mark.xfail. Пропуститьping Отметка «тест» означает, что тест не будет выполнен. Мы можем пропускать тесты, используя @pytest.mark.skip. Отредактируйте файл test_addition.py, добавив следующий код.
import pytest @pytest.mark.skip def test_add_1(): assert 100+200 == 400,"failed" @pytest.mark.skip def test_add_2(): assert 100+200 == 300,"failed" @pytest.mark.xfail def test_add_3(): assert 15+13 == 28,"failed" @pytest.mark.xfail def test_add_4(): assert 15+13 == 100,"failed" def test_add_5(): assert 3+2 == 5,"failed" def test_add_6(): assert 3+2 == 6,"failed"
Вот:
- test_add_1 и test_add_2 пропускаются и не будут выполнены.
- Тесты test_add_3 и test_add_4 завершились с ошибкой (xfailed). Эти тесты будут выполнены и войдут в состав тестов, завершившихся с ошибкой (xfailed) или прошедших тест (xpassed). Никаких дополнительных тестов не будет. traceback для устранения неполадок.
- Будут выполнены функции test_add_5 и test_add_6, при этом test_add_6 сообщит о сбое. traceback while test_add_5 passes.
Выполните тест с помощью команды `py.test test_addition.py -v` и посмотрите результат.
test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED
============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
def test_add_6():
> assert 3+2 == 6,"failed"
E AssertionError: failed
E assert (3 + 2) == 6
test_addition.py:24: AssertionError
================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================
Результаты в формате XML
Мы можем создавать результаты тестирования в формате XML, которые затем можно передавать на серверы непрерывной интеграции для дальнейшей обработки. Это можно сделать с помощью команды `py.test test_sample1.py -v –junitxml=”result.xml”`. В файле `result.xml` будет записан результат выполнения теста. Пример файла `result.xml` приведен ниже.
<?xml version="1.0" encoding="UTF-8"?> <testsuite errors="0" failures="1" name="pytest" skips="0" tests="2" time="0.046"> <testcase classname="test_sample1" file="test_sample1.py" line="3" name="test_file1_method1" time="0.001384973526"> <failure message="AssertionError:test failed because x=5 y=6 assert 5 ==6"> @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" > assert x == y,"test failed because x=" + str(x) + " y=" + str(y) E AssertionError: test failed because x=5 y=6 E assert 5 == 6 test_sample1.py:9: AssertionError </failure> </testcase> <testcase classname="test_sample1" file="test_sample1.py" line="10" name="test_file1_method2" time="0.000830173492432" /> </testsuite>
От Мы видим, что было проведено два теста, один из которых не прошёл. Ниже вы можете увидеть подробную информацию о каждом выполненном тесте. ярлык.
Фреймворк pytest для тестирования API
Теперь мы создадим небольшой фреймворк pytest для тестирования API. В качестве API здесь используется бесплатный сервис. reqres.inЭтот веб-сайт просто предоставляет тестируемый API и не хранит наши данные. Здесь мы напишем несколько тестов для следующих пунктов:
- Приведён список некоторых пользователей.
- Вход с использованием учетных записей пользователей.
Создайте файлы, указанные ниже, с предоставленным кодом. Во-первых, в файле conftest.py есть фикстура, которая будет предоставлять базовые данные. URL для всех методов тестирования.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Далее, файл test_list_user.py содержит тестовые методы для вывода списка действительных и недействительных пользователей.
- Функция test_list_valid_user проверяет корректность запроса пользователя и подтверждает правильность ответа.
- Функция test_list_invaliduser проверяет наличие некорректного запроса пользователя и подтверждает полученный ответ.
import pytest import requests import json @pytest.mark.parametrize("userid, firstname",[(1,"George"),(2,"Janet")]) def test_list_valid_user(supply_url,userid,firstname): url = supply_url + "/users/" + str(userid) resp = requests.get(url) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['data']['id'] == userid, resp.text assert j['data']['first_name'] == firstname, resp.text def test_list_invaliduser(supply_url): url = supply_url + "/users/50" resp = requests.get(url) assert resp.status_code == 404, resp.text
Затем файл test_login_user.py содержит тестовые методы для проверки функциональности авторизации.
- Функция test_login_valid проверяет корректность попытки входа в систему с использованием адреса электронной почты и пароля.
- Функция test_login_no_password проверяет неверную попытку входа в систему без передачи пароля.
- Функция test_login_no_email проверяет некорректную попытку входа в систему без передачи адреса электронной почты.
import pytest import requests import json def test_login_valid(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com','password':'something'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['token'] == "QpwL5tke4Pnpja7X", resp.text def test_login_no_password(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing password", resp.text def test_login_no_email(supply_url): url = supply_url + "/login/" data = {} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing email or username", resp.text
Запустите тест с помощью команды `py.test -v` и посмотрите результат, который вы увидите ниже.
test_list_user.py::test_list_valid_user[1-George] PASSED test_list_user.py::test_list_valid_user[2-Janet] PASSED test_list_user.py::test_list_invaliduser PASSED test_login_user.py::test_login_valid PASSED test_login_user.py::test_login_no_password PASSED test_login_user.py::test_login_no_email PASSED
Обновите тесты и попробуйте различные варианты результатов.




