Підручник PyTest: що таке, як встановити, фреймворк, твердження
Що таке PyTest?
PyTest це платформа тестування, яка дозволяє користувачам писати тестові коди за допомогою 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
Твердження Pytest — це перевірки, які повертають статус True або False. в Python Pytest, якщо твердження не вдається в тестовому методі, тоді виконання цього методу зупиняється там. Код, що залишився в цьому тестовому методі, не виконується, і твердження Pytest продовжаться з наступним тестовим методом.
Приклади Pytest Assert:
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 дозволяє нам запускати певні тести. Ми можемо зробити це двома способами
- Групування імен тестів за відповідністю підрядка
- Групування тестів за маркерами
У нас уже є 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) Виконайте тести за підрядком
Тут, щоб запустити всі тести, які мають метод1 в назві, нам потрібно виконати
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
-н запускає тести за допомогою кількох робітників. У наведеній вище команді буде 4 робочих для запуску тесту.
Світильники Pytest
Фікстури використовуються, коли ми хочемо запустити якийсь код перед кожним методом тестування. Тож замість того, щоб повторювати той самий код у кожному тесті, ми визначаємо фікстури. Зазвичай фікстури використовуються для ініціалізації підключень до бази даних, передачі бази тощо
Метод позначається як кріплення 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. Цей метод поверне список із 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.
Давайте побачимо це на прикладі PyTest нижче. Створіть 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
Метою параметризації тесту є запуск тесту проти кількох наборів аргументів. Ми можемо зробити це за допомогою @pytest.mark.parametrize.
Ми побачимо це на прикладі PyTest нижче. Тут ми передамо 3 аргументи тестовому методу. Цей метод перевірки додасть перші 2 аргументи та порівняє їх із 3-м аргументом.
Створіть тестовий файл 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] ПРОЙДЕНО
test_addition.py::test_add[3-5-12] ПОМИЛКА
Pytest Xfail / пропуск тестів
Будуть ситуації, коли ми не хочемо виконувати тест, або a тестовий випадок не є актуальним для певного часу. У таких ситуаціях у нас є можливість пройти тест 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. Ці тести виконуватимуться та будуть частиною тестів xfailed (у разі невдачі тесту) або xpassed (у разі проходження тесту). Зворотного відстеження помилок не буде.
- test_add_5 і test_add_6 буде виконано, а test_add_6 повідомить про помилку з відстеженням, поки 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>
Від ми можемо побачити загалом два тести, один із яких не пройдений. Нижче ви можете побачити подробиці щодо кожного виконаного тесту тег.
Тестування API Pytest Framework
Тепер ми створимо невелику структуру 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 встановити pytest=2.9.1
- Проста програма pytest і запустіть її за допомогою команди py.test.
- Твердження, assert x==y, повертатимуть True або False.
- Як pytest визначає тестові файли та методи.
- Тестові файли, починаючи з test_ або закінчуючи на _тест
- Методи випробувань починаючи з тест
- Команда py.test запустить усі тестові файли в цій папці та підпапках. Щоб запустити певний файл, ми можемо використати команду py.test
- Запустіть підмножину методів тестування
- Групування імен тестів за підрядком matching.py.test -k -v запустить усі тести, які мають у своїй назві.
- Запустіть тест маркерами. Позначте тести за допомогою @pytest.mark. і запустіть тести за допомогою pytest -m для виконання тестів, позначених як .
- Виконуйте тести паралельно
- Встановіть pytest-xdist за допомогою pip install pytest-xdist
- Запустіть тести за допомогою py.test -n NUM, де NUM – кількість працівників
- Створення методів фікстури для запуску коду перед кожним тестуванням шляхом позначення методу @pytest.fixture
- Область дії методу фікстури знаходиться в межах файлу, який він визначений.
- Доступ до методу фікстури можна отримати в кількох тестових файлах, визначивши його у файлі conftest.py.
- Тестовий метод може отримати доступ до фікстури Pytest, використовуючи його як вхідний аргумент.
- Тести параметризації, щоб запустити його з кількома наборами вхідних даних.
@pytest.mark.parametrize(“вхід1, вхід2, вихід”,[(5,5,10),(3,5,12)])
def test_add(вхід1, вхід2, вихід):
assert input1+input2 == output,”failed”
запустить тест із вхідними даними (5,5,10) і (3,5,12) - Тести Skip/xfail за допомогою @pytets.mark.skip і @pytest.mark.xfail
- Створіть результати тесту у форматі XML, який охоплює деталі виконаного тесту за допомогою py.test test_sample1.py -v –junitxml=”result.xml”
- Зразок фреймворку pytest для тестування API