pytest使用
个人笔记 并没有整理 仅供参考
1 安装 pip3 install pytest
2 cmd 窗口 运行 pytest 或 py.test
-q, --quiet decrease verbosity( 显示简单结果)
Pytest -h 说明文档
1 测试文件以test_开头 或结尾
2 测试类以Test开头 并且不能带有 __init__ 方法
3 测试函数以test_开头
4 断言使用assert
执行用例规则
1 执行某个目录下所有的用例
Pytest 文件名
2 执行某个py文件下用例
Pytest 脚本名称.py
3 -k 按关键字匹配
Pytest -k ‘myclass’
4 按照节点运行
1 pytest test_mode.py::test_func 运行某个函数
2 pytest test_mode.py::TestClass::test_method 运行某个测试类中的某个方法
5 标记表达式;
Pytest -m slow 将运行所有被@pytest.mark.slow装饰器修饰的所有测试
6 从包里面运行
Pytest --pyargs pkg.testing
这将导致pkg.testing 并使用其文件系统位置来查找和运行测试用例
7 -x 遇到错误的时候就停止测试
Pytest -x test_class.py
8 --maxfail=num 当错误到达多少个的时候就停止用例的运行
Pytest --maxfail =4
9 unittest 编写的脚本也可以 使用pytest执行 需要改该工程设置默认的运行器:file->Setting->Tools->Python Integrated Tools->项目名称->Default test runner->选择py.test
10 在pycharm pytest.main(‘q’,’pytest2.py’) main函数中 需要传入多个参数 需用 ,隔开
用例运行级别:
模块级 |
setup_module/teardown_module |
开始于模块始末,全局的 |
函数级 |
setup_function/teardown_function |
只对函数用例生效(不在类中) |
类级 |
setup_class/teardown_class |
只在类中前后运行一次(在类中) |
方法级 |
setup_method/teardown_method |
开始于方法始末(在类中) |
类里面的 |
setup/teardown |
运行在调用方法的前后 |
1 setup_function/teardown_function 每个用例开始和结束就调用一次
2 setup_module是所有用例开始前只执行一次,teardown_module是所有用例结束后只执行一次.
3 1.setup/teardown和unittest里面的setup/teardown是一样的功能,setup_class和teardown_class等价于unittest里面的setupClass和teardownClass 这在用例执行前的初始化 至关重要 比如实例化 数据库
import pytest # 类和方法
class TestCase():
def setup(self):
print("setup: 每个用例开始前执行")
def teardown(self):
print("teardown: 每个用例结束后执行")
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 test_one(self):
print("正在执行----test_one")
x = "this" assert 'h' in x
def test_two(self):
print("正在执行----test_two")
x = "hello"
assert hasattr(x, 'check')
def test_three(self):
print("正在执行----test_three")
a = "hello"
b = "hello world"
assert a in b
if __name__ == "__main__":
pytest.main(["-s", "test_fixtclass.py"])
4 运行级别
setup_module/teardown_module的优先级是最大的,然后函数里面用到的setup_function/teardown_function与类里面的setup_class/teardown_class互不干涉
Fixture 相比setup和teardown 应该来说有一下几点优势
命名方式灵活 不局限setup和teardown 这个命名
Conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置
Scope=”module” 可以实现多个.py 跨文件共享前置, 每一个.py文件调用一次
Scope = ‘session’ 可以实现多个.py 跨文件使用一个session来完成多个用例
Fixture 参数的解释:
fixture(scope="function", params=None, autouse=False, ids=None, name=None): """使用装饰器标记fixture的功能 可以使用此装饰器(带或不带参数)来定义fixture功能。 fixture功能的名称可以在以后使用 引用它会在运行测试之前调用它:test模块或类可以使用pytest.mark.usefixtures(fixturename标记。 测试功能可以直接使用fixture名称作为输入参数,在这种情况下,夹具实例从fixture返回功能将被注入。 :arg scope: scope 有四个级别参数 "function" (默认), "class", "module" or "session". :arg params: 一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它 :arg autouse: 如果为True,则为所有测试激活fixture func 可以看到它。 如果为False(默认值)则显式需要参考来激活fixture :arg ids: 每个字符串id的列表,每个字符串对应于params 这样他们就是测试ID的一部分。 如果没有提供ID它们将从params自动生成 :arg name: fixture的名称。 这默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名 “fixture_ <fixturename>”然后使用”@ pytest.fixture(name ='<fixturename>')“”。
+-
Fixtures可以选择使用yield语句为测试函数提供它们的值,而不是return。 在这种情况下,yield语句之后的代码块作为拆卸代码执行,而不管测试结果如何。fixture功能必须只产生一次 假如中间又个别用例执行失败 那么 yield 也会 等待所有的测试用例执行完毕 才会执行 结束操作
Fixture 中的conftest.py 配置
该文件名称固定 作用域同级别的package下 并且 __init__ 文件
测试报告的设定
需要安装pytest-html 是pytest 的插件
Pytest --html=reportname.html
默认生成在 当前运行脚本的同级目录 ,一般来说测试报告要和脚本分离,可以指定路径
Pytest --html=./reportdirname/reportname.html 但是如果指定了多个运行py文件那么 这些报告是独立的
如果想要合并
Pytest --html=report.html --self-contained-html
默认情况下,“ 结果”表中的所有行都将被展开,但具测试通过的行除外Passed。
可以使用查询参数自定义此行为:?collapsed=Passed,XFailed,Skipped。
失败用例可以自动截图 , 需要改写一些东西。
原理: 根据用例的执行状态 自动截图 ,然后保存为二进制文件 然后存在本地,然后 利用js 动态的在已经生成的报告用动态插入 截图
失败重新跑:
1 pip install pytest-rerunfailures
2 pytest --reruns 2 --html=report.html
参数化 pytest.mark.parametrize装饰器可以实现测试用例参数化。
import pytest
@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
if __name__ == "__main__":
pytest.main(["-s", "test_canshu1.py"])
也可以标记单个测试用例参数化 比如用mark.xfail 标记某个测试用例 错误
import pytest
@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):
print("-------开始用例------")
assert eval(test_input) == expected
if __name__ == "__main__":
pytest.main(["-s", "test_canshu1.py"])
组合参数化:
按照关键字传参 不关注 装饰器 执行顺序
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
print("测试数据组合:x->%s, y->%s" % (x, y))
if __name__ == "__main__":
pytest.main(["-s", "test_data.py"])
命令行传参
需要在conftest 文件中 定义一个被fixture 的函数 然后在命令行中带上就行
pytest -s test_case1.py --cmdopt type2
断言:
Pytest 允许使用标准的python内置 assert
正常断言:
异常信息:
Assert a%2==0,”异常信息str”
异常断言:如果需要对返回的异常进行再次断言 需要根据 异常的 type和value
例如1/0 异常类型 ZeroDivisionError:division by zero
import pytest
def test_zero_division():
'''断言异常'''
with pytest.raises(ZeroDivisionError) as excinfo:
1 / 0 # 断言异常类型type
assert excinfo.type == ZeroDivisionError
# 断言异常value值
assert "division by zero" in str(excinfo.value)
正常断言:
Assert xx |
判断xx为真 |
Assert not xx |
判断xx为假 |
Assert a in b |
判断b是否包含a |
Assert a==b |
A是否等于b |
Assert a!=b |
A是否不等于b |
Skip pytest.mark.skip 可以标记无法在某些平台上无法运行的测试功能,或者希望失败的测试功能
skip意味着只有在满足某些条件时才希望测试通过,否则pytest应该跳过运行测试。 常见示例是在非Windows平台上跳过仅限Windows的测试,或跳过测试依赖于当前不可用的外部资源(例如数据库)。
xfail意味着您希望测试由于某种原因而失败。 一个常见的例子是对功能的测试尚未实施,或尚未修复的错误。 当测试通过时尽管预计会失败(标有pytest.mark.xfail),它是一个xpass,将在测试摘要中报告。
pytest计数并分别列出skip和xfail测试。 未显示有关跳过/ xfailed测试的详细信息默认情况下,以避免混乱输出。 您可以使用-r选项查看与“short”字母对应的详细信息显示在测试进度中
1 skip 直接标记 也可以传递一个可选的的原因 reason 并没有特别的意义
只是 注释 跳过的原因
@pytest.mark.skip(reason="no way of currently testing this") def test_the_unknown():
2 skipif 当条件为真的时候 跳过
import sys
@pytest.mark.skipif(sys.version_info < (3,6), reason="requires python3.6 or higher")
def test_function():
可以在在模块之间 共享 skipif
import mymodule
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1), reason="at least mymodule-1.1 required")
@minversion def test_function(): ...
也饿可以导入标记 在另外模块中重复使用它
from test_mymodule import minversion
@minversion
def test_anotherfunction(): ...
Skip 类或模块
同上
Skip 缺少导入依赖项
您可以在模块级别或测试或测试设置功能中使用以下帮助程序
docutils = pytest.importorskip("docutils")
如果无法在此处导入docutils,则会导致测试跳过结果。 你也可以跳过库的版本号
docutils = pytest.importorskip("docutils", minversion="0.3")
将从指定模块的__version__属性中读取版本。
函数传参和fixture传参数request
如果我们需要重复执行一个函数,那么就绪要动态传染 比如登录 之后 才能进行其他操作
1 @pytest.mark.parametrize
2 request 参数 给fixture传参 固定的参数名称
test_user_data = ["admin1", "admin2"]
@pytest.fixture(scope="module")
def login(request):
‘’’
Request 固定写法 用于给login 传染数
’’’
user = request.param
print("登录账户:%s"%user)
return user
添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
'''登录用例'''
a = login print("测试用例中login的返回值:%s" % a)
assert a != ""
if __name__ == "__main__":
pytest.main(["-s", "test_02.py"])
如果需要request 传入多个参数 ,只需要 将列表中的元素 嵌套就行
test_user_data = [{"user": "admin1", "psw": "111111"}, {"user": "admin1", "psw": ""}]
Mark 标记
Pytest 中可以支持自定义标记 ,自定义标记可以把一个web项目划分多个模块, 然后指定模块名称执行。一个大项目自动化用的时候 也可以划分 多个模块
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
Pass
def test_another():
pass
class TestClass:
def test_method(self):
pass
if __name__ == "__main__":
pytest.main(["-s", "test_server.py", "-m=webtest"])
不想运行就用 not
pytest.main(["-s", "test_server.py", "-m=’not webtest’"])
指定函数节点 id
pytest.main(["-v", "test_server.py::TestClass::test_method"])
运行多个节点
pytest.main(["-v", "test_server.py::TestClass", "test_server.py::test_send_http"])
匹配用例名称
pytest -v -k http
用例之间的关联 如果a用例失败,跳过测试用例b和c 并标注为xfail
当用例a失败的时候,如果用例b和用例c都是依赖于第一个用例的结果,那可以直接跳过用例b和c的测试,直接给他标记失败xfail
用到的场景,登录是第一个用例,登录之后的操作b是第二个用例,登录之后操作c是第三个用例,很明显三个用例都会走到登录。
如果登录都失败了,那后面2个用例就没测试必要了,直接跳过,并且标记为失败用例,这样可以节省用例时间。
设计用例:
- 把登录写为前置操作
- 对登录的账户和密码参数化,参数用canshu = [{"user":"amdin", "psw":"111"}]表示
- 多个用例放到一个Test_xx的class里
- test_01,test_02, test_03全部调用fixture里面的login功能
- test_01测试登录用例
- test_02和test_03执行前用if判断登录的结果,登录失败就执行,pytest.xfail("登录不成功, 标记为xfail")
canshu = [{"user":"amdin", "psw":"111"}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
psw = request.param["psw"]
print("正在操作登录,账号:%s, 密码:%s" % (user, psw))
if psw:
return True
else:
return False
@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():
def test_01(self, login):
'''用例1登录'''
result = login print("用例1:%s" % result)
assert result == True
def test_02(self, login):
result = login
print("用例3,登录结果:%s" % result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
def test_03(self, login):
result = login
print("用例3,登录结果:%s" %result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
if __name__ == "__main__":
pytest.main(["-s", "test_05.py"])
autouse设置为True,自动调用fixture功能
以上介绍过
配置:
Pytest 配置文件可以改变pytest的运行方式,他是一个固定的pytest.ini 文件,读取配置信息,按照指定的方式去运行。
1 Pytest.ini 中 的 addopts=-rsxX 标识pytest 报告所有测试用例被跳过,预计失败,预计失败但是实际被通过的原因
2 mark 标记 @pytest.mark.标记名称
有时候标签多了,不容易记住,为了方便后续执行指令的时候能准确使用mark的标签,可以写入到pytest.ini文件
[pytest] markers = webtest: Run the webtest case hello: Run the hello case
标记好之后,可以使用pytest --markers查看到
3 禁用xpass
设置xfail_strict = true可以让那些标记为@pytest.mark.xfail但实际通过的测试用例被报告为失败
4 addopts
ddopts参数可以更改默认命令行选项,这个当我们在cmd输入指令去执行用例的时候,会用到,比如我想测试完生成报告,指令比较长
pytest -v --rerun 1 --html=report.html --self-contained-html
[pytest] markers = webtest: Run the webtest case hello: Run the hello case
xfail_strict = true
addopts = -v --rerun 1 --html=report.html --self-contained-html
相当于定义了一个变量 执行这个变量就性了
Pytest中 doctest测试框架 是python自带的一个模块 是单元测试的一种。Doctest模块回搜索那些看起来像交互式会话的python代码片段 在python中 有>>>标识的 就认为是 cmd交互环境
Fixture:
Fixture 作为参数传入 返回值可有可无 并且可以返回可变数据类型 err和failed的区别
Err一般是程序执行错 但是在fixture 断言错误就回报错,因为不是test开头
Fixture和fixture之间可以相互调用
import pytest
@pytest.fixture()
def first():
print("获取用户名")
a = "yoyo"
return a
@pytest.fixture()
def sencond(first):
'''psw调用user fixture'''
a = first
b = "123456"
return (a, b)
def test_1(sencond):
'''用例传fixture'''
print("测试账号:%s, 密码:%s" % (sencond[0], sencond[1])) assert sencond[0] == "yoyo"
if __name__ == "__main__":
pytest.main(["-s", "test_fixture6.py"])
Pytest中的conftest.py
可以有多个conftest 但是通常每个目录级别都只会有一个 这种分层的配置有直观重要的意义 ,比如系统 可以根据进入某个应用的设置通用路径
Pytest 运行上次失败的用例
If last-failed只重新运行上次运行失败的用例,如果没有就全部重新跑
pytest --lf
Ff 运行所有的测试,但是首先运行上次运行失败的测试 这可能回重新测试,从而导致fixture setup
--failed-first
Pytest中的分布式执行。当用例比较多的时候,而每条用例都是同步的,时间比较长,可以采用分布式
该pytest-xdist插件扩展了一些独特的测试执行模式pytest:
测试运行并行化:如果有多个CPU或主机,则可以将它们用于组合测试运行。会加快运行速度
- --looponfail:在子进程中重复运行测试。每次运行之后,pytest会等待,直到项目中的文件发生更改,然后重新运行以前失败的测试。
重复此过程直到所有测试通过,之后再次执行完整运行。
多平台覆盖:您可以指定不同的Python解释器或不同的平台,并在所有平台上并行运行测试。
在远程运行测试之前,pytest有效地将您的程序源代码“rsyncs”到远程位置。报告所有测试结果并显示给您的本地终端。您可以指定不同的Python版本和解释器。
实际上就是 python中 主进程开启了多个子进程
pytest -n number
并且xdist 可以支持html文件
pytest -n 3 --html=report.html --self-contained-html
重复执行用例:
pip install pytest-repeat
Pytest-Repeat
指定文件 重复执行 10次
py.test --count=10 test_file.py
但是 如果模块中有多个case 这种方式 是单次执行10次后 在执行下个case 10次
这样可以指定 --repeat-scope 的参数 同fixture一样 有4个级别
1 function 默认 ,范围针对每个用例重复执行,再执行下一个用例
2 class 以class为用例集合单位,重复执行class 里面的用例,再执行下一个
3 module 以模块为单位,重复执行模块里面的用例,再执行下一个
4 session 重复真个测试会话,即所有收集的测试执行一次,然后所有这些测试再次执行等等
pytest baidu/test_1_baidu.py -s --count=5 --repeat-scope=session
只针对的单个用例执行多次 可以在目标函数使用@pytest.mark.repeat(count) 指定次数