測試 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
目錄下的子目錄中。
實際的測試是使用名為
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
值。
然後針對 numpy.i
支援的所有資料型別實作 TEST_FUNC_PROTOS
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
相同的引數和工作方式。針對上述 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"
這些類別中的每一個都被收集到 unittest.TestSuite
中,然後執行。錯誤和失敗會加總在一起,並作為退出引數回傳。任何非零結果都表示至少有一個測試未通過。