测试 numpy.i 类型映射#
简介#
为 numpy.i
SWIG 接口文件编写测试是一个组合难题。目前,支持 12 种不同的数据类型,每种类型都有 74 种不同的参数签名,总共有 888 个“开箱即用”的类型映射得到支持。反过来,每种类型映射可能需要多个单元测试才能验证预期行为是否适用于正确和不正确的输入。目前,当在 numpy/tools/swig
子目录中运行 make test
时,这将导致执行超过 1000 个单独的单元测试。
为了促进如此多的类似单元测试,采用了一些高级编程技术,包括 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
目录下的子目录中。
实际测试使用名为
testVector.py
的 Python 脚本进行,该脚本使用标准 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 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
,它具有相同的参数,并且与 TEST_FUNC_PROTOS
在 Vector.h
中的工作方式相同。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
中,然后执行该测试套件。错误和失败被累加在一起,并作为退出参数返回。任何非零结果都表示至少有一个测试未通过。