官方pytest文档:Full pytest documentation — pytest documentation


一、pytest以及辅助插件的安装

1、pytest安装

pip install pytest

2、辅助插件及介绍

pytest-html(生成htm报告的插件)
pytest-xdist (多线程运行的插件)
pytest-ordering (改变用例的执行顺序的插件)
pytest-rerunfailres (失败用例重跑的插件)
allure-pytest (生成美观自定义的allure报告)pytest test_*.py --alluredir=result + allure serve result
        allure generate --alluredir=result -o report/ --clean
pytest-rerunfailures (失败重跑插件)
pytest-repeat   (重复执行用例的插件)
pytest-assume  (assume多重断言,断言失败后边的代码会继续执行)

//插件太多,可通过pip install -r pludin.txt一次性下载完成
例如:plugin.txt中内容如下
pytest-html
pytest-xdist
终端运行:pip install -r pugin.txt即可下载这两个插件

3、安装插件报错

1)下面是没有安装相应插件报错,例:没有安装allure-pytest插件,运行pytest --alluredir=./temp 会报此错

ERROR: usage: run.py [options] [file_or_dir] [file_or_dir] [...]
run.py: error: unrecognized arguments: --alluredir=./reports/temp/ --clean-alluredir
inifile: C:\Users\EDY\PycharmProjects\ProductAuto\pytest.ini
rootdir: C:\Users\EDY\PycharmProjects\ProductAuto

二、pytest运行

测试套件默认规则:测试文件必须以test_*开头,测试类名字必须是Test*开头,测试方法名字必须是test_*开头,否则运行时会找不到测试套件

pytest命令的参数:

-vs        输出详细信息。输出调试信息。如: pytest -vs
-n      多线程运行。( 前提安装插件: pytest-xdist)如: pytest -VS -n=2
--reruns 失败重跑(前提安装插件: pytest-rerunfailres )如: pytest --reruns=2
   raise Exception()抛出异常,try except解决异常。
-x     出现一个用例失败则停止测试。如: pytest -x
--maxfail 出现几个失败才终止,如: pytest --maxfail=2
--html   生成html的测试报告(前提安装插件: pytest-html) , 如: pytest --html=report/report.html
-k     运行测试用例名称中包含某个字符串的测试用例。
-m     只测试被标记的用例
--strict-markers 未在pytest.ini配置文件注册的标记mark都会引发报错
--reruns n  (安装pytest-rerunfailures插件),失败重跑n次,最大运行次数n pytest --reruns 5
--reruns-delay n )(pytest-rerunfailures插件),pytest --rerun 2 --reruns-delay 5 失败重跑之间间隔5s
--count    重复执行测试,需安装pytest-repeat,使用:pytest --count=5或者pytest --count 5
        重复执行所有测试用例5次,主要结合-x使用,测试偶发bug,运行直到执行失败
        还有@pytest.mark.repeat(n)可对测试类、方法使用

有两种执行方式:

1)命令行运行:pytest -vs -x

2)主函数运行:

if __name__ == "__main__":
pytest.main(["-vs”,“-x"])

指定运行:

pytest  test_*.py    //运行指定的test_*.py ,多个py文件分号隔开
pytest testcase/ //运行指定的testcase目录下所有测试文件
pytest -k "cs” //测试包含有“cs”的测试类和方法
pytest testcase/test_*.py::Test_lei::test_* //可具体指定某个方法

测试执行结果:

  • 退出code 0: 收集并成功通过所有测试用例
  • 退出code 1: 收集并运行了测试,部分测试用例执行失败
  • 退出code 2: 测试执行被用户中断
  • 退出code 3: 执行测试中发生内部错误
  • 退出code 4: pytest命令行使用错误
  • 退出code 5: 没有收集到测试用例

全局配置文件pytest.ini,一般建在项目根目录下

注意:

1)这个配置文件的名称不能改

2)编码格式是ANSI,写入中文后自动变为GB2312 中文简体

3)pytest.main()会带上其中配置的参数

pytest.ini配置文件内容解释,实际中根据需求可选择性添加配置。

[pytest]

//pytest参数配置
addopts = -VS

//需要执行用例模块目录
testpaths =./testcases

//可更改测试文件、类、方法的默认规则,一般不做更改
python_files = test_*.py
python_classes = Test*
python_functions = test_*

//标记用例,一般用于冒烟测试,以下的smoke是标签名,”冒烟测试“是描述,使用
markers=
  smoke:"冒烟测试"
  flow:"流水"

//与@pytest.mark.xfail()的strict一样,为False,意外pass的结果显示xpass,为True,则显示Failed
xfail_strict = False

//控制台日志输出控制器,为True输出,为False关闭
log_cli = True

//用来加速收集用例,去掉不要谝历的目录
norecursedirs = * (目录)

断言类型

1)assert正常断言

def test_001(self):  
   assert var==3  //判断var等于3
   assert var in ‘ab’  //判断字符串‘ab’包含var
   assert var is False  //判断var 为假
   assert var not True  //判断var不为真
   assert var  //判断var为真

2)pytest.raise()异常断言

def test_002(self):
with pytest.raises(ZeroDivisionError):
1/0

3)pytest.warns()警示断言

暂没有合适例子

临时目录

1)项目运行过程中,总会产生一些临时文件,pytest提供了tmp_path来创建临时目录。

tmp_path是一个pathlib/pathlib2.Path对象。以下是测试使用方法的示例如:

# test_tmp_path.py文件内容
import os CONTENT = u"content" def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT)
assert p.read_text() == CONTENT
assert len(list(tmp_path.iterdir())) == 1
assert 0

测试结果如下:除了assert0以外,其他都断言成功
_____________________________ test_create_file _____________________________ tmpdir = local('PYTEST_TMPDIR/test_create_file0') def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
p.write("content")
assert p.read() == "content"
assert len(tmpdir.listdir()) == 1
> assert 0
E assert 0

2)tmpdir_factory

四、前置和后置

前置和后置方法如下:

  • 模块级别:setup_module、teardown_module
  • 函数级别:setup_function、teardown_function,不在类中的方法
  • 类级别:setup_class、teardown_class
  • 方法级别:setup_method、teardown_method
  • 方法细化级别:setup、teardown
def setup_module():
print("=====整个.py模块开始前只执行一次:打开浏览器=====") def teardown_module():
print("=====整个.py模块结束后只执行一次:关闭浏览器=====") def setup_function():
print("===每个函数级别用例开始前都执行setup_function===") def teardown_function():
print("===每个函数级别用例结束后都执行teardown_function====") def test_one():
print("one") class TestCase():
def setup_class(self):
print("====整个测试类开始前只执行一次setup_class====") def teardown_class(self):
print("====整个测试类结束后只执行一次teardown_class====") def setup_method(self):
print("==类里面每个用例执行前都会执行setup_method==") def teardown_method(self):
print("==类里面每个用例结束后都会执行teardown_method==") def setup(self):
print("=类里面每个用例执行前都会执行setup=") def teardown(self):
print("=类里面每个用例结束后都会执行teardown=") def test_three(self):
print("two")

五、fixtrue实现前后置

@pytest.fixtrue(scope="xxx")和yeild实现前置和后置

1)参数scope的五个范围级别:fuction(默认)、class、module、package、session,实例化顺序级别从右至左

2)类调用fixtrue固件只能通过@pytest.mark.usefixtrues("aaa","bbb"),aaa和bbb均为函数名

3)方法能通过参数调用,如def aaa(bbb),aaa为调用函数,bbb为固件函数名,也能使用@pytest.mark.usefixtrues("aaa","bbb")调用

具体效果如下:

//不调用则不显示
@pytest.fixture()
def ttt():
print("fuction-setup4") @pytest.fixture()
def aaa():
print("fuction-setup1") @pytest.fixture()
def bbb():
print("fuction-setup2") @pytest.fixture(scope="class")
def ccc():
print("setup") //调用程序前执行
yield //后面的的方法体相当于后置
print("teardown") //调用程序后执行 //类调用fuction范围级别固件bbb,相当于类里面的每个方法都调用bbb
@pytest.mark.usefixtures("bbb")
//类调用class级别固件ccc,只在调用的类前执行一次
@pytest.mark.usefixtures("ccc")
class Test_study: def test_ddd(self,aaa): //先bbb输出,再aaa输出
print("AAAAAA") @pytest.mark.smoke
def test_eee(self):
print("BBBBBB") -------------------结果如下-------------------------------- kuadi/test_kuadi_login.py::Test_study::test_ddd setup
fuction-setup2
fuction-setup1
AAAAAA
PASSED
kuadi/test_kuadi_login.py::Test_study::test_eee fuction-setup2
BBBBBB
PASSEDteardown

4)其他调用情况

@pytest.fixture(scope="class")
def aaa():
print("aaa_start")
yield
print("aaa_end") @pytest.fixture()
def bbb():
print("bbb_start")
yield
print("bbb_end") @pytest.mark.usefixtures("bbb")
def test_fff():
print("fff") class Test_QQQ:
@pytest.mark.usefixtures("bbb")
def test_ccc(self):
print("ccc") def test_ddd(self, aaa):
print("ddd") def test_eee(self):
print("eee") 结果------------
fds.py::test_fff bbb_start
fff
PASSEDbbb_end fds.py::Test_QQQ::test_ccc bbb_start
ccc
PASSEDbbb_end fds.py::Test_QQQ::test_ddd aaa_start
ddd
PASSED
fds.py::Test_QQQ::test_eee eee
PASSEDaaa_end

5)yield和addfinalizer函数的使用

//yield配合fixtrue实现前后置方法
@pytest.fixture(scope="class")
def ccc():
print("setup")
yield
print("teardown") //with配合yield,等待测试完成后,自动关闭连接
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection //通过后置操作在测试完成后关闭浏览器连接
@pytest.fixture(scope="module")
def test_addfinalizer(request):
# 前置操作setup
print("==再次打开浏览器==")
test = "test_addfinalizer" def fin():
# 后置操作teardown
print("==再次关闭浏览器==") request.addfinalizer(fin)
# 返回前置操作的变量
return test

六、conftest文件

conftest文件注意事项:

  • pytest会默认读取conftest.py里面的所有fixture,所以不用导入fixtrue
  • conftest.py 文件名称是固定的,不能改动
  • conftest.py中fixtrue只对同一个package下的所有测试用例生效,其他目录中引入会报错not found
  • 不同目录可以有自己的conftest.py,一个项目中可以有多个conftest.py
  • 下级目录的conftest中fixtrue可以覆盖重写上级目录中同名的fixtrue,且就近生效
  • 测试用例文件中不需要手动import conftest.py,pytest会自动查找

七、参数化

@pytest.mark.parametrize()

官方文档:How to parametrize fixtures and test functions — pytest documentation

用法如下:

//测试方法和函数使用
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
//测试类使用
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
//模块全局使用
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)]) class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
//mark标记使用
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
//叠加使用
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
print({x},{y}) 结果按顺序:0,2/1,2/0.3/1,3

@pytest.fixtrue()

第一个例子
# content of conftest.py
import pytest
import smtplib @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print("finalizing {}".format(smtp_connection))
smtp_connection.close()
//第二个例子
@pytest.fixture(params=[0, 1], ids=["spam", "ham"])
def a(request):
return request.param def test_a(a):
pass def idfn(fixture_value):
if fixture_value == 0:
return "eggs"
else:
return None @pytest.fixture(params=[0, 1], ids=idfn)
def b(request):
return request.param def test_b(b):
pass 结果-------------------------------------------------
<Function test_a[spam]>
<Function test_a[ham]>
<Function test_b[eggs]>
<Function test_b[1]>

@pytest.fixtrue带标记

@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
return request.param def test_data(data_set):
pass

fixtrue中使用fixtrue:smtp_connection()是第一个用例中fixtrue

class App:
def __init__(self, smtp_connection):
self.smtp_connection = smtp_connection @pytest.fixture(scope="module")
def app(smtp_connection):
return App(smtp_connection) def test_smtp_connection_exists(app):
assert app.smtp_connection

按fix true实例自动对测试进行分组

@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
param = request.param
print(" SETUP modarg", param)
yield param
print(" TEARDOWN modarg", param) @pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
param = request.param
print(" SETUP otherarg", param)
yield param
print(" TEARDOWN otherarg", param) def test_0(otherarg):
print(" RUN test0 with otherarg", otherarg) def test_1(modarg):
print(" RUN test1 with modarg", modarg) def test_2(otherarg, modarg):
print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg)) --------------------------------结果-------------------------------
collecting ... collected 8 items test_module.py::test_0[1] SETUP otherarg 1
RUN test0 with otherarg 1
PASSED TEARDOWN otherarg 1 test_module.py::test_0[2] SETUP otherarg 2
RUN test0 with otherarg 2
PASSED TEARDOWN otherarg 2 test_module.py::test_1[mod1] SETUP modarg mod1
RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
PASSED TEARDOWN otherarg 1 test_module.py::test_2[mod1-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
PASSED TEARDOWN otherarg 2 test_module.py::test_1[mod2] TEARDOWN modarg mod1
SETUP modarg mod2
RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
PASSED TEARDOWN otherarg 1 test_module.py::test_2[mod2-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
PASSED TEARDOWN otherarg 2
TEARDOWN modarg mod2

测试功能不需要直接访问fixtrue时,如建一个临时目录或者文件供测试使用,结合@pytest.markusefixtrues()

# content of conftest.py 

import os
import tempfile import pytest @pytest.fixture
def cleandir():
with tempfile.TemporaryDirectory() as newpath:
old_cwd = os.getcwd()
os.chdir(newpath)
yield
os.chdir(old_cwd) ------------------------------------
# content of test_setenv.py
import os
import pytest @pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open("myfile", "w") as f:
f.write("hello") def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
//其他用法
@pytest.mark.usefixtures("cleandir", "anotherfixture") 指定多个fix true
def test():
pass //使用pytestmark在测试模块级别指定夹具的用途
pytestmark = pytest.mark.usefixtures("cleandir") //将项目中所有测试所需的夹具放入ini文件中
[pytest]
usefixtures = cleandir

结合@pytest.mark.parametrize()使用,fixtrue作测试数据处理功能,如:factory

@pytest.fixture(scope="function")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user @pytest.fixture(scope="function")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw name = ["name1", "name2"]
pwd = ["pwd1", "pwd2"] @pytest.mark.parametrize("input_user", name, indirect=True)
@pytest.mark.parametrize("input_psw", pwd, indirect=True)
def test_more_fixture(input_user, input_psw):
print("fixture返回的内容:", input_user, input_psw)

fixtrue会覆盖同名的fixtrue

tests/
__init__.py conftest.py
# content of tests/conftest.py
import pytest @pytest.fixture(params=['one', 'two', 'three']) //最初的参数化fixtrue
def parametrized_username(request):
return request.param @pytest.fixture  //最初的非参数化fixtrue
def non_parametrized_username(request):
return 'username' test_something.py
# content of tests/test_something.py
import pytest @pytest.fixture  //非参数化fixtrue覆盖参数化fixtrue
def parametrized_username():
return 'overridden-username'

     //参数化fixtrue覆盖非参数化fixtrue
@pytest.fixture(params=['one', 'two', 'three'])
def non_parametrized_username(request):
return request.param def test_username(parametrized_username):
assert parametrized_username == 'overridden-username' def test_parametrized_username(non_parametrized_username):
assert non_parametrized_username in ['one', 'two', 'three'] test_something_else.py
# content of tests/test_something_else.py
def test_username(parametrized_username):
assert parametrized_username in ['one', 'two', 'three'] def test_username(non_parametrized_username):
assert non_parametrized_username == 'username'

直接参数化的fixtrue覆盖非参数化fixtrue

//直接参数化也可以覆盖fixtrue
tests/
__init__.py conftest.py
# content of tests/conftest.py
import pytest @pytest.fixture
def username():
return 'username' test_something.py
# content of tests/test_something.py
import pytest @pytest.mark.parametrize('username', ['directly-overridden-username'])
def test_username(username):
assert username == 'directly-overridden-username'

八、其他装饰器

官方API Reference — pytest documentation

1、@pytest.mark.xfail()、pytest.xfail

@pytest.mark.xfail(condition,*,reason=none,raises=none,run=True,strict=False)

1)raises:none表示默认匹配所有异常错误,值为type(Exception),只能是异常值

2)condition:boolea or str类型,就是有条件执行,只跳过部分特殊用例时使用

3)run:为true,该怎么样就怎么样,为False,则该测试套件不会运行,且始终标为xfail

4)reson:原因说明

5)strict:为False,用例通过显示xpass,用例失败显示xfail,为True,用例失败显示xfail,用例意外通过则显示fail

函数、方法、类执行前使用@pytest.mark.xfail(),预期失败

对方法使用:

//raises参数为空或者与所报的错误匹配时结果是xfail,此结果是预期失败,说明用例通过
@pytest.mark.xfail()
def test_002(self):
print("账户修改")
raise Exception //raises参数与所报的错误不匹配时结果是failed
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_004(self):
print("账户修改")
raise Exception --------------------结果------------------------ test_interface.py::Test_kuaidi::test_002 账户修改
XFAIL
test_interface.py::Test_kuaidi::test_004 账户修改
FAILED

对类使用:

@pytest.mark.xfail(raises=ZeroDivisionError)
class Test_kuaidi: def test_002(self): //不匹配的用例显示failed
print("账户修改")
raise Exception def test_004(self): //xpass,意外通过
print("账户修改")
# raise Exception def test_005(self): //匹配的用例显示xfail
1/0 ------------------------结果展示--------------- test_interface.py::Test_kuaidi::test_002 账户修改
FAILED
test_interface.py::Test_kuaidi::test_004 账户修改
XPASS
test_interface.py::Test_kuaidi::test_005 XFAIL

pytest.xfail(reason=none),主要使用来对已知错误和功能缺失使用的,使用例子:

def test_aaa():
pytest.xfail(reason="功能未做") ---------结果------------------------- test_interface.py::test_aaa XFAIL (功能未做)

2、@pytest.mark.skip()、@pytest.mark.skipif()、pytest.skip、pytest.importorskip

都是跳过用例,区别:

@pytest.mark.skip(reason=None):reason跳过的原因,不影响程序运行

@pytest.mark.skipif(condition, reason=None):condition参数的值只能是Boolean或者str,有条件跳过

@pytest.mark.skip使用在测试类、测试方法、测试函数上,则测试类、测试方法、测试函数中所有用例都会跳过

@pytest.mark.skipif使用在测试类、测试方法、测试函数上,condition有值则根据条件跳过,没有值和@pytest.mark.skip一样

@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
class TestSkipIf(object):
def test_function(self):
print("不能在window上运行")

pytest.skip(reason=none,allow_module_level=False,msg)

allow_module_level:默认为False,为True则会允许模块调用,执行时会跳过模块中剩余的用例

def test_aaa():
pytest.skip("功能未做") //注意,pytest.skip(reason="功能未做")会报错 结果:test_interface.py::test_aaa SKIPPED (功能未做) pytest.skip(r"功能未做",allow_module_level=True) 结果:跳过所有剩余用例

pytest.importorskip(modname,minversion=none,reason=none)

作用:缺少导入包,跳过用例测试

参数:modname,导入的模块名;minversion,模块最小版本号

expy = pytest.importorskip("pytest", minversion="7.0",reason="pytest7.0版本没有出来")

@expy
def test_import():
print("test")
结果:会跳过所有该模块的用例测试

3、@pytest.mark.flaky()

前提安装pytest-rerunfailures,reruns:重跑次数,reruns_delay:重跑间隔时间

禁止:1、不能和@pytest.fixtrue()一起使用

   2、该插件与pytest-xdist的looponfail不兼容

   3、与核心--pdb标志不兼容

    @pytest.mark.flaky(reruns=2,reruns_delay=5)
def test_ddd(self):
assert False ---------------------执行结果-------------------------
kuadi/test_kuadi_login.py::Test_study::test_ddd RERUN
kuadi/test_kuadi_login.py::Test_study::test_ddd RERUN
kuadi/test_kuadi_login.py::Test_study::test_ddd FAILED

九、hooks函数

学习地址:Pytest权威教程21-API参考-04-钩子函数(Hooks) - 韩志超 - 博客园 (cnblogs.com)

最新文章

  1. SQL中varchar和nvarchar的区别
  2. HTML 学习笔记 JavaScript(call方法详解)
  3. a error of misunderstanding
  4. django中抽象基类的Foreignkey的定义
  5. TCP与UDP协议
  6. Bower 手册
  7. 30天,APP创业从0到1【7.26苏州站】
  8. My_Plan part1 小结
  9. PL/SQL 0.几秒出结果,SQL效率一定高吗?
  10. 我的Python---1
  11. 主机通过host-only虚拟网卡连接VBOX虚拟机
  12. acm水题3个:1.求最大公约数;2.水仙花数;3.判断完数
  13. ubuntu14.04 64位 安装JDK1.7
  14. 给查询出的SQL记录添加序号列,解决方法有以下两种
  15. pandas,读取或存储DataFrames的数据到mysql中
  16. python开源数据库gadfly安装排除错误
  17. cxf 相关问题
  18. 新建一个Java Web程序
  19. DataTable快速定制之Expression属性表达式
  20. hdu2553N皇后问题(dfs,八皇后)

热门文章

  1. grequest案例对比requests案例
  2. JZOJ 3242. Spacing
  3. windows server backup 无法使用或wbadmin.msc致命错误解决方法
  4. pdf地址展示成Swiper轮播方式-复制链接
  5. 深入解读.NET MAUI音乐播放器项目(三):界面交互
  6. Infinity 和 -Infinity
  7. KingbaseES R6集群误删除备节点的集群目录恢复方式
  8. Redis一主多从哨兵模式
  9. lg9019题解
  10. C++标准库string学习笔记