pytest测试框架系列 - 正确的使用参数化parametrize,让你的代码更加优雅、简洁!
## 前言
我们先看下如果测试用例,我们在模拟用户名和密码进行登录功能,你们觉得有什么问题呢?
示例:
```python
# !/usr/bin/python3
# _*_coding:utf-8 _*_
""""
# @Time :2021/7/7 21:37
# @Author : king
# @File :test_params.py
# @Software :PyCharm
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
# 用户正常登录
def test_login_success():
username = "king"
password = 123456
assert username == "king" and password == 123456
# 用户名为空
def test_login_username_empty():
username = ""
password = 123456
assert username == "king" and password == 123456
# 用户名错误
def test_login_username_error():
username = "king1"
password = 123456
assert username == "king" and password == 123456
if __name__ == '__main__':
pytest.main()
```
从上面例子可以看出来,存在大量重复代码,是不是看起来非常不优雅,从上面可以看出来只是username和password不一样,我们想象一下能否通过传参方式呢?
通过参数化修改上面例子:
```python
# -*- coding: utf-8 -*-
"""
# @Time :2021/7/7 21:37
# @Author : king
# @File :test_params.py
# @Software :PyCharm
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
@pytest.mark.parametrize("user,pwd", [("king", 123456), ("", 1234567), ("king1", 1234567)])
def test_login(user, pwd):
username = user
password = pwd
assert username == "king" and password == 123456
if __name__ == '__main__':
pytest.main()
```
通过上面参数化的修改,代码是不是看起来简单很多而且优雅了
## @pytest.mark.parametrize 详解(建议掌握程度:☆☆☆☆☆)
说明:
源码分析:
```python
def parametrize(
self,
argnames: Union[str, List[str], Tuple[str, ...]],
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
indirect: Union[bool, Sequence[str]] = False,
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = None,
scope: "Optional[_Scope]" = None,
*,
_param_mark: Optional[Mark] = None,
) -> 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.
:param argnames:
A comma-separated string denoting one or more argument names, or
a list/tuple of argument strings.
:param 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.
:param indirect:
A list of arguments' names (subset of argnames) or a boolean.
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.
:param ids:
Sequence of (or generator for) ids for ``argvalues``,
or a callable to return part of the id for each argvalue.
With sequences (and generators like ``itertools.count()``) the
returned ids should be of type ``string``, ``int``, ``float``,
``bool``, or ``None``.
They are mapped to the corresponding index in ``argvalues``.
``None`` means to use the auto-generated id.
If it is a callable it will be called for each entry in
``argvalues``, and the return value is used as part of the
auto-generated id for the whole set (where parts are joined with
dashes ("-")).
This is useful to provide more specific ids for certain items, e.g.
dates. Returning ``None`` will use an auto-generated id.
If no ids are provided they will be generated automatically from
the argvalues.
:param 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.
"""
"""
翻译:
功能说明:使用给定参数列表值为基本功能测试函数添加新调用,在收集阶段执行参数化,如果您需要设置
高级的资源,请参阅设置indirect进行设置,而不是在测试设置时
:param argnames: 一个逗号分隔的一个或多个字符串参数,或字符串的列表/元组参数。
:param argvalues:这个参数列表决定了测试执行次数通过不同的参数值. 如果仅仅一个参数,指定参数值
为一个列表。如果有N个参数,参数值必须为一个列表且里面为元祖,每个元祖里面的值与参数名相对应。
:param indirect:参数名称列表(参数名称的子集)或布尔值。
如果为 True,则列表包含 argnames 中的所有名称。 每个与此列表中的 argname 对应的 argvalue 将
作为 request.param 传递给其各自的 argname 夹具功能,以便它可以在运行期间执行更高级的测试而
不是收集时间
:param ids: argvalues 的(或生成器)id 序列,或可调用以返回每个 argvalue 的 id 的一部分。
使用序列(以及像“itertools.count()”这样的生成器)返回的 id 应该是类型 ``string``, ``int``,
``float``,``bool`` 或 ``None``。
它们被映射到 argvalues 中的相应索引。``None`` 表示使用自动生成的 id。
如果它是可调用的,它将为中的每个条目调用``argvalues``,并且返回值用作整个集合的自动生成的
id(他们之间使用破折号连接("-"))。
这对于为某些项目提供更具体的 id 很有用,例如日期。 返回 ``None`` 将使用自动生成的 id。
如果没有提供id,就使用参数值。
:param scope: 如果指定,则表示参数的范围。
范围用于按参数实例对测试进行分组。
它还将覆盖任何夹具功能定义的范围,允许使用测试上下文或配置设置动态范围。
"""
```
### 使用方法
- @pytest.mark.parametrize("key", ["val1", "val2", "val3", "val4"])
- @pytest.mark.parametrize("key", [{"key": "val1"}, {"key": "val2"}])
- @pytest.mark.parametrize("key1,key2", [("val1", "val2"), ("val3", "val4")])
- @pytest.mark.parametrize(["key1", "key2"], [("val1", "val2"), ("val3", "val4")])
- @pytest.mark.parametrize(("key1", "key2"), [("val1", "val2"), ("val3", "val4")])
- @pytest.mark.parametrize("key1,key2", [[{"key1": "val1"},{"key2": "val2"}], [{"key1": "val2"},{"key2": "val3"}]])
#### **@pytest.mark.parametrize("key", ["val1", "val2", "val3", "val4"])**
示例:
```python
# -*- coding: utf-8 -*-
"""
# @Project :demo_test
# @Time :2021/7/8 8:33
# @Author :king
# @File :test_params.py
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
@pytest.mark.parametrize("user", ["king", "king1"])
def test_login(user):
assert user.startswith("king")
if __name__ == '__main__':
pytest.main()
```
执行结果:
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210708145316140.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA0NTQxMTc=,size_16,color_FFFFFF,t_70)
#### @pytest.mark.parametrize("key", [{"key": "val1"}, {"key": "val2"}])
关键代码示例:
```python
@pytest.mark.parametrize("user", [{"user": "king"}, {"user": "king1"}])
def test_login(user):
assert user["user"].startswith("king")
```
**其他方式大家可以按照上面示例进行使用即可**
#### 多个参数组合进行参数化
注意:多个参数组合时使用的是笛卡尔积
示例:
```python
# -*- coding: utf-8 -*-
"""
# @Project :demo_test
# @Time :2021/7/8 8:33
# @Author :king
# @File :test_params.py
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
@pytest.mark.parametrize("user", ["king", "wangyong"])
@pytest.mark.parametrize("pwd", [123456, 1234567])
def test_login(user, pwd):
assert user == "king" and pwd == 123456
if __name__ == '__main__':
pytest.main()
```
执行结果:
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=2021070816332132.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA0NTQxMTc=,size_16,color_FFFFFF,t_70)
### ids使用
先看下未设置ids时:
```python
# -*- coding: utf-8 -*-
"""
# @Project :demo_test
# @Time :2021/7/8 8:33
# @Author :king
# @File :test_params.py
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
@pytest.mark.parametrize("user,pwd", [("king", 123456), ("wangyong", 123456)])
def test_login(user, pwd):
assert user.startswith("king") and pwd == 123456
if __name__ == '__main__':
pytest.main()
```
执行结果为:
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210708160815134.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA0NTQxMTc=,size_16,color_FFFFFF,t_70)
在看下设置ids时:
```python
# -*- coding: utf-8 -*-
"""
# @Project :demo_test
# @Time :2021/7/8 8:33
# @Author :king
# @File :test_params.py
# @blog :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
"""
import pytest
@pytest.mark.parametrize("user,pwd", [("king", 123456), ("wangyong", 123456)],
ids=["正确用户名", "用户名错误"])
def test_login(user, pwd):
assert user.startswith("king") and pwd == 123456
if __name__ == '__main__':
pytest.main()
```
执行结果:
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210708162132370.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA0NTQxMTc=,size_16,color_FFFFFF,t_70)
### indirect、scope参数使用
备注:目前基本上不使用这两个参数,暂时不讲解,需要再加上!
## 总结
- @pytest.mark.parametrize() 参数可以有3中方式,例如 `"user,pwd"`、`["user", "pwd"]`、`("user", "pwd")`
- @pytest.mark.parametrize() 参数的值是可迭代的对象,每个迭代的值与参数个数必须保持一致
- @pytest.mark.parametrize() 参数ids的数量与参数的值个数必须保持一致
以上为内容纯属个人理解,如有不足,欢迎各位大神指正,转载请注明出处!
>**如果觉得文章不错,欢迎关注微信公众号,微信公众号每天推送相关测试技术文章**
>个人微信号:搜索 【测试之路笔记】