目錄
SQLite
什么是 SQLite
為什么要用 SQLite
SQLite3 C/C++ API 介紹
SQLite3 C/C++ API 使用
GTest
GTest 是什么
GTest 使用
TEST 宏
斷言
事件機制
全局事件
TestSuite 事件
SQLite
什么是 SQLite
????????SQLite 是一個進程內的輕量級數據庫,它實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。它是一個零配置的數據庫,這意味著與其他數據庫不一樣,我們不需要在系統中配置。像其他數據庫,SQLite 引擎不是一個獨立的進程,可以按應用程序需求進行靜態或動態連接,SQLite 直接訪問其存儲文件。
為什么要用 SQLite
- 不需要一個單獨的服務器進程或操作的系統(無服務器的)。
- SQLite 不需要配置。
- 一個完整的 SQLite 數據庫是存儲在一個單一的跨平臺的磁盤文件。
- SQLite 是非常小的,是輕量級的,完全配置時小于 400KiB,省略可選功能配置時小于250KiB。
- SQLite 是自給自足的,這意味著不需要任何外部的依賴。
- SQLite 事務是完全兼容 ACID 的,允許從多個進程或線程安全訪問。
- SQLite 支持 SQL92(SQL2)標準的大多數查詢語言的功能。
- SQLite 使用 ANSI-C 編寫的,并提供了簡單和易于使用的 API。
- SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32,?WinCE, WinRT)中運行。
SQLite3 C/C++ API 介紹
????????C/C++ API 是 SQLite3 數據庫的一個客戶端, 提供一種用 C/C++操作數據庫的方法。下面我們介紹一下常見的幾個接口:
sqlite3 操作流程:
0. 查看當前數據庫在編譯階段是否啟動了線程安全
?????????int sqlite3_threadsafe(); 0-未啟用; 1-啟用
?????????需要注意的是 sqlite3 是有三種安全等級的:
- 非線程安全模式
- 線程安全模式(不同的連接在不同的線程/進程間是安全的,即一個句柄不能用于多線程間)
- ?串行化模式(可以在不同的線程/進程間使用同一個句柄)
1. 創建/打開數據庫文件,并返回操作句柄
?????????int sqlite3_open(const char *filename, sqlite3 **ppDb) 成功返回SQLITE_OK
?//若在編譯階段啟動了線程安全,則在程序運行階段可以通過參數選擇線程安全等級
?????????int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs );
flag:?
- ?SQLITE_OPEN_READWRITE -- 以可讀可寫方式打開數據庫文件
- ?SQLITE_OPEN_CREATE -- 不存在數據庫文件則創建
- ?SQLITE_OPEN_NOMUTEX--多線程模式,只要不同的線程使用不同的連接即可保證線程安全
- ?SQLITE_OPEN_FULLMUTEX--串行化模式
?返回:SQLITE_OK 表示成功
2. 執行語句
?????????int sqlite3_exec(sqlite3*, char *sql, int?(*callback)(void*,int,char**,char**), ?void* arg, char **err)???返回:SQLITE_OK 表示成功
- sqlite3*:數據庫文件句柄。
- char *sql:sql語句。
- int?(*callback)(void*,int,char**,char**):對執行結果的回調函數。
- void* arg:給回調函數的第一個參數,常用于保存處理結果。
- char **err:錯誤信息。
????????int (*callback)(void*,int,char**,char**)
- ?void* : 是設置的在回調時傳入的 arg 參數。
- ?int:一行中數據的列數。
- ?char**:存儲一行數據的字符指針數組。
- ?char**:每一列的字段名稱。
? ? ? ? ?對于每一行結果都會執行這個回調函數,這個回調函數有個 int 返回值,成功處理的情況下必須返回 0,返回非 0會觸發 ABORT 退出程序
3. 銷毀句柄
?????????int sqlite3_close(sqlite3* db); 成功返回 SQLITE_OK
?????????int sqlite3_close_v2(sqlite3*); 推薦使用--無論如何都會返回SQLITE_OK
????????獲取錯誤信息
?????????const char *sqlite3_errmsg(sqlite3* db);
SQLite3 C/C++ API 使用
????????下面我們將這幾個接口封裝成一個類,快速上手這幾個接口
// 封裝一個sqlitehelper類
#pragma once#include <sqlite3.h>
#include <iostream>
#include <string>class SqliteHelper
{
public:typedef int (*sqliteCallback)(void*, int, char**, char**);SqliteHelper(const std::string &dbfile):_dbfile(dbfile), _handler(nullptr) {}bool open(int safe_level = SQLITE_OPEN_FULLMUTEX){int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level, nullptr);if (ret != SQLITE_OK){std::cerr << "打開/創建數據庫失敗: " << sqlite3_errmsg(_handler) << std::endl;return false;}return true;}bool exec(const std::string &sql, sqliteCallback callback, void *arg){int ret = sqlite3_exec(_handler, sql.c_str(), callback, arg, nullptr);if (ret != SQLITE_OK){std::cerr << sql << std::endl;std::cerr << "執行語句失敗: " << sqlite3_errmsg(_handler) << std::endl;return false;}return true;}void close(){if(_handler)sqlite3_close_v2(_handler);}
private:std::string _dbfile;sqlite3 *_handler;
};
測試程序:
#include "sqlite.hpp"
#include <cassert>
#include <vector>int select_stu_callback(void *arg, int col_count, char **result, char **fields_name)
{std::vector<std::string> *names = (std::vector<std::string>*)arg;names->push_back(result[0]);return 0;
}int main()
{SqliteHelper helper("./test.db");// 創建/打開庫文件assert(helper.open());// 創建表const char *ct = "create table if not exists student(sn int primary key, name varchar(32), age int);";assert(helper.exec(ct, nullptr, nullptr));// 新增數據const char *insert_sql = "insert into student values(1, '小明', 18), (2, '小剛', 19), (3, '小紅', 18);";assert(helper.exec(insert_sql, nullptr, nullptr));const char *select_sql = "select name from student;";std::vector<std::string> names;assert(helper.exec(select_sql, select_stu_callback, &names));for (auto &name : names){std::cout << name << std::endl;}// 關閉數據庫helper.close();return 0;
}
GTest
GTest 是什么
????????GTest 是一個跨平臺的 C++單元測試框架,由 google 公司發布。gtest 是為了在不同平臺上為編寫 C++單元測試而生成的。它提供了豐富的斷言、致命和非致命判斷、參數化等等。
GTest 使用
TEST 宏
TEST(test_case_name, test_name)
TEST_F(test_fixture,test_name)
- TEST:主要用來創建一個簡單測試, 它定義了一個測試函數, 在這個函數中可以使用任何 C++代碼并且使用框架提供的斷言進行檢查。
- TEST_F:主要用來進行多樣測試,適用于多個測試場景如果需要相同的數據配置的情況, 即相同的數據測不同的行為。
斷言
GTest 中的斷言的宏可以分為兩類:
- ASSERT_系列:如果當前點檢測失敗則退出當前函數。
- EXPECT_系列:如果當前點檢測失敗則繼續往下執行。
下面是經常使用的斷言介紹:
// bool 值檢查
ASSERT_TRUE(參數),期待結果是 true
ASSERT_FALSE(參數),期待結果是 false
//數值型數據檢查
ASSERT_EQ(參數 1,參數 2),傳入的是需要比較的兩個數 equal
ASSERT_NE(參數 1,參數 2),not equal,不等于才返回 true
ASSERT_LT(參數 1,參數 2),less than,小于才返回 true
ASSERT_GT(參數 1,參數 2),greater than,大于才返回 true
ASSERT_LE(參數 1,參數 2),less equal,小于等于才返回 true
ASSERT_GE(參數 1,參數 2),greater equal,大于等于才返回 true
下面我們做一個測試:
#include <iostream>
#include <gtest/gtest.h>// 斷言宏的使用 ASSERT_ 斷言失敗則退出 EXPECT_斷言失敗繼續運行 必須在單元測試宏函數中使用TEST(test, great_than)
{int age = 20;ASSERT_GT(age, 18);printf("OK\n");
}int main(int argc, char *argv[])
{testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
運行結果:
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from test
[ RUN ] test.great_than
OK
[ OK ] test.great_than (0 ms)
[----------] 1 test from test (0 ms total)[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
事件機制
????????GTest 中的事件機制是指在測試前和測試后提供給用戶自行添加操作的機制,而且該機制也可以讓同一測試套件下的測試用例共享數據。GTest 框架中事件的結構層次:
- ?測試程序:一個測試程序只有一個 main 函數,也可以說是一個可執行程序是一個測試程序。該級別的事件機制是在程序的開始和結束執行。
- 測試套件:代表一個測試用例的集合體,該級別的事件機制是在整體的測試案例開始和結束執行。
- 測試用例:該級別的事件機制是在每個測試用例開始和結束都執行。
????????事件機制的最大好處就是能夠為我們各個測試用例提前準備好測試環境,并在測試完畢后用于銷毀環境,這樣有個好處就是如果我們有一端代碼需要進行多種不同方法的測試,則可以通過測試機制在每個測試用例進行之前初始化測試環境和數據,并在測試完畢后清理測試造成的影響。
?GTest 提供了三種常見的的事件:
全局事件
????????針對整個測試程序。實現全局的事件機制,需要創建一個自己的類,然后繼承 testing::Environment 類,然后分別實現成員函數 SetUp 和 TearDown,同時在 main 函數內進行調用 testing::AddGlobalTestEnvironment(new?MyEnvironment);函數添加全局的事件機制。
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>
#include <string>class MyEnvironment : public testing::Environment
{
public:virtual void SetUp() override{std::cout << "單元測試環境初始化" << std::endl;}virtual void TearDown() override{std::cout << "單元測試環境清理" << std::endl;}
};TEST(MyEnvironment, test1)
{std::cout << "單元測試1" << std::endl;
}TEST(MyEnvironment, test2)
{std::cout << "單元測試2" << std::endl;
}std::unordered_map<std::string, std::string> mymap;
class MyMapTest : public testing::Environment
{
public:virtual void SetUp() override{std::cout << "單元測試環境初始化" << std::endl;mymap.insert(std::make_pair("hello", "你好"));mymap.insert(std::make_pair("bye", "再見"));}virtual void TearDown() override{std::cout << "單元測試環境清理" << std::endl;mymap.clear();}
};TEST(MyMapTest, test1)
{ASSERT_EQ(mymap.size(), 2);mymap.erase("hello");
}TEST(MyMapTest, test2)
{ASSERT_EQ(mymap.size(), 2);
}int main(int argc, char *argv[])
{testing::AddGlobalTestEnvironment(new MyEnvironment);testing::AddGlobalTestEnvironment(new MyMapTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
運行結果:
[==========] Running 4 tests from 2 test suites.
[----------] Global test environment set-up.
單元測試環境初始化
單元測試環境初始化
[----------] 2 tests from MyEnvironment
[ RUN ] MyEnvironment.test1
單元測試1
[ OK ] MyEnvironment.test1 (0 ms)
[ RUN ] MyEnvironment.test2
單元測試2
[ OK ] MyEnvironment.test2 (0 ms)
[----------] 2 tests from MyEnvironment (0 ms total)[----------] 2 tests from MyMapTest
[ RUN ] MyMapTest.test1
[ OK ] MyMapTest.test1 (0 ms)
[ RUN ] MyMapTest.test2
global.cc:58: Failure
Expected equality of these values:mymap.size()Which is: 12
[ FAILED ] MyMapTest.test2 (0 ms)
[----------] 2 tests from MyMapTest (0 ms total)[----------] Global test environment tear-down
單元測試環境清理
單元測試環境清理
[==========] 4 tests from 2 test suites ran. (0 ms total)
[ PASSED ] 3 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] MyMapTest.test21 FAILED TEST
TestSuite 事件
????????針對一個個測試套件。測試套件的事件機制我們同樣需要去創建一個類,繼承自testing::Test,實現兩個靜態函數 SetUpTestCase 和TearDownTestCase,測試套件的事件機制不需要像全局事件機制一樣在 main 注冊,而是需要將我們平時使用的 TEST 宏改為 TEST_F 宏。
- ?SetUpTestCase() 函數是在測試套件第一個測試用例開始前執行。
- TearDownTestCase() 函數是在測試套件最后一個測試用例結束后執行。
- 需要注意 TEST_F 的第一個參數是我們創建的類名,也就是當前測試套件的名稱,這樣在 TEST_F 宏的測試套件中就可以訪問類中的成員了。
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>
#include <string>class MyTest : public testing::Test
{
public:static void SetUpTestCase(){std::cout << "所有單元測試前初始化環境\n";}static void TearDownTestCase(){std::cout << "所有單元測試完畢后清理環境\n";}std::unordered_map<std::string, std::string> _mymap;
};TEST_F(MyTest, insert_test)
{_mymap.insert(std::make_pair("good", "好"));
}TEST_F(MyTest, size_test)
{ASSERT_EQ(_mymap.size(), 1);
}int main(int argc, char *argv[])
{testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
?運行結果:
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from MyTest
所有單元測試前初始化環境
[ RUN ] MyTest.insert_test
[ OK ] MyTest.insert_test (0 ms)
[ RUN ] MyTest.size_test
suit.cc:40: Failure
Expected equality of these values:_mymap.size()Which is: 01
[ FAILED ] MyTest.size_test (0 ms)
所有單元測試完畢后清理環境
[----------] 2 tests from MyTest (0 ms total)[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] MyTest.size_test1 FAILED TEST
????????能夠看到在上例中,有一個好處,就是將數據與測試結合到同一個測試環境類中了,這樣與外界的耦合度更低,代碼也更清晰。
????????但是同樣的,我們發現在兩個測試用例中第二個測試用例失敗了,這是為什么呢?這就涉及到了 TestCase 事件的機制。
????????? TestCase 事件:?針對一個個測試用例。測試用例的事件機制的創建和測試套件的基本一樣,不同地方在于測試用例實現的兩個函數分別是 SetUp 和 TearDown, 這兩個函數也不是靜態函數
????????○ SetUp()函數是在一個測試用例的開始前執行
????????○ TearDown()函數是在一個測試用例的結束后執行
????????也就是說,在 TestSuite/TestCase 事件中,每個測試用例,雖然它們同用同一個事件環境類,可以訪問其中的資源,但是本質上每個測試用例的環境都是獨立的,這樣我們就不用擔心不同的測試用例之間會有數據上的影響了,保證所有的測試用例都使用相同的測試環境進行測試。
?
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>
#include <string>class MyTest : public testing::Test
{
public:static void SetUpTestCase(){std::cout << "所有單元測試前初始化環境\n";}static void TearDownTestCase(){std::cout << "所有單元測試完畢后清理環境\n";}void SetUp() override{_mymap.insert(std::make_pair("hello", "你好"));_mymap.insert(std::make_pair("bye", "再見"));}void TearDown() override{_mymap.clear();}std::unordered_map<std::string, std::string> _mymap;
};TEST_F(MyTest, insert_test)
{_mymap.insert(std::make_pair("good", "好"));
}TEST_F(MyTest, size_test)
{ASSERT_EQ(_mymap.size(), 2);
}int main(int argc, char *argv[])
{testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
運行結果:
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from MyTest
所有單元測試前初始化環境
[ RUN ] MyTest.insert_test
[ OK ] MyTest.insert_test (0 ms)
[ RUN ] MyTest.size_test
[ OK ] MyTest.size_test (0 ms)
所有單元測試完畢后清理環境
[----------] 2 tests from MyTest (0 ms total)[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.