04-13pytest的基本使用方法
引言
有了unittest这个经典的测试框架做铺垫,那么学习其他任何的测试框架都变得有章法可循了。
pytest测试框架也是由unittest改编而来,所以许多地方都是一脉相承。
我相信许多读者再看了unittest的文章之后,已不需要耗费脑细胞就可以把pytest的使用掌握了。你是不是也是其中一个呢?
正文:
1 断言
pytest直接使用 assert 进行断言
.\test_assert.py
# content of test_assert.py
# 功能函数
def func_number():
return 3
# 测试函数
def test_function():
assert func_number() == 3
我们把脚本运行一下:
...> pytest Stage5\07pytest\pytest test_assert.py -v #### -v 参数能把结果打印的更详细 #### -v, --verbose increase verbosity.
想要知道更多pytest的参数输入pytest –help
结果如下:
(testops) D:\Coding\Project\testops\Stage5\07pytest>pytest test_assert.py -v
======================================================== test session starts ========================================================
platform win32 -- Python 3.7.1, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- d:\python\virtualenvs\testops\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Project\testops\Stage5\07pytest
collected 1 item
test_assert.py::test_function PASSED [100%]
========================================================= 1 passed in 0.01s =========================================================
(testops) D:\Coding\Project\testops\Stage5\07pytest>
如果你的运行结果看不到详细的信息,那你需要加上 -v 参数。
以上是关于pytest断言的抛砖引玉,就在这章其他小节还有其他具体的使用说明,在下一章节还有实战举例说明。
2 Fixture
fixture是测试脚手架的意思。还记得在unittest的文章说起的 厨房脚手架。
上有 抽油烟机顶 ,下游烧火的灶台 ,中间就是厨师的摇摆空间。
在pytest中,fixture的作用得到了改进,不再那么固定和笨重,已经变得 可以移动。
.\test_pytestfixture.py
#fixture的简单例子
import pytest
@pytest.fixture()
def func_isequal():
a = 'robert'
return a
def test_equalrobert(func_isequal):
assert func_isequal == 'robert'
if __name__ == '__main__':
pytest.main("-v -s test_pytestfixture.py")
运行结果如下:
(testops) D:\Coding\Project\testops\Stage5\07pytest>pytest test_pytestfixture.py -v -s
======================================================== test session starts ========================================================
platform win32 -- Python 3.7.1, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- d:\python\virtualenvs\testops\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Project\testops\Stage5\07pytest
collected 1 item
test_pytestfixture.py::test_equalrobert PASSED
========================================================= 1 passed in 0.05s =========================================================
定义脚手架的时候要用@pytest.fixture()关键字
3 参数化
看文章的你脑袋里面会不会在想,参数是什么?
我想,应该没有。如果有的话,那你还是加我联系方式,单独聊,因为这东西只可意会不可言传。此时旁边的人都发出了笑声。
.\test_parameterized.py
# content test_parameterized.py
import pytest
import math
@pytest.mark.parametrize(
"base,exponet,expected",
[(2,3,8),
(2,4,16),
(2,5,32),
(2,6,64)],
ids =["case1_**3","case2_**4","case3_**5","case4_**6"]
)
def test_pow(base,exponet,expected):
assert math.pow(base,exponet) ==expected
pytest的参数化需要用到@pytest.mark.parametrize() 关键字
上面的例子传了3个参数 argnames, argvalues, ids
分别代表,参数名称,参数值,用例名称
这个测试用例是对幂运算的结果进行验证,例如 (2,3,8) 是 指 底数是2,指数是3,预期结果是8 .
其中用到了python 内置math模块的pow方法。
(testops) D:\Coding\Project\testops\Stage5\07pytest>pytest test_parameterized.py -v
======================================================== test session starts ========================================================
platform win32 -- Python 3.7.1, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- d:\python\virtualenvs\testops\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Project\testops\Stage5\07pytest
collected 4 items
test_parameterized.py::test_pow[case1_**3] PASSED [ 25%]
test_parameterized.py::test_pow[case2_**4] PASSED [ 50%]
test_parameterized.py::test_pow[case3_**5] PASSED [ 75%]
test_parameterized.py::test_pow[case4_**6] PASSED [100%]
========================================================= 4 passed in 0.02s =========================================================
4 运行测试
前面我们运行的时候,都是在terminal下,输入命令 pytest test_*.py来运行。
实际上pytest提供了不少其他运行指令,我们可以通过pytest –help查看。
这里列出常用的一些指令:
-k 运行名称中包含某字符的测试用例
-q 减少测试的运行冗长
-x 如果出现一天测试用例失败,则退出测试
-v 输出更详细的日志信息
下面是一个测试py文件
.\test_runrule.py
# 运行测试的案例说明模块
# 功能函数
def func(x,y):
return x+y >10
def func_math(x,y):
import math
return math.fabs(x)
def test_isnottrue():
assert func(3,3) is True
def test_func_math():
assert func_math(3.333,6.222) >0
我们运行上面的test_runrule.py
1.运行命令: pytest test_runrule.py
(testops) D:\Coding\Project\testops\Stage5\07pytest>pytest test_runrule.py
======================================================== test session starts ========================================================
platform win32 -- Python 3.7.1, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: D:\Coding\Project\testops\Stage5\07pytest
collected 2 items
test_runrule.py F. [100%]
============================================================= FAILURES ==============================================================
__________________________________________________________ test_isnottrue ___________________________________________________________
def test_isnottrue():
> assert func(3,3) is True
E assert False is True
E + where False = func(3, 3)
test_runrule.py:13: AssertionError
==================================================== 1 failed, 1 passed in 0.03s ====================================================
5 生成测试报告
pytest支持生成多种格式的测试报告。
运行命令: pytest test_runrule.py –junit-xml=./report/log.xml
(testops) D:\Coding\Project\testops\Stage5\07pytest>pytest test_runrule.py --junit-xml=./report/log.xml
======================================================== test session starts ========================================================
platform win32 -- Python 3.7.1, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: D:\Coding\Project\testops\Stage5\07pytest
collected 2 items
test_runrule.py F. [100%]
============================================================= FAILURES ==============================================================
__________________________________________________________ test_isnottrue ___________________________________________________________
def test_isnottrue():
> assert func(3,3) is True
E assert False is True
E + where False = func(3, 3)
test_runrule.py:14: AssertionError
--------------------------- generated xml file: D:\Coding\Project\testops\Stage5\07pytest\report\log.xml ----------------------------
==================================================== 1 failed, 1 passed in 0.03s ====================================================
查看report\log.xml如下
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite errors="0" failures="1" hostname="DESKTOP-V5FAHI8" name="pytest" skipped="0" tests="2" time="0.030" timestamp="2019-11-01T17:10:51.928076"><testcase classname="test_runrule" file="test_runrule.py" line="12" name="test_isnottrue" time="0.000"><failure message="assert False is True
+ where False = func(3, 3)">def test_isnottrue():
> assert func(3,3) is True
E assert False is True
E + where False = func(3, 3)
test_runrule.py:14: AssertionError</failure></testcase><testcase classname="test_runrule" file="test_runrule.py" line="15" name="test_func_math" time="0.000"></testcase></testsuite></testsuites>
看了这么长的代码或者xml,是不是觉得这报告样式太难看了,无法入眼。
其实,还有许多其他排版很漂亮的报告。例如这个是pytest的html报告模板。
后面会有针对报告的小节来进行详细记述。
6 本地配置文件conftest.py
./conftest.py
import pytest
#设置测试钩子
@pytest.fixture()
def test_url():
return "http://www.qq.com"
return "http://www.baidu.com"
./test_qq.py
def test_qq(test_url):
print(test_url)
confset.py是pytest特有的本地测试配置文件,即可以用来设置项目级别的Fixture,也可以用来导入外部插件,还可以用来指定钩子函数。
总结:
1 断言 的关键字assert
2 脚手架的定义需要用到 @pytest.fixture() 内置方法
3 参数化用到的关键字:@pytest.mark.parametrize()
4 运行测的常用指令
-v
-s
-q
5 生成测试报告的方法有多
6 conftest.py名字是固定的,不可以被修改
思考与延伸
1 pytest中fixture源码解析
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
during the collection phase. If you need to setup expensive resources
see about setting indirect to do it rather at test setup time.
:arg argnames: a comma-separated string denoting one or more argument
names, or a list/tuple of argument strings.
:arg argvalues: The list of argvalues determines how often a
test is invoked with different argument values. If only one
argname was specified argvalues is a list of values. If N
argnames were specified, argvalues must be a list of N-tuples,
where each tuple-element specifies a value for its respective
argname.
:arg indirect: The list of argnames or boolean. A list of arguments'
names (self,subset of argnames). If True the list contains all names from
the argnames. Each argvalue corresponding to an argname in this list will
be passed as request.param to its respective argname fixture
function so that it can perform more expensive setups during the
setup phase of a test rather than at collection time.
:arg ids: list of string ids, or a callable.
If strings, each is corresponding to the argvalues so that they are
part of the test id. If None is given as id of specific test, the
automatically generated id for that argument will be used.
If callable, it should take one argument (self,a single argvalue) and return
a string or return None. If None, the automatically generated id for that
argument will be used.
If no ids are provided they will be generated automatically from
the argvalues.
:arg scope: if specified it denotes the scope of the parameters.
The scope is used for grouping tests by parameter instances.
It will also override any fixture-function defined scope, allowing
to set a dynamic scope using test context or configuration.
"""
:arg ids: list of string ids, or a callable. 这里可以定义测试用例的名称,如果有N个测试的时候,这是个很棒的用法。