測試 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.pyVector_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 測試,但相同資訊適用於 MatrixTensorSuperTensor 測試。

命令 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.hVector.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 中,然後執行。錯誤和失敗會加總在一起,並作為退出引數回傳。任何非零結果都表示至少有一個測試未通過。