Учебники

60) Учебник по PyTest

Что такое PyTest?

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

Зачем использовать PyTest?

Некоторые из преимуществ Pytest являются

  • Очень легко начать с его простой синтаксис.
  • Может запускать тесты параллельно.
  • Может выполнить определенный тест или подмножество тестов
  • Автоматически определять тесты
  • Пропустить тесты
  • Открытый исходный код

В этом уроке вы узнаете:

Как установить 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':'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

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

Резюме

В этом уроке по 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