RFC 72:更新自动测试套件以使用pytest

作者:

克雷格德斯蒂格

联系方式:

craig.destigter@koordinates.com

起动:

2018年9月27日

状态:

在GDAL 2.4中实现

总结

本文提出并描述了将现有Python自动测试套件转换为使用 pytest framework .

与当前自行开发的方法相比,使用pytest为编写、读取和调试python测试提供了显著的生产率提高。

动机

当前的autotest框架可以追溯到2007年(至少),虽然开发人员很难使用和扩展相当全面的框架(以及186000行Python)。

  • 作为一个自主开发的框架,它永远不会比GDAL开发人员投入的精力更好。例如:报告、测试覆盖率、并行化、恢复、日志/输出处理、参数化。

  • 测试失败通常只是描述为“失败”,确定原因需要编辑测试。

  • 很难运行/重新运行单个测试

  • 测试通常假定一组编译选项,这些选项可能对本地生成无效。

  • 测试在各种CI环境中由测试树外的脚本修补/禁用。这对于在本地工作的开发人员来说是不透明的。

  • 有些测试依赖于彼此和特定的执行顺序,因此很难进行调试和扩展。

  • 跨测试和模块重复共享功能

  • 测试通常只为新功能编写,而不是回归。(粗略地说,在去年的2663次提交中,只有725次触及了自动测试树)

通过在广泛使用中采用OSS测试框架,我们可以利用生态系统为GDAL提供好处和改进。自动化测试的实用性已经在GDAL中得到了验证,我们需要使测试编写尽可能简单。

提案

移植现有的Python自动测试套件以使用 pytest framework . 为什么是pytest?它被广泛使用,具有广泛的特性,可以通过插件进行扩展,并且专注于使编写和调试测试尽可能简单——最小化样板代码并最大限度地重用。 This presentation (尽管可以追溯到2014年)简要概述了主要好处。

使用自动代码重构工具来完成这个端口的大部分工作,以便自动测试套件与首选的pytest方法相匹配。虽然pytest支持各种定制的测试收集和执行方法,但是为了增加开发人员的收益,我们应该进行适当的转换。最初的目标是移植测试,删除尽可能多的样板文件,同时保持现有的CI为绿色。未来的目标是继续减少样板代码并增加测试之间的隔离。

我们至少还需要保持现有的能力:

  • 使用现有配置在所有环境中运行所有现有的CI测试

  • 运行单个测试模块

  • 支持现有子进程/多进程测试

  • 支持Python2.7和Python3下的测试

  • 断言失败的堆栈跟踪

新的测试套件将在2018年12月为GDAL 2.4.0版本提供。更改不会被移植到2.3.x或更早版本的分支。

参考文献:

例子

一个典型的现有GDAL python单元测试:

def test_gdaladdo_1():
    if test_cli_utilities.get_gdaladdo_path() is None:
        return 'skip'

    shutil.copy('../gcore/data/mfloat32.vrt', 'tmp/mfloat32.vrt')
    shutil.copy('../gcore/data/float32.tif', 'tmp/float32.tif')

    (_, err) = gdaltest.runexternal_out_and_err(test_cli_utilities.get_gdaladdo_path() + ' tmp/mfloat32.vrt 2 4')
    if not (err is None or err == ''):
        gdaltest.post_reason('got error/warning')
        print(err)
        return 'fail'

    ds = gdal.Open('tmp/mfloat32.vrt')
    ret = tiff_ovr.tiff_ovr_check(ds)
    ds = None

    os.remove('tmp/mfloat32.vrt')
    os.remove('tmp/mfloat32.vrt.ovr')
    os.remove('tmp/float32.tif')

    return ret

最后 变成这样

@pytest.mark.require_files('gcore/data/mfloat32.vrt', 'gcore/data/float32.tif')
def test_gdaladdo_1(gdaladdo):
    gdaladdo('gcore/data/mfloat32.vrt 2 4')
    assert os.path.exists('gcore/data/mfloat32.vrt.ovr')

    tiff_ovr.tiff_ovr_check(gdal.Open('mfloat32.vrt'))

实际上测试的内容要清楚得多,而且所有支持功能都由共享使用fixture处理 (gdaladdo & require_files ),包括清除和条件跳过。

测试输出

Pytest开箱即用产生可读输出,并由 pytest-sugar 使其更加美观的插件:

  • 成功的测试不会产生太多输出(单个 . 默认情况下,每次测试)

  • 失败的测试会产生回溯。失败测试生成的任何日志、stdout和stderr也将被打印。这是调试失败原因的良好开端。

  • 将打印失败断言中使用的任何表达式。

  • 如果终端支持测试输出,则测试输出明显着色(红色/绿色)。

![](pytest-output-example.png,626px,中心)

计划阶段1

进展 pull request 963 .

  • 使用代码自动化,将现有的Python自动测试套件转换为使用pytest样式的断言。

  • 将所有测试重命名为 test_*() . Pytest通过将名称与regex匹配来查找测试,这是默认的regex。

  • 从生成断言 post_reason()/return 'fail' 尽可能打电话

  • 全部替换 skip/fail /``success``返回值

  • 删除多余的 ../pymod 条目来自 sys.path . 所有测试现在都在一个进程中运行

  • 去除 __main__ 封锁和 gdaltest_list 从测试文件

  • 它们共同实现了更好的测试收集/选择、输出捕获和改进的断言和报告

  • 手动将动态生成的测试转换为使用 parametrization

  • 确保慢速/internet测试仍标记为慢速,并且默认情况下跳过。

  • 使用 pytest-sugar 使测试输出美观。在CI中禁用它,因为它不能很好地与travis CI的输出缓冲一起工作。

  • 将特定于环境的测试跳过从CI移动到测试套件,可能需要附加标记。

  • 确保现有的CI测试通过并调试任何失败

  • 为pytest本身添加文档和简单的安装过程

显著变化及其启示

  • 测试现在使用 cd autotest ; pytest . (第一次你可能需要 pip install -r requirements.txt 安装pytest)

  • 所有测试现在都在一个进程中运行(它们以前是为每个测试模块派生的)。这意味着:

    • 测试收集过程中的错误现在很严重,并且会立即通过回溯使整个测试运行失败。以前,诸如文件中的语法错误和模块级的错误很容易被忽略。

    • 一个segfault将杀死整个测试运行死机。

  • 现在可以运行单独的测试,而不仅仅是整个文件。但是,测试是 还没有相互独立 . 因此,这可能会导致测试的行为与运行整个模块时不同。

  • test_py_scripts.run_py_script 已修改为始终将脚本作为子进程运行。原始方法的stdout捕获对pytest做了一些奇怪的事情。这个更改打破了一些依赖于在 /vsimem/ 根到脚本,因此这些脚本已更改为使用 tmp/ 而是根。

  • 不支持Python<2.7的测试套件

计划第2阶段/未来工作

  • 改进测试隔离,因此不需要一次运行整个模块。

  • 删除全局 gdaltest.<drivername>_drv 变量并用pytest fixture替换它们。

  • 使用设备进行临时文件处理和清理

  • 更自动化的测试跳过基于实际编译的内容。

  • 自动样式清理使用 Black .

  • 默认情况下,考虑并行测试运行(有几个 plugins available 为了这个)

投票历史

PSC成员以下列表决通过:

  • +1名来自Evner、DanielM、HowardB和KurtS

  • +0来自JukkaR