测试 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 or _Vector.dylib ,取决于平台.这个扩展模块和代理文件 Vector.py 都被放置在 build 目录下的一个子目录中.
实际的测试发生在名为的 Python 脚本中:
testVector.py
它使用标准的 Python 库模块 unittest ,该模块对 Vector.h 中定义的每个函数针对支持的每种数据类型执行多个测试.
对二维数组的测试方式完全相同.上述描述适用,但将 Vector 替换为 Matrix .对于三维测试,将 Vector 替换为 Tensor .对于四维测试,将 Vector 替换为 SuperTensor .对于扁平原位数组测试,将 Vector 替换为 Flat .在接下来的描述中,我们将参考 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 ,该宏具有相同的参数,并且其工作方式与 Vector.h 中的 TEST_FUNC_PROTOS 相同.如上所述,为 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 中,然后执行.错误和失败被加在一起并作为退出参数返回.任何非零结果都表明至少有一个测试未通过.