背景
在需要每次都要进程一组设备测试的时候,需要用到fixtures参数化,比如需要对夜神和Honor 5C进行APP自动化测试,这时候参数化就可以起到很好的用途
这个两台设备实际应用中代码和报告:
params=["Honor_5C", "YeShen"] #登录:无toast弹框,不重置 @pytest.fixture(params=params) def login_common_driver(request): driver = BaseDriver().base_driver(device=request.param) is_welcome(driver) yield driver driver.close_app() driver.quit()
参数化用法
使用params
request是pytest中内建的fixture之一,代表fixture的调用状态,request.param作为返回值供测试使用。比如下面的例子,简单判断下拿到的request.param值有没有在原来的参数列表中。实际上就相当于遍历了一遍参数列表(参数化变量actual_url可以是列表或元组)们可以看到测试方法被调用了两次
#test_fixture_param.py import pytest actual_url = ["www.baidu.com", "www.google.com"] @pytest.fixture(params=actual_url) def back_actual_url(request): return request.param def test_url(back_actual_url): assert back_actual_url in actual_url
使用params + ids
如果没有指定ids,就是上面那个运行结果,额可以看到测试id拿的是参数列表中的元素。如果指定了ids,这个测试id就变成了了指定的值
#test_fixture_param.py import pytest actual_url = ["www.baidu.com", "www.google.com"] @pytest.fixture(params=actual_url, ids=["test001", "test002"]) def back_actual_url(request): return request.param def test_url(back_actual_url): assert back_actual_url in actual_url
ids也可以是一个函数,比如我们根据条件判断,如果默认id为"www.baidu.com",则通过idfunc()函数将"www.baidu.com"转化为"baidu",ids=idfunc用来接收转化后的值,然后指定id后,按照新的id值作为测试id
#test_fixture_param.py import pytest actual_url = ["www.baidu.com", "www.google.com"] def idfunc(fixture_value): if fixture_value == "www.baidu.com": return "baidu" else: return None @pytest.fixture(params=actual_url, ids=idfunc) def back_actual_url(request): return request.param def test_url(back_actual_url): assert back_actual_url in actual_url
参数化之笛卡尔乘积
如果有下面一种场景,即同一个测试方法同时调用两个fixture,而这两个fixture又各自有两个参数,那么总共有要执行多少次用例?执行结果又如何?
#test_fixture_param.py import pytest actual_url = ["www.baidu.com", "www.google.com"] expect_url = ["www.baidu.com", "www.google.com"] @pytest.fixture(params=actual_url) def back_actual_url(request): return request.param @pytest.fixture(params=expect_url) def back_expect_url(request): return request.param def test_url(back_actual_url, back_expect_url): assert back_actual_url == back_expect_url
答案是4次,执行结果是两个PASS,两个Fail,这就是笛卡尔乘积:A = (0, 1),B = (0, 1), A X B = { (0, 0), (0, 1) (1, 0), (1, 1) },只有 0 = 0, 1 = 1,其他两种不相等,所以Fail
fixture实例引起的测试自动分组
一个参数中的元素可以看做是一个fixture实例,有两个参数可以看做是调用了两次fixture实例。正如最开始提到的多个设备执行测试,有设备列表[夜神,Honor],有其他参数[1, 2],那么在执行必定是夜神-1,夜神-2,Honor-1,荣耀-2。举个更复杂的官方的例子:
#test_module.py
import pytest @pytest.fixture(scope="module", params=["mod1", "mod2"]) def modarg(request): param = request.param print(" SETUP modarg", param) yield param print(" TEARDOWN modarg", param) @pytest.fixture(scope="function", params=[1, 2]) def otherarg(request): param = request.param print(" SETUP otherarg", param) yield param print(" TEARDOWN otherarg", param) def test_0(otherarg): print(" RUN test0 with otherarg", otherarg) def test_1(modarg): print(" RUN test1 with modarg", modarg) def test_2(otherarg, modarg): print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 8 items
test_module.py::test_0[1] SETUP otherarg 1
RUN test0 with otherarg 1
PASSED TEARDOWN otherarg 1
test_module.py::test_0[2] SETUP otherarg 2
RUN test0 with otherarg 2
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod1] SETUP modarg mod1
RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
PASSED TEARDOWN otherarg 1
test_module.py::test_2[mod1-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod2] TEARDOWN modarg mod1
SETUP modarg mod2
RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
PASSED TEARDOWN otherarg 1
test_module.py::test_2[mod2-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
PASSED TEARDOWN otherarg 2
TEARDOWN modarg mod2
============================ 8 passed in 0.12s =============================
看到这个结果是不是很疑惑
画了一个不太准确的图,简单说明下,为什么是这种执行顺序?首先pytest是按照test_0,test_1,test_2的顺序执行的
想象fixture实例在使用时就相当于占用了一个资源,当test_0执行时,资源先被[1, 2]中的1占用,然后虽然test_3也有用到[1, 2],但没有test_1还没有开始,是不能进入test_3的,所以资源传递给了[1, 2]中的2,同理2执行结束后,就开始了test_1,所以能把资源传递给mod1,当mod1在test_1中执行完后,发现test_2要用到它,于是继续给test_2使用,test_2使用完后,mod1将资源传递给了mod2,这时候mod2交给test_1先使用,最后传递给test_2使用