Что такое PyTest?
Pytest — это среда тестирования, которая позволяет нам писать тестовые коды с использованием Python. Вы можете написать код для тестирования чего угодно, например, базы данных, API, даже пользовательского интерфейса, если хотите. Но pytest в основном используется в промышленности для написания тестов для API.
Зачем использовать PyTest?
Некоторые из преимуществ Pytest являются
- Очень легко начать с его простой синтаксис.
- Может запускать тесты параллельно.
- Может выполнить определенный тест или подмножество тестов
- Автоматически определять тесты
- Пропустить тесты
- Открытый исходный код
В этом уроке вы узнаете:
- Что такое PyTest?
- Зачем использовать PyTest?
- Как установить PyTest
- Первый Базовый PyTest
- Утверждения в PyTest
- Как pytest идентифицирует тестовые файлы и методы тестирования
- Запустите несколько тестов из определенного файла и нескольких файлов.
- Выполнить подмножество всего теста
- Выполнение тестов параллельно
- Pytest светильники
- Параметризованные тесты
- Xfail / Пропустить тесты
- XML результатов
- Фреймворк Pytest, тестирующий API
Как установить PyTest
Шаг 1) Вы можете установить pytest
pip install pytest==2.9.1
После завершения установки вы можете подтвердить это с помощью
py.test -h
Это отобразит помощь
Первый Базовый 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.
F говорит неудачу
Точка (.) Говорит об успехе.
В разделе сбоев вы можете увидеть сбойный метод (ы) и линию сбоя. Здесь x == y означает 5 == 6, что неверно.
Утверждения в PyTest
Утверждения — это проверки, которые возвращают статус True или False. В 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 идентифицирует только имена файлов, начиная с test_ или заканчивая _test в качестве тестовых файлов. Мы можем явно упомянуть другие имена файлов (объяснено позже). Pytest требует, чтобы имена тестовых методов начинались с «test ». Все остальные имена методов будут игнорироваться, даже если мы явно попросим запустить эти методы.
Смотрите некоторые примеры действительных и недействительных имен файлов 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 <filename>
py.test test_sample1.py
Выполнить подмножество всего теста
Иногда мы не хотим запускать весь набор тестов. Pytest позволяет нам запускать определенные тесты. Мы можем сделать это двумя способами
- Группировка имен тестов по совпадению подстроки
- Группировка тестов по маркерам
У нас уже есть 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-xdist, запустив
pip install pytest-xdist
Вы можете запустить тесты сейчас
py.test -n 4
-n <num> запускает тесты, используя несколько рабочих. В приведенной выше команде будет 4 рабочих для запуска теста.
Pytest светильники
Светильники используются, когда мы хотим запустить некоторый код перед каждым тестовым методом. Поэтому вместо того, чтобы повторять один и тот же код в каждом тесте, мы определяем приборы. Обычно приспособления используются для инициализации соединений с базой данных, передачи базы и т. Д.
Метод помечается как приспособление, отмечая
@pytest.fixture
В методе тестирования можно использовать прибор, упомянув прибор в качестве входного параметра.
Создайте новый файл 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. Этот метод вернет список из 3 значений.
- У нас есть 3 метода испытаний по сравнению с каждым из значений.
Каждая из тестовых функций имеет входной аргумент, имя которого совпадает с доступным прибором. Затем 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, а остальные 2 теста не пройдены.
Метод fixture имеет область действия только в том тестовом файле, в котором он определен. Если мы попытаемся получить доступ к устройству в каком-либо другом тестовом файле, мы получим сообщение об ошибке, в котором говорится, что устройство ‘supply_AA_BB_CC’ не найдено для методов тестирования в других файлах.
Чтобы использовать одно и то же устройство для нескольких тестовых файлов, мы создадим методы устройства в файле с именем conftest.py.
Давайте посмотрим на это на примере ниже. Создайте 3 файла 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.mark.parametrize.
Мы увидим это на примере ниже. Здесь мы передадим 3 аргумента в метод теста. Этот метод теста добавит первые 2 аргумента и сравнит его с третьим аргументом.
Создайте тестовый файл 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"
Здесь тестовый метод принимает 3 аргумента — input1, input2, output. Он добавляет input1 и input2 и сравнивает с выводом.
Давайте запустим тест по 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
Вы можете видеть, что тесты выполнялись 2 раза — одна проверка 5 + 5 == 10, а другая проверка 3 + 5 == 12
test_addition.py::test_add[5-5-10] PASSED
test_addition.py::test_add[3-5-12] FAILED
Xfail / Пропустить тесты
В некоторых ситуациях мы не хотим выполнять тест или тестовый случай не подходит для определенного времени. В этих ситуациях у нас есть возможность пропустить тест или пропустить тесты.
Испытание xfailed будет выполнено, но оно не будет засчитано как неудачные или пройденные испытания детали. В случае сбоя этого теста трассировка не будет отображаться. Мы можем xfail тесты, используя
@ Pytest.mark.xfail.
Пропуск теста означает, что тест не будет выполнен. Мы можем пропустить тесты, используя
@ 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 (в случае неудачи теста) или xpassed (при прохождении теста). Там не будет никаких следов отказов.
- test_add_5 и test_add_6 будут выполнены, и test_add_6 сообщит об ошибке с помощью traceback, пока test_add_5 пройдет
Выполните тест с помощью 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, которые мы можем передавать на серверы Continuous Integration для дальнейшей обработки и так далее. Это может быть сделано
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>
Из <testsuite errors = «0» failures = «1» name = «pytest» skips = «0» tests = «2» time = «0.046»> мы видим всего два теста, один из которых провалился. Ниже вы можете увидеть подробную информацию о каждом выполненном тесте в теге <testcase>.
Фреймворк Pytest, тестирующий API
Теперь мы создадим небольшую платформу Pytest для тестирования API. Используемый здесь API является бесплатным с https://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':'[email protected]','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':'[email protected]'} 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
Обновите тесты и попробуйте различные результаты
Резюме
В этом уроке по Pytest мы рассмотрели
- Установите pytest с помощью pip install pytest = 2.9.1
- Простая программа pytest и запустить ее с помощью команды py.test.
- Утверждения утверждения assert x == y вернут либо True, либо False.
- Как pytest определяет тестовые файлы и методы.
- Тестовые файлы, начинающиеся с test_ или заканчивающиеся _test
- Методы испытаний, начиная с теста
- Команда py.test запустит все тестовые файлы в этой папке и подпапках. Чтобы запустить определенный файл, мы можем использовать команду py.test <имя файла>
- Запустите подмножество методов тестирования
- Группировка имен тестов по совпадению подстрок.
py.test -k <name> -v запустит все тесты с именем <name>. - Запустите тестирование по маркерам. Отметьте тесты, используя @ pytest.mark. <Name>, и запустите тесты, используя pytest -m <name>, чтобы запустить тесты, помеченные как <name>.
- Группировка имен тестов по совпадению подстрок.
- Выполнять тесты параллельно
- Установите pytest-xdist с помощью pip install pytest-xdist
- Запустите тесты, используя py.test -n NUM, где NUM - количество рабочих
- Создание методов фикстуры для запуска кода перед каждым тестом, помечая метод @ pytest.fixture
- Область применения метода фикстуры находится в файле, в котором он определен.
- Доступ к методу фиксации можно получить через несколько тестовых файлов, определив его в файле conftest.py.
- Тестовый метод может получить доступ к устройству, используя его в качестве входного аргумента.
- Параметризация тестов для запуска с несколькими наборами входов.
@ pytest.mark.parametrize («input1, input2, output», [(5,5,10), (3,5,12)]) def test_add (input1, input2, output):
подтвердить ввод1 + input2 == выход , «провал»
запустит тест с входами (5,5,10) и (3,5,12) - Пропустить / xfail тесты, используя @ pytets.mark.skip и @ pytest.mark.xfail
- Создайте результаты теста в формате XML, который охватывает подробности выполненного теста, используя py.test test_sample1.py -v --junitxml = "result.xml"
- Пример платформы Pytest для тестирования API