测试 numpy.i 类型映射#

简介#

numpy.i SWIG 接口文件编写测试是一项组合性的难题。目前,支持 12 种不同的数据类型,每种类型有 74 种不同的参数签名,总共有 888 种“开箱即用”的类型映射。反过来,每种类型映射可能都需要多个单元测试,以验证正确和不正确输入的预期行为。目前,当在 numpy/tools/swig 子目录中运行 make test 时,会执行超过 1,000 个单独的单元测试。

为了方便进行如此多的相似单元测试,采用了多种高级编程技术,包括 C 和 SWIG 宏,以及 Python 继承。本文档的目的是描述用于验证 numpy.i 类型映射是否按预期工作的测试基础设施。

测试组织#

支持三种独立的测试框架,分别用于一维、二维和三维数组。对于一维数组,有两个 C++ 文件,一个头文件和一个源文件,名为

Vector.h
Vector.cxx

其中包含各种将一维数组作为函数参数的函数的原型和代码。文件

Vector.i

是一个 SWIG 接口文件,它定义了一个 Python 模块 Vector,该模块封装了 Vector.h 中的函数,同时利用 numpy.i 中的类型映射来正确处理 C 数组。

Makefile 调用 swig 生成 Vector.pyVector_wrap.cxx,并执行 setup.py 脚本,该脚本编译 Vector_wrap.cxx 并链接生成扩展模块 _Vector.so_Vector.dylib,具体取决于平台。此扩展模块和代理文件 Vector.py 都放置在 build 目录下的一个子目录中。

实际测试通过一个名为的 Python 脚本进行

testVector.py

该脚本使用标准 Python 库模块 unittest,对 Vector.h 中定义的每个函数针对每种支持的数据类型执行多项测试。

二维数组以完全相同的方式进行测试。上述描述同样适用,只需将 Matrix 替换为 Vector。对于三维测试,将 Vector 替换为 Tensor。对于四维测试,将 Vector 替换为 SuperTensor。对于平面就地数组测试,将 Vector 替换为 Flat。在接下来的描述中,我们将参考 Vector 测试,但同样的信息也适用于 MatrixTensorSuperTensor 测试。

命令 make test 将确保所有测试软件都已构建,然后运行所有三个测试脚本。

测试头文件#

Vector.h 是一个 C++ 头文件,它定义了一个名为 TEST_FUNC_PROTOS 的 C 宏,该宏接受两个参数:TYPE,它是一个数据类型名称,例如 unsigned int;以及 SNAME,它是同一数据类型的短名称,没有空格,例如 uint。此宏定义了多个函数原型,这些原型以 SNAME 为前缀,并且至少有一个参数是 TYPE 类型的数组。那些具有返回参数的函数返回一个 TYPE 值。

然后为 numpy.i 支持的所有数据类型实现了 TEST_FUNC_PROTOS

  • signed char

  • unsigned char

  • short

  • unsigned short

  • int

  • unsigned int

  • long

  • unsigned long

  • long long

  • unsigned long long

  • float

  • double

测试源文件#

Vector.cxx 是一个 C++ 源文件,它实现了 Vector.h 中指定的每个函数原型的可编译代码。它定义了一个 C 宏 TEST_FUNCS,该宏具有与 Vector.h 中的 TEST_FUNC_PROTOS 相同的参数,并以相同的方式工作。如上所述,TEST_FUNCS 已为所有 12 种数据类型实现。

测试 SWIG 接口文件#

Vector.i 是一个 SWIG 接口文件,它定义了 Python 模块 Vector。它遵循本章中描述的 numpy.i 使用约定。它定义了一个 SWIG%apply_numpy_typemaps,该宏只有一个参数 TYPE。它使用 SWIG 指令 %apply 将提供的类型映射应用于 Vector.h 中找到的参数签名。然后为 numpy.i 支持的所有数据类型实现此宏。然后它通过 %include "Vector.h"Vector.h 中的所有函数原型使用 numpy.i 中的类型映射进行封装。

测试 Python 脚本#

在使用 make 构建测试扩展模块后,可以运行 testVector.py 来执行测试。与其他使用 unittest 进行单元测试的脚本一样,testVector.py 定义了一个继承自 unittest.TestCase 的类

class VectorTestCase(unittest.TestCase):

然而,这个类不能直接运行。相反,它作为其他几个 Python 类的基类,每个类都特定于一种数据类型。VectorTestCase 类存储了两个用于类型信息的字符串

self.typeStr

一个字符串,与 SNAMEVector.hVector.cxx 中使用的一个前缀匹配。例如,"double"

self.typeCode

一个短(通常是单字符)字符串,表示 numpy 中的数据类型,并与 self.typeStr 对应。例如,如果 self.typeStr"double",则 self.typeCode 应为 "d"

VectorTestCase 类定义的每个测试都通过访问 Vector 模块的字典来提取它试图测试的 Python 函数

length = Vector.__dict__[self.typeStr + "Length"]

在双精度测试的情况下,这将返回 Python 函数 Vector.doubleLength

然后我们为每种支持的数据类型定义一个新的测试用例类,其简短定义如下:

class doubleTestCase(VectorTestCase):
    def __init__(self, methodName="runTest"):
        VectorTestCase.__init__(self, methodName)
        self.typeStr  = "double"
        self.typeCode = "d"

这 12 个类中的每一个都被收集到一个 unittest.TestSuite 中,然后执行。错误和失败总计在一起,并作为退出参数返回。任何非零结果都表示至少有一个测试未通过。