我们都知道参数化。
比如我要测试一个查询接口/test/get_goods_list
,这个接口可以查询到商品的信息。
在请求中,我可以根据请参数goods_status的不同传值,可以查询到对应状态的商品数据,比如:1-未销售
、2-销售中
、3-已售罄
。
那么在编写自动化测试case的时候,在断言里就要分别验证到这3种状态的商品数据。
通常,在执行case之前,会去数据库分别插入对应状态的商品数据,来满足测试需求。
而在pytest框架中,我喜欢用fixture()
去实现测试数据的准备和清理工作。
于是,2种实现方式瞬间出现:
- 写3个case,只有传参不一样。对应写3个fixture来分别初始化3种状态的商品数据。
- 写1个case,在case里用
@pytest.mark.parametrize()
进行参数化。只写一个fixture一次性的插入3种状态的商品数据。
在这2个方法里,显然第二种更优雅,避免了case的冗余代码。
但是一把梭的插入所有的测试数据还是差点意思,如果我只想执行其中的某一个数据的case,那么其他2个不必要的数据也生成了。
所以,我想要的样子是,可以自由的根据执行的case的参数,去对应的初始化测试数据。
具体点的描述就是:参数化里3个参数,我只执行2-销售中
的时候,只去插入2-销售中
这一种状态的数据。
网上搜的都是简单的fixture参数化的东西,达不到我想要的需求。于是乎我自己去翻阅官方文档,发现可以用fixture中的params和ids
这2个参数去实现我的需求。
一、fixture中的params
params
是一个列表,用来存放我们要参数化的值。
举例:这里的代码放了参数1
和参数2
这2个参数,2个测试case,都会用params
里的参数去分别执行2次。
import pytest @pytest.fixture(params=['参数1', '参数2']) def my_fixture(request): return request.param def test_fixtures_01(my_fixture): print('\n 执行test_fixtures_01') print(my_fixture) def test_fixtures_02(my_fixture): print('\n 执行test_fixtures_02') print(my_fixture)
运行一下:
collecting ... collected 4 items test_ids.py::test_fixtures_01[\u53c2\u65701] PASSED [ 25%] 执行test_fixtures_01 参数1 test_ids.py::test_fixtures_01[\u53c2\u65702] PASSED [ 50%] 执行test_fixtures_01 参数2 test_ids.py::test_fixtures_02[\u53c2\u65701] PASSED [ 75%] 执行test_fixtures_02 参数1 test_ids.py::test_fixtures_02[\u53c2\u65702] PASSED [100%] 执行test_fixtures_02 参数2 ============================== 4 passed in 0.03s ============================== Process finished with exit code 0
二、fixture中的ids
ids
也是要结合着params
一起使用的。当有多个 params
时,针对每一个 param
,可以指定一个id
,
然后,这个 id
会变成测试用例名字的一部分。如果没有提供 id
,则 id
将自动生成。
import pytest @pytest.fixture(params=['参数1', '参数2'], ids=["id-01", "id-02"]) def my_fixture(request): return request.param def test_fixtures_01(my_fixture): print('\n 执行test_fixtures_01') print(my_fixture)
运行下,结果里case名称后分别带了 id:[id-01]
和[id-02]
collecting ... collected 2 items test_ids.py::test_fixtures_01[id-01] PASSED [ 50%] 执行test_fixtures_01 参数1 test_ids.py::test_fixtures_01[id-02] PASSED [100%] 执行test_fixtures_01 参数2 ============================== 2 passed in 0.02s ============================== Process finished with exit code 0
三、利用ids实现需求
ids的赋值除了上述的方式之外,还有一种,也就是帮我实现需求的一种,直接看代码。
import pytest def init_data(fixture_value): if fixture_value == 1: return "unsold" elif fixture_value == 2: return "onSale" elif fixture_value == 3: return "sellOut" @pytest.fixture(params=[1, 2, 3], ids=init_data) def my_method(request): req_param = request.param print("\n参数为:【{}】,执行sql--插入【{}】状态的数据".format(req_param, req_param)) yield req_param print("\n执行sql--清理参数为【{}】的测试数据".format(req_param, req_param)) print("\n----------------------------------------") def test_01(my_method): print("\n正在执行【{}】的case--".format(my_method)) if __name__ == '__main__': pytest.main(['-s', '-v', 'test_my_fixture.py::test_01'])
示例代码就没有去真正的写一个接口的case了,因为太(lan)忙了,所以直接用print()打印出我要做的事儿。
上述代码中,重点就是3个部分:
test_01()
,这是测试casemy_method()
这是我定义的fixture函数init_data()
这个是用来初始化测试数据的函数
1、test_01()
测试case没什么说的,一般来说接口的case里的组成就是:传参
、调用测试接口
、断言
。
case里传入了my_method
函数,这是调用fixture
的一种方式。
def test_01(my_method): print("\n正在执行[{}]的case--".format(my_method))
2、my_method()
我定义了my_method这个fixture去进行case执行之前的测试数据处理。
@pytest.fixture(params=[1, 2, 3], ids=init_data) def my_method(request): req_param = request.param print("\n参数为:{}".format(req_param)) yield req_param print("\n执行sql--清理参数为【{}】的测试数据".format(req_param)) print("\n----------------------------------------")
params=[1,2,3]
就是相当于我接口请求体里查询不同状态商品的数据对应的参数,1-未销售
、2-销售中
、3-已售罄
。
ids
在上面单独介绍的例子中是直接赋值的,但是在这里,我是把一个函数赋给了它,这个函数就是init_data()
。
当然了,为了满足后面我的指定参数执行case的需求,init_data
要返回具体的id
才行。
3、init_data()
init_data(fixture_value)
函数里传入的fixture_value
,其实就是fixture
函数里的params=[1, 2, 3]
。
return
出来的则是这个参数对应的id,分别是"未销售"
、"销售中"
、"已售罄"
,那么我就可以通过pytest命令 加上 -k
来指定要运行的case。
def init_data(fixture_value): if fixture_value == 1: return "unsold" elif fixture_value == 2: return "onSale" elif fixture_value == 3: return "sellOut"
先不用-k
,看下整个的运行结果。
if __name__ == '__main__': pytest.main(['-s', '-v', 'test_my_fixture.py::test_01'])
运行结果:
collected 3 items test_my_fixture.py::test_01[\u672a\u9500\u552e] 参数为:【1】,执行sql--插入【1】状态的数据 正在执行【1】的case-- PASSED 执行sql--清理参数为【1】的测试数据 ---------------------------------------- test_my_fixture.py::test_01[\u9500\u552e\u4e2d] 参数为:【2】,执行sql--插入【2】状态的数据 正在执行【2】的case-- PASSED 执行sql--清理参数为【2】的测试数据 ---------------------------------------- test_my_fixture.py::test_01[\u5df2\u552e\u7f44] 参数为:【3】,执行sql--插入【3】状态的数据 正在执行【3】的case-- PASSED 执行sql--清理参数为【3】的测试数据 ---------------------------------------- ============================================================================= 3 passed in 0.03s =============================================================================
可以看到,在case执行之前,就插入了3种状态的测试数据,并且运行了3个case。
接下来,我们用-k
来执行 id是"onSale"
的case:
if __name__ == '__main__': pytest.main(['-s', '-v', '-k', "onSale", 'test_my_fixture.py::test_01'])
运行结果:
collected 3 items / 2 deselected / 1 selected test_my_fixture.py::test_01[onSale] 参数为:【2】,执行sql--插入【2】状态的数据 正在执行【2】的case-- PASSED 执行sql--清理参数为【2】的测试数据 ---------------------------------------- ====================================================================== 1 passed, 2 deselected in 0.03s ======================================================================
可以看到,找到了3个case,但是只执行了我们制定要运行的case。
在我以前写的case中,其实并没有这样写。之前我们写了一个mock服务,于是乎我就把一些会变化的请求参数也配置进去了,然后根据我
传参的不同,去拿到我想要的请求body,最后再去请求我要测试的接口。
现在新换了个地方,每天忙于dian业dian务dian,工作中突然有了这个想法,于是乎抽空找寻了下方法。这个方法我在后面的自动化服务搭建
中会去运用,届时有新想法再分享。