测试 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.py 和 Vector_wrap.cxx ,并且还执行 setup.py 脚本来编译 Vector_wrap.cxx 并将扩展模块 _Vector.so 或 _Vector.dylib 链接在一起,具体取决于平台.此扩展模块和代理文件 Vector.py 都放置在 build 目录下的子目录中.
实际的测试是在名为::的 Python 脚本中进行的
testVector.py
它使用标准 Python 库模块 unittest ,该模块对 Vector.h 中定义的每个函数针对支持的每种数据类型执行多个测试.
二维数组的测试方式完全相同.上面的描述适用,但是用 Matrix 代替 Vector .对于三维测试,用 Tensor 代替 Vector .对于四维测试,用 SuperTensor 代替 Vector .对于平面就地数组测试,用 Flat 代替 Vector .对于以下描述,我们将引用 Vector 测试,但相同的信息适用于 Matrix , Tensor 和 SuperTensor 测试.
命令 make test 将确保构建所有测试软件,然后运行所有三个测试脚本.
测试头文件#
Vector.h 是一个 C++ 头文件,它定义了一个名为 TEST_FUNC_PROTOS 的 C 宏,该宏接受两个参数: TYPE ,这是一个数据类型名称,例如 unsigned int ;以及 SNAME ,它是相同数据类型的短名称,没有空格,例如 uint .此宏定义了几个函数原型,这些函数原型具有前缀 SNAME ,并且至少有一个参数是 TYPE 类型的数组.具有返回参数的那些函数返回一个 TYPE 值.
然后为 numpy.i 支持的所有数据类型实现 TEST_FUNC_PROTOS :
signed charunsigned charshortunsigned shortintunsigned intlongunsigned longlong longunsigned long longfloatdouble
测试源文件#
Vector.cxx 是一个 C++ 源文件,它为 Vector.h 中指定的每个函数原型实现可编译的代码.它定义了一个 C 宏 TEST_FUNCS ,该宏具有与 TEST_FUNC_PROTOS 相同的参数,并且以相同的方式在 Vector.h 中工作.对于上述 12 种数据类型中的每一种,都实现了 TEST_FUNCS .
测试 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.h和Vector.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 中,然后执行.错误和失败加在一起并作为退出参数返回.任何非零结果都表明至少有一个测试未通过.