如何用cmocka对C语言数据库函数做单元测试及包装SQL API?
使用CMocka为C语言数据库函数编写单元测试
CMocka完全可以用来测试你的数据库函数,它的函数打桩(wrap)机制能轻松隔离外部SQL API依赖,下面是核心场景的测试示例及dbclose函数的wrap配置方案。
一、核心场景测试示例
1. 数据库连接测试
假设你的连接函数dbconnect依赖SQLAllocConnect、SQLDriverConnect等API,测试用例如下:
#include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h> #include "Base.h" // Mock SQL API函数 int __wrap_SQLAllocConnect(SQLHENV henv, SQLHDBC *phdbc) { *phdbc = (SQLHDBC)0xDEADBEEF; // 返回模拟连接句柄 return SQL_SUCCESS; } int __wrap_SQLDriverConnect(SQLHDBC hdbc, SQLHWND hwnd, SQLCHAR *szConnStrIn, SQLSMALLINT cbConnStrIn, SQLCHAR *szConnStrOut, SQLSMALLINT cbConnStrOutMax, SQLSMALLINT *pcbConnStrOut, SQLUSMALLINT fDriverCompletion) { check_expected(hdbc); // 验证传入句柄是否符合预期 return SQL_SUCCESS; } void test_dbconnect_success(void **state) { (void)state; // 设置期望:调用SQLDriverConnect时传入指定句柄 expect_value(__wrap_SQLDriverConnect, hdbc, (SQLHDBC)0xDEADBEEF); int result = dbconnect(0); // 调用你的连接函数 assert_int_equal(result, 0); // 假设成功返回0 } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_dbconnect_success), }; return cmocka_run_group_tests(tests, NULL, NULL); }
2. 数据库读写/更新测试
假设查询函数dbquery依赖SQLExecDirect、SQLFetch等API,测试用例如下:
#include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h> #include "Base.h" // Mock SQLExecDirect int __wrap_SQLExecDirect(SQLHSTMT hstmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr) { check_expected(szSqlStr); // 验证执行的SQL语句 return SQL_SUCCESS; } // Mock SQLFetch:第一次返回数据,第二次返回无数据 int __wrap_SQLFetch(SQLHSTMT hstmt) { static int call_count = 0; return (call_count++ == 0) ? SQL_SUCCESS : SQL_NO_DATA; } void test_dbquery_fetch_data(void **state) { (void)state; // 设置期望:执行指定SQL语句 expect_string(__wrap_SQLExecDirect, szSqlStr, "SELECT * FROM users WHERE id=1"); char buffer[256]; int result = dbquery(0, "SELECT * FROM users WHERE id=1", buffer); assert_int_equal(result, 0); // 假设成功返回0 assert_string_equal(buffer, "mock_user_data"); // 验证返回的模拟数据 }
二、dbclose函数的Wrap配置及SQL API单独包装
1. CMakeLists.txt配置
要包装SQLDisconnect和SQLFreeConnect,需在CMake中指定这两个函数为wrap目标,同时链接CMocka库:
cmake_minimum_required(VERSION 3.10) project(db_unit_tests) # 引入CMocka库 find_package(CMocka REQUIRED) # 编译测试程序:包含你的数据库实现文件和测试文件 add_executable(db_tests Tests.c Base.c) # 链接CMocka依赖 target_link_libraries(db_tests PRIVATE ${CMOCKA_LIBRARIES}) # 指定需要Wrap的SQL API函数 set_target_properties(db_tests PROPERTIES WRAP_FUNCTIONS "SQLDisconnect;SQLFreeConnect" ) # 包含头文件目录 target_include_directories(db_tests PRIVATE ${CMOCKA_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR})
2. Tests.c中使用Mock函数
在测试文件中定义__wrap_SQLDisconnect和__wrap_SQLFreeConnect替代原API,并设置调用期望:
#include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h> #include "Base.h" // 模拟Base.c中的静态hDbc数组 SQLHDBC hDbc[10] = {NULL}; // Mock SQLDisconnect int __wrap_SQLDisconnect(SQLHDBC hdbc) { check_expected(hdbc); // 验证传入句柄 return SQL_SUCCESS; } // Mock SQLFreeConnect int __wrap_SQLFreeConnect(SQLHDBC hdbc) { check_expected(hdbc); // 验证传入句柄 return SQL_SUCCESS; } void test_dbclose_valid_handle(void **state) { (void)state; // 设置模拟连接句柄 hDbc[0] = (SQLHDBC)0x12345678; // 设置调用期望 expect_value(__wrap_SQLDisconnect, hdbc, (SQLHDBC)0x12345678); expect_value(__wrap_SQLFreeConnect, hdbc, (SQLHDBC)0x12345678); dbclose(0); // 调用dbclose // 验证句柄被置空 assert_null(hDbc[0]); } void test_dbclose_null_handle(void **state) { (void)state; hDbc[0] = NULL; dbclose(0); // 调用dbclose,此时不会触发SQL API调用 assert_null(hDbc[0]); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_dbclose_valid_handle), cmocka_unit_test(test_dbclose_null_handle), }; return cmocka_run_group_tests(tests, NULL, NULL); }
3. 单独包装SQL API函数的可行性
完全可以单独包装SQLDisconnect和SQLFreeConnect。CMocka通过动态符号替换实现wrap,只要在CMake的WRAP_FUNCTIONS中列出目标函数名,测试时就会用你定义的__wrap_xxx函数替代原API,无需修改原业务代码。
注意:如果原SQL API是通过静态库链接的,需确保编译时添加-fPIC参数,避免静态符号重定位问题。
内容的提问来源于stack exchange,提问作者jst
相关产品推荐
相关产品推荐

