测试 numpy.i 类型映射#
介绍#
为 numpy.i 的 SWIG 接口文件编写测试是一个组合的噩梦.目前,支持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.py 和 Vector_wrap.cxx,还执行编译 Vector_wrap.cxx 并链接扩展模块 _Vector.so 或 _Vector.dylib 的 setup.py 脚本,具体取决于平台.这个扩展模块和代理文件 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 值.
TEST_FUNC_PROTOS 然后为 numpy.i 支持的所有数据类型实现:
signed charunsigned charshortunsigned shortintunsigned intlongunsigned longlong longunsigned long longfloatdouble
测试源文件#
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.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 中,然后被执行.错误和失败被汇总在一起,并作为退出参数返回.任何非零结果表明至少有一个测试未通过.