日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TDD实例

發(fā)布時(shí)間:2023/12/19 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TDD实例 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

TDD實(shí)例

github地址

項(xiàng)目中對(duì)于 TDD 的實(shí)戰(zhàn),依賴的是 GoogleTest 框架

我負(fù)責(zé)編碼單元對(duì)中控提供

  • 設(shè)置編碼單元
  • 設(shè)置視頻源
  • 設(shè)置視頻輸出
  • 狀態(tài)檢測(cè)
  • 開啟通道
  • 關(guān)閉通道

這 6 個(gè)接口,中控通過 http 調(diào)用編碼單元接口,為了解耦和方便進(jìn)行 TDD 測(cè)試,我們將這幾個(gè)方法寫成一個(gè)抽象類,編碼單元再實(shí)現(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)體。

在測(cè)試用例中首先繼承 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"); }

實(shí)現(xiàn)具體接口,這里只需要對(duì)他進(jìn)行賦值即可,將本身傳入 HttpController 中,這里我們先不測(cè)試網(wǎng)絡(luò),直接調(diào)用接收到 http 請(qǐng)求后解析 json 的函數(shù)。再根據(jù)參數(shù)中命令選擇調(diào)用 設(shè)置編碼源還是具體哪一個(gè)接口。因?yàn)樾枰诨卣{(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; }

這邊還添加了一個(gè)類專門用來處理不同 json,方便以后擴(kuò)展

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; }

解析接口測(cè)試完畢后,接下來加入網(wǎng)絡(luò)模塊。
在 HttpController 類中先添加成員變量 tucodec::Server *server_;,修改構(gòu)造和析構(gòu)

HttpController::HttpController(IEventHandler *event_handler) {event_handler_ = event_handler;server_ = nullptr; }HttpController::~HttpController() {if (server_ != nullptr) {delete server_;server_ = nullptr;} }

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!");

測(cè)試類中加入客戶端初始化以及阻塞函數(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_{};

添加測(cè)試用例:

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ù),每一個(gè)小模塊都可以單獨(dú)測(cè)試,在保證程序按預(yù)期穩(wěn)定運(yùn)行的基礎(chǔ)上(有測(cè)試用例的存在),再將我們的代碼進(jìn)行重構(gòu),小步前進(jìn),出現(xiàn)問題也能快速定位,或者 revert 。


我們這里用的是 TestCase 級(jí)別的事件機(jī)制

gtest 提供了多種事件機(jī)制,非常方便我們?cè)诎咐盎蛑笞鲆恍┎僮鳌?偨Y(jié)一下 gtest 的事件一共有 3 種:

  • 全局的,所有案例執(zhí)行前后。
  • TestSuite 級(jí)別的,在某一批案例中第一個(gè)案例前,最后一個(gè)案例執(zhí)行后。
  • TestCase 級(jí)別的,每個(gè) TestCase 前后。
  • 全局事件

    要實(shí)現(xiàn)全局事件,必須寫一個(gè)類,繼承 testing::Environment 類,實(shí)現(xiàn)里面的 SetUp 和 TearDown 方法。

  • SetUp() 方法在所有案例執(zhí)行前執(zhí)行
  • TearDown() 方法在所有案例執(zhí)行后執(zhí)行
  • class FooEnvironment : public testing::Environment { public:virtual void SetUp(){std::cout << "Foo FooEnvironment SetUP" << std::endl;}virtual void TearDown(){std::cout << "Foo FooEnvironment TearDown" << std::endl;} };

    當(dāng)然,這樣還不夠,我們還需要告訴 gtest 添加這個(gè)全局事件,我們需要在 main 函數(shù)中通過 testing::AddGlobalTestEnvironment 方法將事件掛進(jìn)來,也就是說,我們可以寫很多個(gè)這樣的類,然后將他們的事件都掛上去。

    int main(int argc, char* argv[]) {testing::AddGlobalTestEnvironment(new FooEnvironment);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS(); }

    TestSuite事件

    我們需要寫一個(gè)類,繼承 testing::Test ,然后實(shí)現(xiàn)兩個(gè)靜態(tài)方法

  • SetUpTestCase() 方法在第一個(gè) TestCase 之前執(zhí)行
  • TearDownTestCase() 方法在最后一個(gè) TestCase 之后執(zhí)行
  • class FooTest : public testing::Test {protected:static void SetUpTestCase() {shared_resource_ = new ;}static void TearDownTestCase() {delete shared_resource_;shared_resource_ = NULL;}// Some expensive resource shared by all tests.static T* shared_resource_; };

    在編寫測(cè)試案例時(shí),我們需要使用 TEST_F 這個(gè)宏,第一個(gè)參數(shù)必須是我們上面類的名字,代表一個(gè) 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 事件是掛在每個(gè)案例執(zhí)行前后的,實(shí)現(xiàn)方式和上面的幾乎一樣,不過需要實(shí)現(xiàn)的是 SetUp 方法和 TearDown 方法:

  • SetUp() 方法在每個(gè) TestCase 之前執(zhí)行
  • TearDown() 方法在每個(gè) TestCase 之后執(zhí)行
  • class FooCalcTest:public testing::Test { protected:virtual void SetUp(){m_foo.Init();}virtual void TearDown(){m_foo.Finalize();}FooCalc m_foo; };TEST_F(FooCalcTest, HandleNoneZeroInput) {EXPECT_EQ(4, m_foo.Calc(12, 16)); }TEST_F(FooCalcTest, HandleNoneZeroInput_Error) {EXPECT_EQ(5, m_foo.Calc(12, 16)); }

    在 TEST_F 中使用的變量可以在初始化函數(shù)SetUp中初始化,在 TearDown 中銷毀,并且所有的 TEST_F 是互相獨(dú)立的,都是在初始化以后的狀態(tài)開始運(yùn)行,一個(gè) TEST_F 不會(huì)影響另一個(gè) TEST_F 所使用的數(shù)據(jù)

    總結(jié)

    以上是生活随笔為你收集整理的TDD实例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。