有两个接口,一个是订单创建接口,另一个是订单查询接口。在设计查询类接口的接口测试用例时,我习惯设计两条用例,一条是所有选填参数全部置空,另一条是所有选填参数全部选择相应的值。针对上述场景,我需要在编写查询接口测试用例时做参数化处理,需要传入两个参数,一个是 None,另一个则是创建订单接口创建成功后返回的新增订单 id。下文我将记录过程中遇到的问题和解决过程。
变量 a 为中间变量 - 订单 id,test_1 为新增订单接口,断言通过后将新增订单的 id 赋值给 a,test_2 为订单查询接口,参数化时传入新增订单 id,即 a 的值。
# 我期望结果是test_1断言通过后,将a的值改变成1,然后test_2测试时,a的值取1。
a = None
def test_1():
global a
assert 1 == 1
a = 1
@user1ize('p1,p2',[(a,1),(a,2)])
def test_2(p1,p2):
assert p1 == p2
运行后发现,test_2 中 a 的值取得是 None,而不是期望结果 1。查阅资料后得到结果:在 pytest 中,参数化是在收集阶段进行的,因此在收集阶段 a 的值已经被确定了,而不会在测试运行过程中动态改变。
这个时候我想到了列表,想尝试下是否可以利用 python 列表是可变数据类型的特性解决这个问题。(参数化收集阶段收集的是列表的引用,取值的时候取的是列表中的值,有搞头,试一试)
a = [None]
def test_1():
global a
assert 1 == 1
a[0] = 1
@user2ize('p1,p2',[(a,1),(a,2)])
def test_2(p1,p2):
assert p1[0] == p2
运行后发现,确实可以!test_2 中的 a 确实是 1,而不是 None,实践证明我的想法是对的。
a = [None]
def test_1():
global a
assert 1 == 1
a = [1]
@user3ize('p1,p2',[(a,1),(a,2)])
def test_2(p1,p2):
assert p1[0] == p2
运行后发现,又不行了。。a 又变成了 None。此处不细述心情不美丽的查找原因的过程。最终结论是在 test_1 中,当执行 a = [1] 时,它实际上创建了一个新的列表对象,并将 a 指向了这个新的列表对象,而不是修改了原来的列表对象的内容。因此由于收集阶段收集到的列表的内容没变,所以 a 始终是 None
a = None
def test_1():
global a
assert 1 == 1
a = 1
@user4ize('p1,p2',[([a],1),([a],2)])
def test_2(p1,p2):
assert p1[0] == p2
运行后发现,依旧达不到预期效果,a 始终是 None。在 gpt 上我查到了这个问题的回复,回复内容如下:
在你最新的修改中,问题出在参数化时对参数的传递上。虽然你将 a 包装在列表中,但是这并不会解决问题。因为在参数化时,pytest 会将参数值进行解析并传递给测试函数,这个过程是在测试收集阶段进行的,并不会等到测试执行时才解析。所以在参数化时,a 的值已经被解析为 None,并且被传递给了参数 p1,后续在测试执行时修改了 a 的值也不会影响参数化阶段已经确定的 p1 的值。
但我其实并不是很理解,尤其是答复中的 "因为在参数化时,pytest 会将参数值进行解析并传递给测试函数,这个过程是在测试收集阶段进行的,并不会等到测试执行时才解析。",为什么 [a] 会被解析成 None,而不是 [None]",如果有佬哥佬姐有更通俗易懂的原因,希望留言,解惑。
上述就是我对接口依赖情况下实现参数化的思考和时间,虽然在实例 1 中我们达到了预期效果,但我想可能在代码可读性上还有一些疑惑,有可能团队中的其他伙伴会对 a = [None] 这行代码中为什么要把 None 放在列表里不明所以。最后,我又想到了另一种能达到预期且代码可读性也比较好的解决办法。
a = None
def test_1():
global a
assert 1 == 1
a = 1
@user5ize('p1,p2',[(None,1),(None,2)])
def test_2(p1,p2):
if p1 is None:
p1 = a
assert p1 == p2
上面的代码即能实现预期效果,代码可读性也增强了不少,所以我就不再解释了。
作为测试小学生,在面对"接口依赖情况下实现参数化"这个问题时,上述是我能想到的解决办法,如果有佬哥佬姐有更好的解决办法,希望留言,解惑 。