Підручник з PyTest: Що таке PyTest? (з прикладами)
⚡ Розумний підсумок
Пайтест – це Python Фреймворк для тестування, який допомагає вам писати прості, масштабовані тести для баз даних, API та інтерфейсів користувача. Він підтримує фікстури, параметризацію, маркери, паралельні запуски та детальну звітність про твердження, від базових модульних тестів до складних функціональних тестів.
Пітест є одним з найбільш широко використовуваних фреймворків для тестування в Python екосистема. Він забезпечує коротке та читабельне виконання тестового коду, автоматично виявляє тести та перетворюється з першого твердження на повноцінний набір тестів для баз даних, API та інтерфейсів користувача. У розділах нижче описано встановлення, твердження, фікстури, маркери, параметризацію та паралельні запуски з практичними прикладами.
Що таке Пайтест?
Пітест це фреймворк для тестування, який дозволяє писати тестовий код, використовуючи Python мова програмування. Вона допомагає вам писати прості та масштабовані тестові випадки для баз даних, API та навіть інтерфейсів користувача. Pytest використовується переважно для написання тестів для API та масштабується від простих модульних тестів до складних функціональних тестів.
Чому варто використовувати Pytest?
Деякі переваги pytest:
- Дуже легко почати, завдяки простому та зрозумілому синтаксису.
- Можна запускати тести паралельно.
- Може запускати певний тест або підмножину тестів.
- Автоматично виявляє тести.
- Можна пропускати тести.
- Відкрите джерело.
Як встановити Pytest
Нижче наведено процес встановлення pytest:
Крок 1) Ви можете встановити pytest за допомогою команди нижче.
pip install pytest==2.9.1
Після завершення встановлення ви можете підтвердити це за допомогою наступної команди.
py.test -h
Це відобразить довідку.
Перший базовий приклад Pytest
Тепер ви дізнаєтеся, як використовувати 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, літера F означає невдачу, а крапка (.) – успіх. У розділі невдач ви можете побачити метод(и), що зазнав(ли) невдачі, та рядок невдачі. Тут 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 визначає лише імена файлів, що починаються з test_ або закінчуючи на _тест як тестові файли. Однак ми можемо явно згадати інші назви файлів (пояснення буде зроблено пізніше). 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 дозволяє нам запускати певні тести. Ми можемо зробити це двома способами:
- Гроуping назв тестів шляхом зіставлення підрядків.
- Гроуping тестів за допомогою маркерів.
У нас вже є файл 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
Тут, -n запускає тести, використовуючи кілька виконавців. У наведеній вище команді буде чотири виконавці для запуску тестів.
Світильники 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"
Тут тестовий метод приймає три аргументи: 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
Будуть деякі ситуації, коли ми не захочемо виконувати тест або тестовий випадок не є актуальним у певний момент часу. У таких ситуаціях ми маємо можливість xfailed (не пройшов тест) або пропустити тести. Тест xfailed (не пройшов тест) буде виконано, але він не буде врахований як частина невдалих або успішно складених тестів. Не буде жодних tracЯкщо тест не вдасться, відображається електронна пошта. Ми можемо завершити тести xfail за допомогою @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 (у разі успішного проходження тесту). Жодних тестів не буде. tracповернення коштів за невдачі.
- Будуть виконані test_add_5 та test_add_6, а test_add_6 повідомить про невдачу з tracповернення під час проходження 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, які потім можна буде передавати на сервери безперервної інтеграції для подальшої обробки. Це можна зробити за допомогою команди 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 Framework для тестування 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
Оновіть тести та спробуйте різні результати.





