实际场景描述

有两个接口,一个是订单创建接口,另一个是订单查询接口。在设计查询类接口的接口测试用例时,我习惯设计两条用例,一条是所有选填参数全部置空,另一条是所有选填参数全部选择相应的值。针对上述场景,我需要在编写查询接口测试用例时做参数化处理,需要传入两个参数,一个是 None,另一个则是创建订单接口创建成功后返回的新增订单 id。下文我将记录过程中遇到的问题和解决过程。

抽离成 demo

变量 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 的值已经被确定了,而不会在测试运行过程中动态改变。

思考:能不能在参数化数据收集阶段,收集到的 a 是一个引用,而不是具体的值,在真正用 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,实践证明我的想法是对的。

可我总觉着 test_1() 中的赋值语句有点别扭,如果换成 a = [1] 行不行?

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] 很别扭,如果换成 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

上面的代码即能实现预期效果,代码可读性也增强了不少,所以我就不再解释了。

作为测试小学生,在面对"接口依赖情况下实现参数化"这个问题时,上述是我能想到的解决办法,如果有佬哥佬姐有更好的解决办法,希望留言,解惑🍻


↙↙↙阅读原文可查看相关链接,并与作者交流