TDD实例
TDD實例
github地址
項目中對于 TDD 的實戰(zhàn),依賴的是 GoogleTest 框架
我負責(zé)編碼單元對中控提供
- 設(shè)置編碼單元
- 設(shè)置視頻源
- 設(shè)置視頻輸出
- 狀態(tài)檢測
- 開啟通道
- 關(guān)閉通道
這 6 個接口,中控通過 http 調(diào)用編碼單元接口,為了解耦和方便進行 TDD 測試,我們將這幾個方法寫成一個抽象類,編碼單元再實現(xiàn)具體的方法:
class IEventHandler {public:virtual bool StartChannel(int channel_id) = 0;virtual bool StopChannel(int channel_id) = 0;virtual bool ConfigChannel(int channel_id,const tucodec::SChannelSourceConfig &source_config) = 0;virtual bool ConfigChannel(int channel_id,const tucodec::SChannelDestConfig &dest_config) = 0;virtual bool ConfigChannel(int channel_id,const tucodec::SChannelEncoderConfig &encoder_config) = 0;virtual void DetectWorker(int channel_id, int duration,tucodec::SWorkerStatus &worker_status) = 0;};這里 ConfigChannel 方法處理不同的參數(shù)結(jié)構(gòu)體。
在測試用例中首先繼承 testing::Test 和 public IEventHandler
class HttpControllerTest : public testing::Test, public IEventHandler {public:bool StartChannel(int channel_id) override {channel_id_ = channel_id; return true;}bool StopChannel(int channel_id) override {channel_id_ = channel_id; return true;}bool ConfigChannel(int channel_id,const tucodec::SChannelSourceConfig &source_config) override {channel_id_ = channel_id;// 其他參數(shù)省略source_config_.username = source_config.username;return true;}bool ConfigChannel(int channel_id,const tucodec::SChannelDestConfig &dest_config) override {channel_id_ = channel_id;// 其他參數(shù)省略dest_config_.port = dest_config.port;return true;}bool ConfigChannel(int channel_id,const tucodec::SChannelEncoderConfig &encoder_config) override {channel_id_ = channel_id;// 其他參數(shù)省略encoder_config_.output_h = encoder_config.output_h; return true;}void DetectWorker(int channel_id, int duration,tucodec::SWorkerStatus &worker_status) override {channel_id_ = channel_id;}; protected:void SetUp() override { tucodec::TuLog::Init("", "http_controller_test.log", 50, 2 * 1024 * 1024);tucodec::TuLog::Instance()->SetPriority(tucodec::kTulogDebug); controller_.Init();}void TearDown() override { tucodec::TuLog::Destory();}protected : HttpController controller_{this};tucodec::SChannelSourceConfig source_config_;tucodec::SChannelDestConfig dest_config_;tucodec::SChannelEncoderConfig encoder_config_;int channel_id_{}; };TEST_F(HttpControllerTest, GetStateTest) {unsigned char get_json[] = "{\n"" \"cmd\":\"get_state\",\n"" \"chn_id\":1,\n"" \"duration\":2\n""}";controller_.ResolveData(get_json, sizeof(get_json));ASSERT_EQ(channel_id_, 1); }TEST_F(HttpControllerTest, StartChannelTest) {unsigned char start_json[] = "{\n"" \"cmd\":\"start_chn\",\n"" \"chn_id\":1\n""}";controller_.ResolveData(start_json, sizeof(start_json));ASSERT_EQ(channel_id_, 1); }TEST_F(HttpControllerTest, StopChannelTest) {unsigned char stop_json[] = "{\n"" \"cmd\":\"stop_chn\",\n"" \"chn_id\":2\n""}";controller_.ResolveData(stop_json, sizeof(stop_json));ASSERT_EQ(channel_id_, 2); }TEST_F(HttpControllerTest, DestConfigTest) {unsigned char destination_json[] = "{\n"" \"cmd\":\"set_destination\",\n"" \"chn_id\":1,\n"" \"address\":\"192.168.1.221\",\n"" \"port\":554,\n"" \"destination_type\":\"onvif\",\n"" \"stream_id\":\"live1\"\n""}";controller_.ResolveData(destination_json, sizeof(destination_json));ASSERT_EQ(channel_id_, 1);ASSERT_EQ(dest_config_.address, "192.168.1.221");ASSERT_EQ(dest_config_.port, 554);ASSERT_EQ(dest_config_.destination_type, "onvif");ASSERT_EQ(dest_config_.stream_id, "live1"); }實現(xiàn)具體接口,這里只需要對他進行賦值即可,將本身傳入 HttpController 中,這里我們先不測試網(wǎng)絡(luò),直接調(diào)用接收到 http 請求后解析 json 的函數(shù)。再根據(jù)參數(shù)中命令選擇調(diào)用 設(shè)置編碼源還是具體哪一個接口。因為需要在回調(diào)函數(shù)中解析 json,這里就直接繼承 DataCallBack 類,在接收方法中直接調(diào)用 ResolveData 。
class HttpController : public tucodec::DataCallBack {public:explicit HttpController(IEventHandler* event_handler);virtual ~HttpController();bool Init(const std::string &server_ip = "0.0.0.0", int server_port = 8081);bool ResolveData(unsigned char* data, unsigned int len);void ReceiveDataCallBack(int local_busy_handle,int remote_handle,unsigned char* data,unsigned int data_length,void* user) override {};void ReceiveDataCallBack(int local_busy_handle,const std::string &remote_ip,int remote_port,unsigned char* data,unsigned int data_length,void* user) override {};void ReceiveDataCallBack(int local_busy_handle,const std::string& remote_ip,int remote_port,const std::string& path,unsigned char* data,unsigned int data_length,void* user) override {tucodec::TuLog::Instance()->Info("receive data: %s length: %d ", data, data_length); //NOLINTtucodec::Server* server = reinterpret_cast<tucodec::Server*>(user);if (ResolveData(data, data_length)) {unsigned char response[] = "{\"code\":0}";int len = sizeof(response);server->SendResponse2Client(0, response, len);} else {unsigned char response[] = "{\"code\":-1}";int len = sizeof(response);server->SendResponse2Client(0, response, len);}};void StatusCallBack(int local_busy_handle,int remote_handle,std::string remote_ip,int remote_port,int remote_status,void* user) override {};private:IEventHandler* event_handler_;tucodec::Server* server_; }; const char *k_set_codec_source = "set_codec_source"; const char *k_set_destination = "set_destination"; const char *k_start_chn = "start_chn"; const char *k_stop_chn = "stop_chn"; const char *k_get_state = "get_state"; const char *k_service_state = "service_state";HttpController::HttpController(IEventHandler *event_handler) {event_handler_ = event_handler;server_ = nullptr; }HttpController::~HttpController() {if (server_ != nullptr) {delete server_;server_ = nullptr;} }bool HttpController::Init(const std::string &server_ip, int server_port) {return true; }bool HttpController::ResolveData(unsigned char *data, unsigned int len) {tucodec::TuJson json;std::stringstream x_read_write;x_read_write.write((const char *) data, len);json.Load(x_read_write);std::string cmd = json.GetString("cmd");int channel_id = json.GetInt("chn_id");if (cmd == k_set_codec_source) {SourceCodecHandler source_codec_handler;return source_codec_handler.HandleJson(json, channel_id, event_handler_);}if (cmd == k_set_destination) {DestinationHandler destination_handler;return destination_handler.HandleJson(json, channel_id, event_handler_);}if (cmd == k_get_state) {GetStatusHandler get_status_handler;return get_status_handler.HandleJson(json, channel_id, event_handler_);}if (cmd == k_start_chn) {return event_handler_->StartChannel(channel_id);}if (cmd == k_stop_chn) {return event_handler_->StopChannel(channel_id);}return false; }這邊還添加了一個類專門用來處理不同 json,方便以后擴展
class IHttpHandler {public:virtual bool HandleJson(tucodec::TuJson &json, int channel_id, //NOLINTIEventHandler* event_handler_) = 0; //NOLINT };class DestinationHandler : public IHttpHandler {public:bool HandleJson(tucodec::TuJson &json, int channel_id,IEventHandler* event_handler_) override; };class SourceCodecHandler : public IHttpHandler {public:bool HandleJson(tucodec::TuJson &json, int channel_id,IEventHandler* event_handler_) override; };class GetStatusHandler : public IHttpHandler {public:bool HandleJson(tucodec::TuJson &json, int channel_id,IEventHandler* event_handler_) override; }; bool DestinationHandler::HandleJson(tucodec::TuJson &json,int channel_id, IEventHandler *event_handler_) { //NOLINTif (event_handler_ == nullptr) {return false;}tucodec::ChannelDestConfig channel_dest_config;channel_dest_config.port = json.GetInt("port");channel_dest_config.address = json.GetString("address");channel_dest_config.stream_id = json.GetString("stream_id");channel_dest_config.destination_type = json.GetString("destination_type");return event_handler_->ConfigChannel(channel_id, channel_dest_config); } bool SourceCodecHandler::HandleJson(tucodec::TuJson &json,int channel_id, IEventHandler *event_handler_) { //NOLINTif (event_handler_ == nullptr) {return false;}// codectucodec::ChannelEncoderConfig channel_encoder_config;// 省略部分參數(shù)channel_encoder_config.output_w = json.GetInt("output_w");if (!event_handler_->ConfigChannel(channel_id, channel_encoder_config)) {tucodec::TuLog::Instance()->Error("EncoderConfig Error!");return false;}// sourcetucodec::ChannelSourceConfig channel_source_config;channel_source_config.source_type = json.GetString("source_type");channel_source_config.address = json.GetString("address");channel_source_config.port = json.GetInt("port");channel_source_config.username = json.GetString("username");channel_source_config.password = json.GetString("password");return event_handler_->ConfigChannel(channel_id, channel_source_config);; }bool GetStatusHandler::HandleJson(tucodec::TuJson &json, int channel_id, IEventHandler *event_handler_) {if (event_handler_ == nullptr) {return false;}tucodec::SWorkerStatus status;int duration = json.GetInt("duration");event_handler_->DetectWorker(channel_id, duration, status);return status.encoder_working == 1 && status.source_working == 1; }解析接口測試完畢后,接下來加入網(wǎng)絡(luò)模塊。
在 HttpController 類中先添加成員變量 tucodec::Server *server_;,修改構(gòu)造和析構(gòu)
Init 方法中添加:
if (server_ == nullptr) {server_ = tucodec::NetWorkFactory::CreateHttpServer(server_ip,server_port);server_->SetDataCallBack(this,reinterpret_cast<void *>(server_));if (!server_->StartServer()) {tucodec::TuLog::Instance()->Error("start http server failed!");return false;} } tucodec::TuLog::Instance()->Info("start http server success!");測試類中加入客戶端初始化以及阻塞函數(shù):
void Wait(int time) {while (watcher_ == 0 && time > 0) {std::this_thread::sleep_for(std::chrono::seconds(1));--time;} }protected: void SetUp() override {watcher_ = 0;tucodec::TuLog::Init("", "http_controller_test.log", 50, 2 * 1024 * 1024);tucodec::TuLog::Instance()->SetPriority(tucodec::kTulogDebug);controller_.Init();client_ = tucodec::NetWorkFactory::CreateHttpClient("apt/v1/test",tucodec::kRequestPost);client_->ConnectServer("127.0.0.1", 8081);client_->SetDataCallBack(nullptr, nullptr); }void TearDown() override {delete client_;client_ = nullptr;tucodec::TuLog::Destory(); } protected : tucodec::Client *client_ = nullptr; HttpController controller_{this}; int watcher_{};添加測試用例:
TEST_F(HttpControllerTest, SourceCodeConfigTest) {unsigned char source_codec_json[] = "{\n"" \"cmd\":\"set_codec_source\",\n"" \"chn_id\":1,\n"" \"source_type\":\"onvif\"\n""}";client_->SendRequest2Server(source_codec_json, sizeof(source_codec_json));Wait(2);// 省略參數(shù)ASSERT_EQ(channel_id_, 1);ASSERT_EQ(source_config_.source_type, "onvif"); }盡量解耦和,讓業(yè)務(wù)依賴于接口,而非接口依賴于業(yè)務(wù),每一個小模塊都可以單獨測試,在保證程序按預(yù)期穩(wěn)定運行的基礎(chǔ)上(有測試用例的存在),再將我們的代碼進行重構(gòu),小步前進,出現(xiàn)問題也能快速定位,或者 revert 。
我們這里用的是 TestCase 級別的事件機制
gtest 提供了多種事件機制,非常方便我們在案例之前或之后做一些操作。總結(jié)一下 gtest 的事件一共有 3 種:
全局事件
要實現(xiàn)全局事件,必須寫一個類,繼承 testing::Environment 類,實現(xiàn)里面的 SetUp 和 TearDown 方法。
當(dāng)然,這樣還不夠,我們還需要告訴 gtest 添加這個全局事件,我們需要在 main 函數(shù)中通過 testing::AddGlobalTestEnvironment 方法將事件掛進來,也就是說,我們可以寫很多個這樣的類,然后將他們的事件都掛上去。
int main(int argc, char* argv[]) {testing::AddGlobalTestEnvironment(new FooEnvironment);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS(); }TestSuite事件
我們需要寫一個類,繼承 testing::Test ,然后實現(xiàn)兩個靜態(tài)方法
在編寫測試案例時,我們需要使用 TEST_F 這個宏,第一個參數(shù)必須是我們上面類的名字,代表一個 TestSuite 。
TEST_F(FooTest, Test1){// you can refer to shared_resource here } TEST_F(FooTest, Test2){// you can refer to shared_resource here }TestCase事件
TestCase 事件是掛在每個案例執(zhí)行前后的,實現(xiàn)方式和上面的幾乎一樣,不過需要實現(xiàn)的是 SetUp 方法和 TearDown 方法:
在 TEST_F 中使用的變量可以在初始化函數(shù)SetUp中初始化,在 TearDown 中銷毀,并且所有的 TEST_F 是互相獨立的,都是在初始化以后的狀態(tài)開始運行,一個 TEST_F 不會影響另一個 TEST_F 所使用的數(shù)據(jù)
總結(jié)
- 上一篇: 事务流程java怎么实现_使用队列和事务
- 下一篇: java函数ao活动对象_Java程序设