Glib 對 C 函數進行單元測試
1. Glib 單元測試框架
Glib 為單元測試提供了一套完整的測試框架,每個測試運行包括以下幾個部分
- 測試數據結構
- 測試 setup 與 teardown 函數
- 測試函數
2. 單元測試數據結構
在一組測試中使用的元素稱為一個 fixture,Glib 要求每個 fixture 都是一個結構,所以我們需要聲明一個結構體,包含我們測試數據或是對象。
/** fixture for Glib test */ typedef struct Matrix2dTest { Matrix2d mat; ///< test object double TOL; ///< maximum error } Matrix2dTest;
如上代碼中定義了我們測試所需結構體 Matrix2dTest
,結構體中元素 mat
即為後面進行測試的對象(結構體)。
3. 測試環境構建
在每個測試函數運行前,可能都需要進行一系列初始化和後處理操作。Glib 運行時將這兩個過程函數作為參數傳給測試函數,如此一來,每個測試函數都可以生成一個新的程序來運行,並且互不影響。
添加測試函數的語句是
g_test_add("/MatUtils/Test_inverseMatrix2d", // test label
Matrix2dTest, // test structure
NULL, // input user data
Setup_Matrix2dTest, // setup function
Test_inverseMatrix2d, // test function
Teardown_Matrix2dTest); // teardown function
其中,Matrix2dTest
和 NULL
為測試函數輸入參數,Test_inverseMatrix2d
為測試函數,Setup_Matrix2dTest
和 Teardown_Matrix2dTest
為測試初始化和後處理函數。
在調用測試函數時候,Glib 通過 fork 系統調用生成新的線程來測試失敗。運行 g_test_run()
語句即可運行所有測試,具體執行命令可參見第5節。
4. 單元測試函數
由於測試框架是確定的,因此單元測試函數參數也是固定的。在上面測試中,我們添加測試函數名為 Test_inverseMatrix2d
,函數完整定義為
static void
Test_inverseMatrix2d(Matrix2dTest* mattest, const void* test_data)
{
Matrix2d mat1;
mallocMatrix2d(3, 3, &mat1);
copyMatrix2d(mattest->mat, &mat1);
inverseMatrix2d(mat1);
Matrix2d mat2;
mallocMatrix2d(3, 3, &mat2);
multiplyMatrix2d(mattest->mat, mat1, 1.0, 0.0, mat2);
g_assert(fabs(mat2.ets[0][0] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[1][1] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[2][2] - 1.0) < mattest->TOL);
freeMatrix2d(mat1);
freeMatrix2d(mat2);
}
可以看到,由於函數只有文件內測試用,因此聲明為 static 函數。函數有兩個參數,第一個為 mattest 類型指針,第二個為傳入用戶數據,由於我們不需要其他數據,因此調用時傳入 NULL
。
這個測試主要用來測試矩陣求逆過程,計算完成後將逆矩陣與矩陣相乘,看結果是否為單元矩陣。最後的判斷采用如下語句
g_assert(fabs(mat2.ets[0][0] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[1][1] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[2][2] - 1.0) < mattest->TOL);
5. 完整測試函數
#include <stdio.h>
#include <math.h>
#include "MatUtils.h"
#include <glib.h>
/** fixture for Glib test */
typedef struct Matrix2dTest
{
Matrix2d mat; ///< test object
double TOL; ///< maximum error
} Matrix2dTest;
static void
Setup_Matrix2dTest(Matrix2dTest* mattest, const void* test_data)
{
mallocMatrix2d(3, 3, &(mattest->mat));
double vt[] = {
3.0, -1.0, -1.0, 4.0, -2.0, -1.0, -3.0, 2.0, 1.0,
};
mattest->TOL = 1e-6;
setMatrix2dFromArray(vt, mattest->mat);
}
static void
Teardown_Matrix2dTest(Matrix2dTest* mattest, const void* test_data)
{
freeMatrix2d(mattest->mat);
}
static void
Test_inverseMatrix2d(Matrix2dTest* mattest, const void* test_data)
{
Matrix2d mat1;
mallocMatrix2d(3, 3, &mat1);
copyMatrix2d(mattest->mat, &mat1);
inverseMatrix2d(mat1);
Matrix2d mat2;
mallocMatrix2d(3, 3, &mat2);
multiplyMatrix2d(mattest->mat, mat1, 1.0, 0.0, mat2);
g_assert(fabs(mat2.ets[0][0] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[1][1] - 1.0) < mattest->TOL);
g_assert(fabs(mat2.ets[2][2] - 1.0) < mattest->TOL);
freeMatrix2d(mat1);
freeMatrix2d(mat2);
}
int
main(int argc, char** argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add("/MatUtils/Test_inverseMatrix2d", // test label
Matrix2dTest, // test structure
NULL, // input user data
Setup_Matrix2dTest, // setup function
Test_inverseMatrix2d, // test function
Teardown_Matrix2dTest); // teardown function
return g_test_run();
}
編譯時由於需要鏈接 Glib 函數庫所以需要添加一些附加命令。這裏我采用的是 cmake 軟件編譯,在工程根目錄的 CMakeLists.txt
中直接添加如下命令添加 Glib 頭文件和庫文件目錄,
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0>=2.23)
include_directories(${GLIB_INCLUDE_DIRS})
link_directories(${GLIB_LIBRARY_DIRS})
在測試函數所在的文件夾內 CMakeLists.txt
中,則添加如下命令編譯測試函數
target_link_libraries(MatUtilsTest matutils ${GLIB_LIBRARIES})
如此便可對測試函數進行正確的編譯和鏈接了。
獲得可執行的測試函數後,用過命令
gtester -k -o=test.xml ./MatUtilsTest
生成測試結果文件 test.xml
,再利用語句 gtester-report 命令即可將 XML 文件轉化為 html 文件,並用留瀏覽器打開。
gtester-report test.xml > test.html
但是不知什麽原因,在我的 Macbook 筆記本上出現錯誤
? build gtester-report test.xml > test.html
Traceback (most recent call last):
File "/usr/local/bin/gtester-report", line 490, in <module>
main()
File "/usr/local/bin/gtester-report", line 484, in main
HTMLReportWriter(rr.get_info(), rr.binary_list()).printout()
File "/usr/local/bin/gtester-report", line 348, in printout
self.handle_info ()
File "/usr/local/bin/gtester-report", line 242, in handle_info
self.oprint (‘<h3>Package: %(package)s, version: %(version)s</h3>\n‘ % self.info)
KeyError: ‘package‘
通過搜索我發現是 xml 文件缺少內容所致 Running gtester-report on gtester output raises KeyError。打開 xml 文件,在 <gtester>
字段內添加
<info>
<package>PACKAGENAME</package>
<version>VERSION</version>
<revision>REVISION</revision>
語句,聲明軟件名和軟件版本。如此便可得到可視化的測試結果,如圖所示:
參考文獻
理解 GLib 的單元測試框架
https://segmentfault.com/a/1190000003996312
Ben Klemens. C程序設計新思維[M]. 人民郵電出版社, 2015.
Glib 對 C 函數進行單元測試