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 模块化易扩展 总结链接: