Pytest是一个测试框架,允许我们使用python编写测试代码。 您可以编写代码以测试数据库,API等任何内容,甚至可以测试UI。 但是pytest主要在行业中用于编写API测试。
pytest的一些优点是
步骤1)您可以通过安装pytest
pip install pytest==2.9.1
安装完成后,您可以通过
py.test -h
进行确认。将显示帮助
第一个基本的PyTest
创建一个文件夹study_pytest。 我们将在此文件夹中创建测试文件。
请在命令行中导航到该文件夹。
在文件夹内创建一个名为test_sample1.py的文件
将以下代码添加到其中并保存
import pytest
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
assert x == y,"test failed"
def test_file1_method2():
x=5
y=6
assert x+1 == y,"test failed"
使用以下命令运行测试
py.test
您将获得输出为
test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed"
E AssertionError: test failed
E assert 5 == 6
test_sample1.py:6: AssertionError
Here in test_sample1.py F.
F says failure
Dot(.) says success.
在“失败”部分中,您可以查看失败的方法和失败行。 x == y表示5 == 6,这是错误的。
断言是返回True或False状态的检查。 在pytest中,如果断言在测试方法中失败,则该方法的执行在那里停止。 该测试方法中的其余代码不会执行,并且pytest将继续使用下一个测试方法。
Examples:
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: test failed x=5 y=6
默认情况下,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.pytest_file1_method1()test_file1_method2()
test_sample2.pytest_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 ===============================
在这里,您可以看到最后由’-kmethod1’取消选择的2个测试,分别是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.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"
这里
每个测试函数都有一个输入自变量,其名称与可用的夹具匹配。 然后Pytest调用相应的Fixture方法,返回的值将存储在输入参数中,此处为列表[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 =================================
由于zz = BB = 35,所以已通过test test_comparewithBB,其余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个参数传递给测试方法。 此测试方法将添加前两个参数,并将其与第三个参数进行比较。
使用以下代码创建测试文件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个参数-输入1,输入2,输出。 它将输入1和输入2相加并与输出进行比较。
让我们通过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]失败
在某些情况下,我们不想执行测试,或者在特定时间内测试案例不相关。 在这种情况下,我们可以选择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"
这里
通过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格式的测试结果,然后将其提供给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。 这里使用的API是https://reqres.in/中的免费API。 该网站仅提供可测试的API。 该网站不存储我们的数据。
在这里,我们将为
列出一些用户
与用户登录
使用给出的代码创建以下文件
conftest.py-有一个夹具,可以为所有测试方法提供基本网址
import pytest
@pytest.fixture
def supply_url():
return "https://reqres.in/api"
test_list_user.py –包含列出有效和无效用户的测试方法
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 –包含用于测试登录功能的测试方法。
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.mark.parametrize(“ input1,input2,output”,[((5,5,10),(3,5,12)])def test_add(input1,input2,output):
断言input1 + input2 ==输出,“失败”
将使用输入(5,5,10)和(3,5,12)运行测试