自动化测试中对多断言的思考和实践

最近笔者在测试一个数据下载项目,测试过程中需要校验导出 CSV 文件中指标值的准确性。涉及指标数量 400+,并且需要关注不同聚合层级、不同时间粒度指标计算的准确性。验证数据量大,手工验证效率低、人力成本高,因此通过自动化脚本的方式来提高测试效率以及方便回归验证。
在实现的过程中,最初采用 unittest 测试框架中内置的断言方法,进行数据的比对,实践发现内置断言并不能满足使用要求,进行了一系列的实践后,最终找到了合适的断言方案,本篇文章主要介绍了该方案的探索过程。

unittest 框架内置的 assertEqual 断言
最初使用 unittest 测试框架中的内置的 assertEqual 断言方法,进行实际值与预期值的校验,assertEqual 方法的实现逻辑如下:
1. 如果断言失败,则抛出一个 AssertionError,并标识该测试为失败状态
2. 如果断言成功,则标识该测试为成功状态
指标验证过程中,一个测试用例中需要包含多个指标断言,代码如下图所示:

执行结果如下图所示,一个 assertEqual 断言失败后,抛出异常,该条用例即被终止,后续断言不会被继续执行,从而无法得知后续的断言执行情况,导致该自动化测试无法一次性反馈所有测试结果,与使用场景要求不符。

一个断言一个方法
为了解决以上方案中断言失败后不再继续执行其他断言的问题,将用例进行拆分,将每个指标的断言放在独立的方法中,这样单个指标断言失败,不会中断其他指标的验证,每个方法中的断言都会被执行。
实现代码如下图所示:


如下图中的执行结果可见,虽然 test_two 方法中的断言失败了,但是后续的 test_two1 方法中的断言仍可正常执行,并且在全部执行完成后返回异常结果。

此方案虽然解决了断言失败不继续执行的问题,但是需要为每个指标创建独立的方法去进行结果校验。在指标数量非常多的情况下,此方案会造成代码的大量冗余,且执行效率较低。
重写 unittest 断言方法
为了从根本上解决断言失败后阻断其他断言继续执行的问题,将 unittest 断言方法进行了重写。
实现思路:通过自定义类 checkPoint 封装 unittest 断言,将 assertEqual 放入异常捕获代码,断言失败时,异常捕获模块会采集失败信息以及失败次数(记录在 flag 中),即使有断言失败,也会继续执行其他断言。

如下图所示代码,最后获取用例执行结果时,返回断言失败的信息:

测试用例继承父类 checkPoint,使用封装的方法,代码示例如下图所示:

使用过程中,当有断言失败后,其他断言也会继续执行,并在最后返回失败断言的信息,运行结果如下图所示:

使用 pytest 框架
除了 unittest 框架断言方法外,较为流行的 pytest 框架也提供了内置的断言方法,下面让我们来研究一下,使用 pytest 框架实现的解决方案。
1. pytest 框架中有内置的 assert 断言方法
assert 断言方法可以实现结果的校验,案例代码如下:

执行结果如下:
①. 当(x, y)=(1,1)时断言 3 失败;

②. 当(x, y)=(1,0)时断言 1 失败,断言 2 和断言 3 不执行;

由此可以看出 assert 方法一旦遇到断言失败则停下,无法实现一个方法中多个断言同时执行的需求。
2. 使用 pytest 框架 pytest-assume 插件
pytest-assume 插件可以解决原生 assert 断言方法存在的问题,案例代码如下:

执行结果如下:
①. 当(x, y)=(1,1)时断言 3 失败;

②. 当(x, y)=(1,0)时断言 1 失败,断言 2 和断言 3 继续执行;

由此可以看出 pytest-assume 插件可以实现单个断言失败的情况,其他断言依然会被执行。
3. 上下文管理器 with
pytest-assume 也可通过上下文管理器 with 使用,从执行结果中看出,与以上方法中直接使用 pytest-assume 插件相比,使用上下文管理器的好处是不用 try 和 finally 捕获异常,更加简洁有效。


如上图所示,在使用上下文管理器时需要注意,一个 with 语句只能跟一个 assert 语句。如果一个 with 语句包含多个断言,当前面的断言失败的时候,后面的断言依然不会执行。
总结
以上是指标校验脚本中断言方案探索的过程,以及最终的解决方案和效果,希望本文能为大家解决类似的问题提供一些思路。
评论