测试 numpy.i 类型映射#

介绍#

numpy.iSWIG 接口文件编写测试是一个组合的噩梦.目前,支持12种不同的数据类型,每种类型有74种不同的参数签名,总共支持888种typemap.每个typemap可能需要几个单元测试来验证正确和错误输入的预期行为.目前,这导致在 numpy/tools/swig 子目录中运行 make test 时执行超过1,000个单独的单元测试.

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

测试组织#

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

Vector.h
Vector.cxx

包含各种函数的原型和代码,这些函数以一维数组作为函数参数.文件:

Vector.i

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

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

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

testVector.py

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

二维数组以完全相同的方式进行测试.上述描述适用,但用 Matrix 代替 Vector.对于三维测试,用 Tensor 代替 Vector.对于四维测试,用 SuperTensor 代替 Vector.对于就地扁平数组测试,用 Flat 代替 Vector.对于以下描述,我们将引用 Vector 测试,但相同的信息适用于 MatrixTensorSuperTensor 测试.

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

测试头文件#

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

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

  • 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" 以使用 numpy.i 中的类型映射包装 Vector.h 中的所有函数原型.

测试 Python 脚本#

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

class VectorTestCase(unittest.TestCase):

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

self.typeStr

一个匹配 Vector.hVector.cxx 中使用的 SNAME 前缀的字符串.例如,``”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 中,然后被执行.错误和失败被汇总在一起,并作为退出参数返回.任何非零结果表明至少有一个测试未通过.