pytest使用教程
参考文档:pytest中文文档
前言
好的测试框架,核心为,每个文件,层级之间传递全局参数和函数。
一、基本测试方法:
1、指定目录下执行pytest,正常函数
test_*.py文件会被默认测试
1 def playvoice(voice): 2 return "play "+voice 3 4 def test_playvoice(): 5 assert playvoice("audio") == "play audio"
2、指定目录下执行pytest,测试类
1 def play_one(one): 2 return "==="+one 3 def play_two(two): 4 return two+150 5 6 class TestClass(object): 7 def test_play_one(self): 8 assert play_one("one") == "===one" 9 def test_play_two(self): 10 assert play_two(30) == 18
二、pytest的功能
1、第三方库或参数实现
1 多线程或多进程跑用例,大大缩短执行时间。安装pytest-xdist 参数-n=2
2 对失败用例重跑,对于一些不稳定设备测试,自动重跑是必要的。安装pytest-returnfailures 参数--reruns=1
3 对整组自动化执行任务的约束,-x 只要一个失败即停止任务 --maxfail=2 两个失败就停止。
4 unittest 按照ascII的大小执行,pytest按照代码安排顺序执行。
如果想改变这个顺序可以用pytest-ordering方法。
@pytest.mark.run(order=2) def test_baidu_web_channel(self): print("===baiduhoutaiweb_channel===")注意这个装饰器方法生肖之前,一定先安装pytest-ordering模块
2、pytest实现前后置三种方法
1 自带setup和teardown方法
import pytest class TestBaidu: # 对于所有用例前执行一次的。 def setup_class(self,): print(" ===class=== 创建日志对象,创建数据库链接,创建服务接口链接") def setup(self): print(" ===func=== 打开浏览器,加载页面") def testbaiduapi_1(self,): print("===baiduapi_1") def testbaiduapi_2(self,): print("===baiduapi_2") def teardown(self): print(" ===func=== 关闭浏览器") def teardown_class(self,): print(" ===class=== 注销日志对象,数据库连接对象,接口连接对象") if __name__ == "__main__": pytest.main(["-vs"])
执行结果:
============================================ test session starts ============================================ platform win32 -- Python 3.8.5, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- c:program filespython38python.exe cachedir: .pytest_cache rootdir: C:UsersdevevfDocumentsAItestvoiceaudio_autotestvoice_recommshiyan plugins: allure-pytest-2.8.19 collected 4 items testapi/test_baiduapi.py::TestBaidu::testbaiduapi_1 ===class=== 创建日志对象,创建数据库链接,创建服务接口链接 ===func=== 打开浏览器,加载页面 ===baiduapi_1 PASSED ===func=== 关闭浏览器 testapi/test_baiduapi.py::TestBaidu::testbaiduapi_2 ===func=== 打开浏览器,加载页面 ===baiduapi_2 PASSED ===func=== 关闭浏览器 ===class=== 注销日志对象,数据库连接对象,接口连接对象 testapi/test_googleapi.py::TestBaidu::testgl_1 ===glapi_1 PASSED testapi/test_googleapi.py::TestBaidu::testgl_2 ===glapi_2 PASSED ============================================= 4 passed in 0.06s =============================================
2 pytest.fixture 装置设置部分前后置方法
无参数方法操作
import pytest @pytest.fixture(scope="class",autouse=True) def setfix(): print("===set 前置=== 打开浏览器,加载页面") yield print("===set 后置=== 关闭浏览器") class TestBaidu: def testbaiduapi_1(self,): print(" ===baiduapi_1") def testbaiduapi_2(self, setfix): print(" ===baiduapi_2") if __name__ == "__main__": pytest.main(["-vs"])
注意:1 默认scope="function",autouse=False
2 scope="class",autouse=True,则每个class执行
3 scope="function",autouse=True,则测试用例是否安装装置fixture都会自动执行
参数注释:scope 作用域范围 默认function,session/package,module,class,function
autouse,自动执行
参数操作
import pytest @pytest.fixture(scope="class",params=["n1","n2","n3"]) def setfix(request): print("===set 前置=== 打开浏览器,加载页面") yield request.param print("===set 后置=== 关闭浏览器") class TestBaidu: def testbaiduapi_1(self,): print(" ===baiduapi_1") def testbaiduapi_2(self,setfix): print(" ===baiduapi_2===", setfix)
注意:前后置方法 入参名字一定是request
yield request.param返回一个元素
yield前后为前后置
name参数可以重命名装饰器函数
@pytest.fixture(scope="class",params=["n1","n2","n3"],name="zhuang") def setfix(request): print("===set 前置=== 打开浏览器,加载页面") yield request.param print("===set 后置=== 关闭浏览器")
3 基于conftest.py和fixture的全局前置方法
核心思想,针对项目的前置方法。
项目全局登录和模块相同全局操作
特点:1 单独存在,名字不可修改,fixture配置文件,不用引用,可以跨文件执行。
2 每一级都可配置文件,优先使用本级的配置文件中前置方法
3 多个前后置方法,按照排列顺序执行
代码为 模块级conftest.py
import pytest @pytest.fixture(scope="function",params=["baidu key"]) def baidusetfix(request): print("===set 前置=== 打开浏览器,加载页面") yield request.param print("===set 后置=== 关闭浏览器")
代码为项目级conftest.py
import pytest @pytest.fixture(scope="function",params=["n1","n2"]) def setfix(request): print("===set 前置=== 打开浏览器,加载页面") yield request.param print("===set 后置=== 关闭浏览器")
模块内测试用例调用顺序为
class TestBaidu: def testbaiduapi_1(self,setfix,baidusetfix): print(" ===baiduapi_1",setfix,baidusetfix)
3、跳过用例
无条件
import pytest class TestBaidu: def testbaiduapi_1(self,): print(" ===baiduapi_1===") @pytest.mark.skip(reason="环境维护") def testbaiduapi_2(self,): print(" ===baiduapi_2===") def testbaiduapi_3(self,): print(" ===baiduapi_3===")
结果api2被跳过
testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_1 ===baiduapi_1=== PASSED testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_2 SKIPPED testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_3 ===baiduapi_3=== PASSED ======================================= 2 passed, 1 skipped in 0.06s ========================================
有条件 skipif 第一位:条件,第二位原因
import pytest class TestBaidu: conf=False def testbaiduapi_1(self,): print(" ===baiduapi_1===") @pytest.mark.skipif(conf==True,reason="环境") def testbaiduapi_2(self,): print(" ===baiduapi_2===") def testbaiduapi_3(self,): print(" ===baiduapi_3===")
结果因为条件判断不相等 所以不跳过
testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_1 ===baiduapi_1=== PASSED testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_2 ===baiduapi_2=== PASSED testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_3 ===baiduapi_3=== PASSED ============================================= 3 passed in 0.13s =============================================
4、数据驱动的标准写法
pytest.mark.parametrize()
字符串变量 + params
“字符串,字符串”+params 解压缩
注意params入参数据结构为列表,元组,列表字典,元组字典。当然可以列表中带列表
import pytest class TestBaidu: def testbaiduapi_1(self,): print(" ===baiduapi_1===") @pytest.mark.smoke @pytest.mark.parametrize("code",["utf","gbk","uni"]) def testbaiduapi_2(self,code): print(" ===baiduapi_2===") print("encode===",code) @pytest.mark.user @pytest.mark.parametrize("api,url",[["baidu","www.baidu"],["gl","www.google"]]) def testbaiduapi_3(self,api,url): print(" ===baiduapi_3===") print("===",api,url)
三、pytest 操作
1、pytest默认规则
1 文件/模块以test_, _test 起始或结尾
2 类 以Test开始,不能带有init方法
3 函数或方法以test开头。
2、执行方法
1 pytest.main()
可以添加入参,详见本章第三小节
调用格式如下:
import pytest
if __name__=="__main__": pytest.main(["-s"])
作用范围,与之同级的符合pytest运行规则的所有文件,目录内文件。
这里test_ail.py同级的cases_inter目录下的test_baidu.py内函数被执行了。
因为自己文件名也是test_ail.py 所以图中右侧部分的test_print类中的方法也会被执行。
也可以运行主函数时,函数所在文件命名为ail.py则其中的函数就不会执行了。
我们也尝试了在cases_inter/test_baidu.py中加入pytest.main(),执行后,test_baidu外层的文件是不执行的,如test_ail.py文件没有执行。
2 对应的命令行也可以执行
在terminal中执行
3 配置文件pytest.ini执行
1 位置 测试项目根目录下
2 要求:使用notepad++ 修改编码为ANSI格式,其中不能有中文
3 作用可以修改pytest默认规则
4 规则,不管是主函数方法还是terminal方法都会先读取这个文件
pytest --help指令可以查看pytest.ini的设置选项
显示如下
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: markers (linelist) markers for test functions empty_parameter_set_mark (string) default marker for empty parametersets norecursedirs (args) directory patterns to avoid for recursion testpaths (args) directories to search for tests when no files or dire console_output_style (string) console output: classic or with additional progr usefixtures (args) list of default fixtures to be used with this project python_files (args) glob-style file patterns for Python test module disco python_classes (args) prefixes or glob names for Python test class discover python_functions (args) prefixes or glob names for Python test function and m xfail_strict (bool) default for the strict parameter of addopts (args) extra command line options minversion (string) minimally required pytest version
简约版
[pytest] addopts = -vs testpaths = ./cases_inter python_files = unit_*.py python_classes = Test* python_functions = test
只执行main或者pytest,不用加参数和指定目录,即可完成配置测试任务。
3、参数
注意参数应该放在列表中写,pytest.main(["-s","test_baidu.py"])
-s 是显示测试中的print打印
-v 是显示测试用例的详细信息
用双::来标定类名,文件/模块名,函数名(用例)。
目录和文件之间用路径符号分割,注意/ 和
模块名和 用例方法名用 ::双::分割
文件中还是可以不在类中定义函数。如test_bd测试用例
def test_bd(): print("===baidu inter") class TestBaidu: def test_baidu(self): print("baidu")
-n 支持多线程,也有支持多进程的库,参见pytest-parallel
前提条件,安装pytest-xdist,pip install pytest-xdist
参数输入格式,terminal中
pytest -vs cases_inter est_baidu.py -n=2
pytest.main中
if __name__ == "__main__":
pytest.main(["-vs","-n=2",r"cases_inter est_baidu.py"])
-reruns num
作用为失败用例重跑。
安装方法:
pip install pytest-rerunfailures -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
pip install pytest-rerunfailures直接安装可能显示安装内容不全的信息。
操作方法:
pytest.main 参数方法:
if __name__=="__main__":
pytest.main(["-vs","--reruns=1"])
结果:
=========================== short test summary info ===========================
FAILED test_baidu.py::TestBaidu::test_baidu - assert 1 == 2
==================== 1 failed, 1 passed, 1 rerun in 0.36s =====================
注意
--reruns=1 reruns前是两个--, 1 表示失败的用例重新跑一次
terminal 命令行
pytest -vs cases_inter est_baidu.py --reruns=1
======================================================= short test summary info
FAILED cases_inter/test_baidu.py::TestBaidu::test_baidu - assert 1 == 2
================================================= 1 failed, 1 passed, 1 rerun in 0.21s
-x 参数 只要一个用例即可停止,这个针对冒烟测试比较好用。 --maxfail 只要两个失败就停止。
-k 根据测试用例名称包含指定字符串的执行
if __name__=="__main__": pytest.main(["-vs","--reruns=1","-k=web"])
--html ./report/report.html 生成报告。不常用,常用alluer框架生成。
1 安装pytest-html
2 --html ./report/report.html,也可以再ini配置
-m 按照标记执行,
背景:冒烟测试,用例分布在各个模块中,需要从中挑选
任务需要按照模块执行,或按照api执行,就需要为指定用例标记
标记步骤,1 ini配置
[pytest] addopts = -vs markers = smoke: 冒烟测试 user: 用户管理
2 测试脚本添加装饰器
import pytest class TestBaidu: def testbaiduapi_1(self,): print(" ===baiduapi_1===") @pytest.mark.smoke def testbaiduapi_2(self,): print(" ===baiduapi_2===") assert 1==2 @pytest.mark.user def testbaiduapi_3(self,): print(" ===baiduapi_3===")
3 -m 命令行执行 注意支持and or 如:“smoke or user” or写在字符串中
pytest -m "smoke or user" . estapiaidu
结果
collected 3 items / 1 deselected / 2 selected testapiaidu est_baiduapi.py::TestBaidu::testbaiduapi_2 ===baiduapi_2=== PASSED ===baiduapi_3=== PASSED ============================================= warnings summary ============================================== testapiaidu est_baiduapi.py:5 C:UsersdevevfDocumentsAItestvoiceaudio_autotestvoice_recommshiyan estapiaidu est_baiduapi.py:5: PytestUnknownMarkWarning: Unknown pytest.mark.run - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html @pytest.mark.run(reruns=2) -- Docs: https://docs.pytest.org/en/stable/warnings.html ================================ 2 passed, 1 deselected, 1 warning in 0.07s =================================
问题:
1 参考
@pytest.fixture(scope='class')
def gfixtures(request, logger, adb, carsim, uia):
# https://stackoverflow.com/questions/26405380/how-do-i-correctly-setup-and-teardown-my-pytest-class-with-tests
# https://computableverse.com/blog/pytest-sharing-class-fixtures
# https://stackoverflow.com/questions/53800448/many-pytest-fixtures-vs-one-large-container-fixture
# https://stackoverflow.com/questions/40139956/access-testcase-name-in-pytest-fixture
# http://hackebrot.github.io/pytest-tricks/fixtures_as_class_attributes/
request.cls.logger = logger
request.cls.adb = adb
request.cls.uia = uia
request.cls.carsim = carsim
# request.cls.uiselector = uiselector # f() takes 1
positional argument but 2 were given
# from pytestlib.fixtures import fixtures
# request.cls.fixtures = fixtures
yield
2
理解fixture 注册流程 与 request 范围:基本不能跨文件 只有注册了fixture的函数可以跨文件
>>> 实验结果
核心目标 文件之间的传递参数和实例化对象
1 conftest中
@pytest
.mark.parametrize(
"login"
,test_users,indirect
=
True
)
indirect
解决parametrize中传入函数问题indirect
间接使用反射特性"login"为函数test_users=[{"user":"admin","pwd":"123456"},{"user":"test","pwd":""}] @pytest.fixture(scope="module") def login(request): user=request.param["user"] pwd=request.param["pwd"] print("登录账户:%s" % user) print("登录密码:{}".format(pwd)) return pwd
参考pytest 中文文档第五章
第五章 fixture 模块化易扩展 总结链接: