接口测试 接口测试如何实现重复执行还能保持结果一致

醋精测试媛 · 2021年07月30日 · 最后由 回复于 2021年09月01日 · 3829 次阅读

比如增加接口,一般来说重复添加都会报错。那么如果第一次添加的用例发生了失败(添加成功了,但是查询或者校验时发生了失败),第二次执行时,必定会失败(因为重复添加),这样两次发生错误的原因不同了,有时候是无法真正探知到第一次失败的原因的,因为后面全是因为重复添加报错的。

在↑这种情况下,如果在最后使用删除接口将添加的内容删除掉,那么就不会影响测试的二次进行,我有点疑惑的是

  • 我可以使用这个接口吗,如果使用删除接口,我是否可以信任这个接口?还是说这种情况下默认这个删除接口是没有问题的。

  • 如果要” 重复执行保持一致 “, 删除接口也要” 先添加,再删除 “,这个接口的用例测试中是否也默认添加接口是没问题的?这个过程中我感觉都不仅仅是接口 A 依赖接口 B 这么简单了,而是接口的相互依赖了,感觉自己很难去告诉自己这样设计没问题。

请大家解惑释疑😀 😀 😀 ,以及请教是否存在更好的设计用例的方法!

最佳回复

有点懂了,自己也试了下,确实还是每个用例 setup 阶段就会创建新数据的方式,每次产生新的数据来解决问题,之前很疑惑 @ pytest.mark.parametrize()还是会导致两次数据一致 的问题,通过查阅一些资料发现用 @ pytest.fixture 里面的 params 和 ids 可以避免这个问题,也可以提供对用例的相关描述,并且即使不在一个文件也没有问题,如以下结构:

# data.py
fake = Faker()


def init_data(fixture_value):
    if fixture_value == 10:
        return "case 1"
    elif fixture_value == 20:
        return "case 2"
    elif fixture_value == 30:
        return "case 3"


@pytest.fixture(params=[10, 20, 30], ids=init_data)
def init_data_by_status(request):
    status = request.param
    # status 标识不同的用例!
    print("fixture 参数", status)
    # 这里创建新数据!
    uid = fake.pyint(min_value=10086, max_value=99999, step=1)
    yield dict(.....)# 传入的数据

    # 这里可以清理数据操作,也可以定时清理,因为我的日志还比较完整,所以排查问题并不难,我选择的是即时清理。

# test.py
def test(init_data_by_status)
    # 业务流程开始

谢谢大家的指教~ 学习到了很多细节上的问题的处理方法!

共收到 24 条回复 时间 点赞

正统做法:
1、tearDown 里做好删除(调删除接口或直接删除数据库数据)。方便重复使用。
2、每次都重新初始化完整数据库内容,保证干净

但我们是金融类系统,为了方便回溯,系统其实是没有任何硬删除的。软删除且添加时带有一些不能重复的 key,会导致二次添加直接失败。直接删数据也不容易删干净,各个系统间有比较多关联关系。

这种情况下我们的偏门解法:每次都用新数据。从用户注册开始,全部都是新的数据。

添加用户是一个接口,有成功响应以及各种失败响应。
"输入合法用户信息,能够成功添加用户"这是一条用例,执行前得保证构造合法数据,想避免重复用时间戳做后缀是个不错的方法。
“输入已存在用户信息,提示用户重复” 这是另一条用例,执行前得保证该信息一定已在库中。
上面两条用例都是调用添加这个接口。
至于删除相关的用例,应当有一个前置条件,前置条件可以是通过调用添加接口或其他方式来实现。
所谓测试技术还是得围绕基础理论来转啊。

墨妖 回复

添加,然后查询,但是查询出错了,此时失败重跑,第二次,添加,这时会在添加时出错,因为实际上上一次添加成功了,所以这次再依次添加,会报错” 重复添加数据 “,所以两次报错原因会不一样,其他的上面说了。

MarvinWu 回复

"输入合法用户信息,能够成功添加用户"这是一条用例,执行前得保证构造合法数据,想避免重复用时间戳做后缀是个不错的方法。

如果是失败重跑的话,两次应该只能是一样的数据(即使添加了时间戳,但是通过@pytest.mark.parametrize()放进来的数据是不会变的),还是说这样必须在用例的流程中造数据来避免重复,但是这样不就违背了数据和业务分离的想法了吗?

在添加用例执行前,到数据库进行操作,删除你要添加的数据。

dubda 回复

开发不支持对数据库的增删查改

陈恒捷 回复

请问您是如何设计失败重跑机制的呢?

我之前看到过的失败重跑机制有:

  1. pytest-rerunfailures 失败后 x 秒后重跑,在这个情况下,即使每次都是新的数据,重跑时的数据和第一次数据肯定是一样的,就会回到上面的问题;
  2. -lf 全部运行完之后重跑,之前使用了-lastfailed ,但是觉得它没有那么灵活,无法设置重跑次数。总觉得没那么方便也没那么好用(有时候是用例里面和 teardown 都出错了,但是它算两次测试失败等)
陈恒捷 回复

新数据我觉得不算偏门,我们也是这么做。
teardown 删除我觉得并不是很好,不利于排查问题。

我们用的 java 的 testng ,重跑机制和你第二个比较接近。

没太明白你说的 重跑时的数据和第一次数据肯定是一样的 是为啥?我们每个用例 setup 阶段就会创建新数据了,所以重跑用的数据不会和第一次一样的。

花菜 回复

哈哈,没想到还有一样做法的。握个爪

我觉得偏门,主要是和自动化测试的一些原则违背了。大量的一次性数据,虽然不影响什么,但还是会有点不大舒服。

陈恒捷 回复

自动化产生的数据是要清理,但立刻清理的做法我认为不妥。定期清理可能会更好。

陈恒捷 回复

我不是在 setup 中创数据的,而是直接弄一个数据模块,然后在测试用例中导入这个模块,因为有很多用例,所以没办法直接在 setup 中实现创造所有数据,比如 a 用例是字段 1 为空,b 用例字段 2 为空,因此我是直接重新设置了一个模块。但是这样也导致,导入之后,用例的数据不会发生改变,但是第一次运行和第二次运行的数据不一样。

花菜 回复

+1 立即清理可能造成用例失败难以排查

是否可以考虑唯一性字段使用动态变量(随机数、时间戳、UUID 等?)

teardown 吧。teardown 的时候把你排查的时候要看的东西记录下来就行。
现在做的接口项目,我把重试机制直接干掉了,感觉没啥必要。如果报错,肯定是哪里有了问题,没必要重试。(当然不同项目可能不一样)
PS:接口测试,默认稳定。

cheunghr 回复

请问排查的时候需要看什么呢?为什么删掉数据无法排查?

Ouroboros 回复

还是需要重跑的,因为可能是用例比较多,出现过不稳定的情况,有时候运行会失败,但是有时候运行又没事

陈恒捷 回复

比如:

data1.py

data_of_add_interface = [
        { 
              "input": "a",
              "expected": "error"
        }, 
        { 
              "input": "123",
              "expected": "error"
        }, 
        ...
]

test_case.py

from data1.py import data_of_add_interface

@  pytest.mark.parametrize("data", data_of_add_interface)
def test_case_1(data):
        response = request_interface(data["input"])
        assert data["expected"] == response

main.py

pytest.main(["./tests/test_case.py", "--reruns=2", "--reruns-delay=10", "-s"])

所以执行第一条用例时,两次重跑,data 中的数据是一样的,都是输入” a“,当然,data.py 中,我是使用了每次都是新数据的,但是执行重跑时还是一样。

cheunghr 回复

如 19 楼的解释,请问使用了唯一性字段使用动态变量(随机数、时间戳、UUID 等?)之后仍然无法结合重试机制进行怎么办?

想确认下,你的 data.py 数据的更新,是在什么时候更新的?

听你意思,是在运行所有用例前?

如果是,时机改为是运行任意用例前,那是不是就可以做到不管是首次执行还是重试执行,都会使用新的数据了?

PS:我觉得你问题的核心,是先排查清楚用例不稳定的原因,针对性解决,而不是纠结怎么重跑?

有点懂了,自己也试了下,确实还是每个用例 setup 阶段就会创建新数据的方式,每次产生新的数据来解决问题,之前很疑惑 @ pytest.mark.parametrize()还是会导致两次数据一致 的问题,通过查阅一些资料发现用 @ pytest.fixture 里面的 params 和 ids 可以避免这个问题,也可以提供对用例的相关描述,并且即使不在一个文件也没有问题,如以下结构:

# data.py
fake = Faker()


def init_data(fixture_value):
    if fixture_value == 10:
        return "case 1"
    elif fixture_value == 20:
        return "case 2"
    elif fixture_value == 30:
        return "case 3"


@pytest.fixture(params=[10, 20, 30], ids=init_data)
def init_data_by_status(request):
    status = request.param
    # status 标识不同的用例!
    print("fixture 参数", status)
    # 这里创建新数据!
    uid = fake.pyint(min_value=10086, max_value=99999, step=1)
    yield dict(.....)# 传入的数据

    # 这里可以清理数据操作,也可以定时清理,因为我的日志还比较完整,所以排查问题并不难,我选择的是即时清理。

# test.py
def test(init_data_by_status)
    # 业务流程开始

谢谢大家的指教~ 学习到了很多细节上的问题的处理方法!

这个属于测试数据管理方面的问题了,一般来说都是通过全局和局部 fixture 组合提供测试数据,每次使用 fixture 都是新的数据处理的,这样接口之间数据依赖就会解耦。

感觉属于职责不清晰
1:添加后查询出错,就要看为什么查询出错
2:添加后再删除,是不是默认添加是正常的?是的,因为添加接口是在其它用例来保证的

添加删除都有,那假设还有个查询接口也合理吧
添加然后查询,删除了再次查询,下次还是这一套,会有什么问题吗

兔子🐰 [该话题已被删除] 中提及了此贴 02月18日 16:23
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册