加入收藏 | 设为首页 | 会员中心 | 我要投稿 衡阳站长网 (https://www.0734zz.cn/)- 数据集成、设备管理、备份、数据加密、智能搜索!
当前位置: 首页 > 综合聚焦 > 资源网站 > 资源 > 正文

Pytest之测试的参数化

发布时间:2019-12-24 05:31:09 所属栏目:资源 来源:博客园
导读:在实际工作中, 测试 用例 可能需要支持多种场景,我们可以把和场景强相关的部分抽象成参数,通过对参数的赋值来驱动用例的执行; 参数化的行为表现在不同的层级上: fixture的参数化:参考 4、fixtures:明确的、模块化的和可扩展的 -- fixture的参数化;
  在实际工作中,测试用例可能需要支持多种场景,我们可以把和场景强相关的部分抽象成参数,通过对参数的赋值来驱动用例的执行;  参数化的行为表现在不同的层级上:  fixture的参数化:参考 4、fixtures:明确的、模块化的和可扩展的 -- fixture的参数化;  测试用例的参数化:使用@pytest.mark.parametrize可以在测试用例、测试类甚至测试模块中标记多个参数或fixture的组合;  另外,我们也可以通过pytest_generate_tests这个钩子方法自定义参数化的方案;  1. @pytest.mark.parametrize标记  @pytest.mark.parametrize的根本作用是在收集测试用例的过程中,通过对指定参数的赋值来新增被标记对象的调用(执行);  首先,我们来看一下它在源码中的定义:  着重分析一下各个参数:  argnames:一个用逗号分隔的字符串,或者一个列表/元组,表明指定的参数名;  对于argnames,实际上我们是有一些限制的:  只能是被标记对象入参的子集:  test_sample中并没有声明expected参数,如果我们在标记中强行声明,会得到如下错误:  不能是被标记对象入参中,定义了默认值的参数:  虽然test_sample声明了expected参数,但同时也为其赋予了一个默认值,如果我们在标记中强行声明,会得到如下错误:  会覆盖同名的fixture:  test_sample标记中的expected(2)覆盖了同名的fixture expected(1),所以这条用例是可以测试成功的;  argvalues:一个可迭代对象,表明对argnames参数的赋值,具体有以下几种情况:  如果argnames包含多个参数,那么argvalues的迭代返回元素必须是可度量的(即支持len()方法),并且长度和argnames声明参数的个数相等,所以它可以是元组/列表/集合等,表明所有入参的实参:  注意:考虑到集合的去重特性,我们并不建议使用它;  如果argnames只包含一个参数,那么argvalues的迭代返回元素可以是具体的值:  如果你也注意到我们之前提到,argvalues是一个可迭代对象,那么我们就可以实现更复杂的场景;例如:从excel文件中读取实参:  实现这个场景有多种方法,你也可以直接在一个fixture中去加载excel中的数据,但是它们在测试报告中的表现会有所区别;  或许你还记得,在上一篇教程(10、skip和xfail标记 -- 结合pytest.param方法)中,我们使用pytest.param为argvalues参数赋值:  现在我们来具体分析一下这个行为:  无论argvalues中传递的是可度量对象(列表、元组等)还是具体的值,在源码中我们都会将其封装成一个ParameterSet对象,它是一个具名元组(namedtuple),包含values, marks, id三个元素:  如果直接传递一个ParameterSet对象会发生什么呢?我们去源码里找答案:  可以看到如果直接传递一个ParameterSet对象,那么返回的就是它本身(return parameterset),所以下面例子中的两种写法是等价的:  到这里,或许你已经猜到了,pytest.param的作用就是封装一个ParameterSet对象;那么我们去源码里求证一下吧!  正如我们所料,现在你应该更明白怎么给argvalues传参了吧;  indirect:argnames的子集或者一个布尔值;将指定参数的实参通过request.param重定向到和参数同名的fixture中,以此满足更复杂的场景;  具体使用方法可以参考以下示例:  ids:一个可执行对象,用于生成测试ID,或者一个列表/元组,指明所有新增用例的测试ID;  如果使用列表/元组直接指明测试ID,那么它的长度要等于argvalues的长度:  搜集到的测试ID如下:  如果指定了相同的测试ID,pytest会在后面自动添加索引:  搜集到的测试ID如下:  如果在指定的测试ID中使用了非ASCII的值,默认显示的是字节序列:  搜集到的测试ID如下:  可以看到我们期望显示中文,实际上显示的是u4e2du6587;  如果我们想要得到期望的显示,该怎么办呢?去源码里找答案:  我们可以通过在pytest.ini中使能disable_test_id_escaping_and_forfeit_all_rights_to_community_support选项来避免这种情况:  再次搜集到的测试ID如下:  如果通过一个可执行对象生成测试ID:  搜集到的测试ID如下:  通过上面的例子我们可以看到,对于一个具体的argvalues参数(1, 2)来说,它被拆分为1和2分别传递给idfn,并将返回值通过-符号连接在一起作为一个测试ID返回,而不是将(1, 2)作为一个整体传入的;  下面我们在源码中看看是如何实现的:  和我们猜想的一样,先通过zip(parameterset.values, argnames)将argnames和argvalues的值一一对应,再将处理过的返回值通过"-".join(this_id)连接;  另外,如果我们足够细心,从上面的源码中还可以看出,假设已经通过pytest.param指定了id属性,那么将会覆盖ids中对应的测试ID,我们来证实一下:  搜集到的测试ID如下:  测试ID是id_via_pytest_param,而不是second;  讲了这么多ids的用法,对我们有什么用呢?  我觉得,其最主要的作用就是更进一步的细化测试用例,区分不同的测试场景,为有针对性的执行测试提供了一种新方法;  例如,对于以下测试用例,可以通过-k 'Window and not Non'选项,只执行和Windows相关的场景:  scope:声明argnames中参数的作用域,并通过对应的argvalues实例划分测试用例,进而影响到测试用例的收集顺序;  如果我们显式的指明scope参数;例如,将参数作用域声明为模块级别:  搜集到的测试用例如下:  以下是默认的收集顺序,我们可以看到明显的差别:  scope未指定的情况下(或者scope=None),当indirect等于True或者包含所有的argnames参数时,作用域为所有fixture作用域的最小范围;否则,其永远为function;  test_input和expected的作用域都是module,所以参数的作用域也是module,用例的收集顺序和上一节相同:  1.1. empty_parameter_set_mark选项  默认情况下,如果@pytest.mark.parametrize的argnames中的参数没有接收到任何的实参的话,用例的结果将会被置为SKIPPED;  例如,当python版本小于3.8时返回一个空的列表(当前Python版本为3.7.3):  我们可以通过在pytest.ini中设置empty_parameter_set_mark选项来改变这种行为,其可能的值为:  skip:默认值  xfail:跳过执行直接将用例标记为XFAIL,等价于xfail(run=False)  fail_at_collect:上报一个CollectError异常;  1.2. 多个标记组合  如果一个用例标记了多个@pytest.mark.parametrize标记,如下所示:  实际收集到的用例,是它们所有可能的组合:  1.3. 标记测试模块  我们可以通过对pytestmark赋值,参数化一个测试模块:  2. pytest_generate_tests钩子方法  pytest_generate_tests方法在测试用例的收集过程中被调用,它接收一个metafunc对象,我们可以通过其访问测试请求的上下文,更重要的是,可以使用metafunc.parametrize方法自定义参数化的行为;  我们先看看源码中是怎么使用这个方法的:  首先,它检查了parametrize的拼写错误,如果你不小心写成了["parameterize", "parametrise", "parameterise"]中的一个,pytest会返回一个异常,并提示正确的单词;然后,循环遍历所有的parametrize的标记,并调用metafunc.parametrize方法;  现在,我们来定义一个自己的参数化方案:  在下面这个用例中,我们检查给定的stringinput是否只由字母组成,但是我们并没有为其打上parametrize标记,所以stringinput被认为是一个fixture:  现在,我们期望把stringinput当成一个普通的参数,并且从命令行赋值:  首先,我们定义一个命令行选项:  然后,我们通过pytest_generate_tests方法,将stringinput的行为由fixtrue改成parametrize:  最后,我们就可以通过--stringinput命令行选项来为stringinput参数赋值了:  如果我们不加--stringinput选项,相当于parametrize的argnames中的参数没有接收到任何的实参,那么测试用例的结果将会置为SKIPPED   注意:  不管是metafunc.parametrize方法还是@pytest.mark.parametrize标记,它们的参数(argnames)不能是重复的,否则会产生一个错误:ValueError: duplicate 'stringinput';

(编辑:衡阳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读