Урок за 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 идентифицира само имената на файловете, започващи с тест_ или завършва с _тест като тестовите файлове. Въпреки това можем изрично да споменем други имена на файлове (обяснено по-късно). 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 ни позволява да изпълняваме специфични тестове. Можем да го направим по 2 начина
- Групиране на имена на тестове чрез съвпадение на подниз
- Групиране на тестове по маркери
Вече имаме 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
-н изпълнява тестовете с помощта на множество работници. В горната команда ще има 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 теста са неуспешни.
Методът на фиксиране има обхват само в този тестов файл, в който е дефиниран. Ако се опитаме да получим достъп до приспособлението в някой друг тестов файл, ще получим грешка, казваща приспособление '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>
от можем да видим общо два теста, единият от които е неуспешен. По-долу можете да видите подробности относно всеки изпълнен тест под етикет.
Pytest Framework Тестване на 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
Актуализирайте тестовете и опитайте различни резултати
Oбобщение
В този урок на PyTest разгледахме
- Инсталирайте pytest с помощта на pip инсталиране pytest=2.9.1
- Проста програма pytest и я стартирайте с команда py.test.
- Изявленията за твърдения, assert x==y, ще върнат True или False.
- Как pytest идентифицира тестови файлове и методи.
- Тестови файлове, започващи с тест_ или завършва с _тест
- Методи за изпитване, започващи с тест
- 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 fixture, като го използва като входен аргумент.
- Тестове за параметризиране, за да го изпълните срещу множество набори от входове.
@pytest.mark.parametrize(“вход1, вход2, изход”,[(5,5,10),(3,5,12)])
def test_add(вход1, вход2, изход):
assert input1+input2 == изход,”неуспешно”
ще изпълни теста с входове (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