首页 前端知识 Protobuf 和Json的互转功能在项目中的应用

Protobuf 和Json的互转功能在项目中的应用

2024-06-06 10:06:04 前端知识 前端哥 892 768 我要收藏

比较大型的项目中,通常会依照总体软件架构,将交付件切分到不同的交付链中,处在中间层的交付件,往往有着自己的上游和下游。

那么,如何确保自己可以放心的向下游交付呢?

UT的覆盖,基本聚焦点是组件对外提供的接口,级别较低,只能保障接口实现符合设计上的预期。

作为项目组一员,要保障功能上衔接上游后达到预期,就不是UT能够完全cover的了。

然后一个限制是,你不可能拿到下游的代码工程来测试自己的组件。

另外,本团队的tester必须有工具来实现接近真实环境(往往不包含上游组件)TA case。

这个时候,是不是可以设计一种工具,模拟下游对当前subsystem的接口调用?

业内应该也有不少类似的这样的实现,当前的这个实现(非常初级的版本,但是从抽象层次上,已经足够)简介如下.

tester期望:

有一种工具,比如我只要读配置脚本,脚本可能是xml/json形式,为简单记,往往json更能达成一致。工具可以读json脚本,从json文件中提取需要测试的API以及精心设计的不同参数,这样工具可以模拟下游,来从各个维度调用API,并得到预期中的结果。这样在后续的TA case设计中,tester主要关注的就是配置脚本和预期输出。

开发如何满足tester期望?

1 设计一个protobuf形式的结构,来描述一个比较至关重要的类的接口,往往特指你对外交付的组件(include + lib + .pc 三件套?)接口,不仅在入参上约定好格式,也约定结果输出的格式。

2 工具能够读配置脚本(json格式),然后将json格式默认转换为protobuf表达,完成参数获取工作。工具模拟下游代码,将参数填入,调用接口,获得输出。

3 工具将输出也以预设好的protobuf格式存储,待所有接口测试完成,以json形式固化到文件。tester可以解析文件来得到结果。

接上上面描述,自然的会出现下面的流程,可以方面的达成TA

1  tester准备精心设计好的配置脚本,存放在某个目录,比如 /tmp/your-test-multiple-directory/

2 tester将工具安装在环境中, 工具名称 your_own_tool

3 tester 可以运行命令

your_own_tool  --input=/tmp/your-test-multiple-directory/   --output_summary=/tmp/your-output-summary.txt"

4 tester 解析/tmp/your-output-summary.txt来获取结果。

乱七八糟的说了一通,对于很多有经验的看官来说,其实司空见惯。

笔者接手这个工具的任务时,一开始其实有些懵的,背景知识少,也几乎没有可参考对象,从零开始。

个中经历无需赘言,码农的基操,因为水平有限,其实最终版本还经历了后续不少高手的优化,这里提供原始版本,不涉嫌泄密。如有雷同,请联系我删除这篇文章。

整个工具思路的一个重要的实现核心,是围绕着  protobuf 和json之间互转完成的, protobuf实在是比较好用,在未来的工具相关开发中,还会大量的用到。

本样例是无情阉割版本,大致体现一个意思

1 protobuf 设计

syntax = "proto3";
import "google/protobuf/timestamp.proto";

package your_own_toolproto;

message Result
{
    uint32 errorcode = 1;
    uint64 consumed = 4;
}

message Context {
    repeated string name = 1;
}

message Do_Something{
    Result result = 1;
    string params = 2;
}

message setEventHandler
{
    Result result = 1;
    string eventfile = 2;
}

message ProxyA {
    repeated Do_Something somethingparams = 1;
    setEventHandler seteventhandler = 2;
}
message ProxyB {
    uint32 dummy = 1;
}
message ProxyC {
    uint32 dummy = 1;
}

message Proxy
{
    Context context = 1;
    ProxyA proxya = 2;
    ProxyA proxyb = 3;
    ProxyA proxyc = 4;

}

2 命令行以及解析

your_own_tool_util.h

#include <string>

namespace your_own_tool {

struct CmdParams
{
    std::string inputfilepath;
    unsigned int repeatedtimes = 1;
    std::string output_summary_file;
    std::string output_details_file;
};

}
#endif //your_own_tool_UTIL_H

debug_option.h


#ifndef DEBUGTOOL_DEBUGOPTIONS_H
#define DEBUGTOOL_DEBUGOPTIONS_H
#include <fstream>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/program_options/config.hpp>
#include "cmdrunner.h"


namespace your_own_tool {

class DebugOptions {
public:
    DebugOptions( int argc, char** argv, CmdParams& cmdParams);
    ~DebugOptions();

    DebugOptions() = delete;
    DebugOptions(const DebugOptions&) = delete;
    DebugOptions& operator=(const DebugOptions&) = delete;

    bool IsParseSuccessfully() {return parseSuccess_;}

private:
    bool IsValidCommand();
    void ParseCommand(int ac, char **av);
    void CommandDescription();
    void PrintUsage();

    boost::program_options::variables_map vm_;
    boost::program_options::options_description cmd_line_options_;

    std::string inputfilepath;
    unsigned int repeatedtimes;
    std::string output_summary_file;
    std::string output_details_file;

    CmdParams &cmdParams;
    bool parseSuccess_ = true;

};

}
#endif //DEBUGTOOL_DEBUGOPTIONS_H

debug_option.cpp

#include "debug_option.h"
#include <syslog.h>
#include <thread>
#include <string>
#include <iostream>

using std::cout; using std::cin;
using std::endl; using std::string;

using namespace your_own_tool;

namespace po = boost::program_options;

DebugOptions::DebugOptions(int argc, char **argv, CmdParams& IncmdParams):cmdParams(IncmdParams)
{
    ParseCommand(argc, argv);
}

DebugOptions::~DebugOptions()
{

}


void DebugOptions::ParseCommand(int ac, char **av)
{
    po::options_description cmd("mandatory operation");
    cmd.add_options()
            ("help,h", "produce help message")
            ("input", po::value<std::string>(&cmdParams.inputfilepath),
             "configure, a json file or a directory with multiple json files")
            ("repeated-times", po::value<unsigned int>(&cmdParams.repeatedtimes), "repeated-times")
            ("output_summary", po::value<std::string>(&cmdParams.output_summary_file), \
                                                                           "output summary reports to given file name")

            ("output_details", po::value<std::string>(&cmdParams.output_details_file), \
                                                                      "output detailed reports to given name or paths")
            ;

    cmd_line_options_.add(cmd);

    po::store(po::command_line_parser(ac, av).options(cmd_line_options_).run(), vm_);
    po::notify(vm_);

    if (!IsValidCommand())
    {
        parseSuccess_ = false;
        PrintUsage();
    }

}

bool DebugOptions::IsValidCommand()
{
    bool ret = true;
    if (!(vm_.count("input")|| vm_.count("help")))
    {
        ret = false;
    }

    return ret;
}


void DebugOptions::PrintUsage()
{
    std::cout << cmd_line_options_ << std::endl;
    std::cout << "For examples:"<< std::endl;
    std::cout << "your_own_tool  --input=/tmp/your-test-multiple-directory/  --repeated-times=10  --output_summary=/tmp/your-output-summary.txt" << std::endl;
    std::cout << "your_own_tool input=/tmp/your-test-single-file.json  --output_summary=/tmp/your-output-summary.txt" << std::endl;
    std::cout << "your_own_tool input=/tmp/your-test-single-file.json  --output_details=/tmp/your-output-details.txt" << std::endl;
}

3 脚本的参数提取以及执行,结果输出

cmdrunner.h


#ifndef CMDRUNNER_H
#define CMDRUNNER_H
#include <fstream>
#include <filesystem>
#include "your_own_tool_util.h"
#include "your_own_tool.pb.h"

namespace fs = std::filesystem;

namespace your_own_tool {

class CmdRunner {
public:
    CmdRunner( CmdParams &cmdParams );
    ~CmdRunner();

    CmdRunner() = delete;
    CmdRunner(const CmdRunner&) = delete;
    CmdRunner& operator=(const CmdRunner&) = delete;

    bool runCmd();

private:
    int runCmdwithDir(const fs::path &filepath);
    int runCmdwithFile(const fs::path &filepath);
    int parseJsonAndRun(const fs::path &filepath);
    void getoutput_summary_file(std::string &output_summary_file);
    void getoutput_detail_file(const fs::path &filepath, std::string &output_details_file);
    std::string readJsonFile(const fs::path &filepath);
    void writeJsonfile(your_own_toolproto::Proxy &proxy_outputdetail);

private:
    CmdParams &cmdParams;
    std::string _output_summary_file;
    std::string _output_details_file;
};

}
#endif //CMDRUNNER_H

cmdrunner.cpp

#include "cmdrunner.h"
#include <syslog.h>
#include <thread>
#include <iostream>
#include <chrono>
#include <vector>
#include <string>
#include <stdlib.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/json_util.h>



using std::cout; using std::cin;
using std::endl; using std::string;
using std::filesystem::directory_iterator;
namespace fs = std::filesystem;
using namespace your_own_tool;


CmdRunner::CmdRunner( CmdParams &IncmdParams ):cmdParams(IncmdParams)
{

}

CmdRunner::~CmdRunner()
{

}


void CmdRunner::getoutput_summary_file(std::string &output_summary_file)
{
    if(cmdParams.output_summary_file.empty())
    {
        char summary_file_template[] = "/tmp/your_output_summary_XXXXXX.log";
        mktemp(summary_file_template);
        output_summary_file = string(summary_file_template);
    }
    else
    {
        output_summary_file = cmdParams.output_summary_file;
    }

}

void CmdRunner::getoutput_detail_file(const fs::path &filepath, std::string &output_details_file)
{
    if(cmdParams.output_details_file.empty())
    {
        char details_file_template[] = "_XXXXXX.log";
        mktemp(details_file_template);
        output_details_file = string("/tmp/your_output_details/") + filepath.stem().string() + string(details_file_template);
    }
    else
    {
        output_details_file = cmdParams.output_details_file;
    }
}


int  CmdRunner::runCmdwithDir(const fs::path &filepath)
{
    for (const auto & file : fs::directory_iterator(filepath))
    {
        if(fs::is_regular_file(file.path()))
        {
            auto ret = runCmdwithFile(file.path());
            if(ret != 0)
            {
                std::cout<< file.path().string()<< "load failed!"<< endl;
            }
        }
    }
    return 0;
}

int CmdRunner::runCmdwithFile(const fs::path &filepath)
{
    getoutput_summary_file(_output_summary_file);
    getoutput_detail_file(filepath, _output_details_file);

    std::cout << "output_summary_file:"<<_output_summary_file << std::endl;
    std::cout << "output_details_file:"<<_output_details_file << std::endl;

    parseJsonAndRun(filepath);

    return 0;
}


std::string CmdRunner::readJsonFile(const fs::path &filepath)
{
    std::ifstream ifStr(filepath.string());
    if(!ifStr.is_open())
    {
        throw std::runtime_error(std::string("Open JSON file failed: ") + filepath.string() + strerror(errno));
    }

    std::ostringstream sin;
    sin << ifStr.rdbuf();
    ifStr.close();

    return sin.str();
}

void CmdRunner::writeJsonfile(your_own_toolproto::Proxy &proxy_outputdetail)
{
    std::string newstr;
    google::protobuf::TextFormat::PrintToString(proxy_outputdetail, &newstr);

    std::ofstream fout(_output_details_file, std::ios::out);
    if ( ! fout)
    {
        throw std::runtime_error(std::string("Open JSON file failed: ") + _output_details_file + strerror(errno));
    }

    fout << newstr<< endl;
    fout.close();
}


int CmdRunner::parseJsonAndRun(const fs::path &filepath)
{
    your_own_toolproto::Proxy proxy_params;
    auto status = google::protobuf::util::JsonStringToMessage(readJsonFile(filepath), &proxy_params);
    if(!status.ok())
    {
        throw std::runtime_error(std::string("Invalid JSON format: ") + filepath.string() + status.ToString());
    }

    if(!proxy_params.has_context())
    {
        return -1;
    }

    // only the first one.
    auto _context = std::make_shared<Context>();
    your_own_toolproto::Proxy proxy_outputdetail;

    proxy_outputdetail.mutable_context()->CopyFrom(proxy_params.context());

    if(proxy_params.has_proxya())
    {
        proxy_outputdetail.mutable_proxya()->CopyFrom(proxy_params.proxya());
      
        auto proxya = std::make_shared<ProxyA>(*_context);
        //duration = end time - start time
        auto start = std::chrono::system_clock::now();
        auto ret = proxya->dosomething(...);
        auto end = std::chrono::system_clock::now();
        auto tmconsume = (end - start).count();

        std::cout << "time: " << tmconsume << "ns" << std::endl;


        proxy_outputdetail.mutable_proxya()->mutable_somethingparams(i)->mutable_result()->set_errorcode(ret.value());
        proxy_outputdetail.mutable_proxya()->mutable_somethingparams(i)->mutable_result()->set_consumed(tmconsume/1000);

    }

    if(proxy_params.has_proxyb())
    {

    }

    return 0;
}

bool CmdRunner::runCmd()
{
    for(unsigned int i = 0; i < cmdParams.repeatedtimes; i++)
    {
        fs::path inputPath(cmdParams.inputfilepath);

        if (fs::is_directory(inputPath))
        {
            runCmdwithDir(inputPath);
        }
        else if(fs::is_regular_file(inputPath))
        {
            runCmdwithFile(inputPath);
        }
    }
    return true;
}

4 总入口

main.cpp

#include <string>
#include <iostream>
#include "debug_option.h"
#include "cmdrunner.h"

int main(int argc, char **argv) {
    your_own_tool::CmdParams cmdParams;

    try {
        your_own_tool::DebugOptions options(argc, argv, cmdParams);
        if(options.IsParseSuccessfully())
        {
            your_own_tool::CmdRunner cmdRunner(cmdParams);
            cmdRunner.runCmd();
        }
    }
    catch (const std::exception &e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    };
    return 0;
}

5 Makefile样本

BASE_CPPFLAGS = -I$(top_srcdir)/your_own_tool \
                -I$(top_srcdir)/your_own_tool/protobuf

BASE_CXXFLAGS = $(PTHREAD_CFLAGS) \
                $(BASE_CPPFLAGS)

AM_CXXFLAGS = $(WARN_CXXFLAGS) \
    -Wno-switch-enum \
    -Werror -Wextra -Wno-switch-default -Wno-missing-declarations \
    -std=c++17

protobuf_dir = protobuf
protobuf_srcs = \
    $(protobuf_dir)/your_own_tool.pb.cc

MOSTLYCLEANFILES = $(protobuf_dir)

BUILT_SOURCES = \
    $(protobuf_dir)/your_own_tool.pb.h

%.pb.cc %.pb.h: %.proto
	$(PROTOC) --proto_path=$(srcdir)/$(protobuf_dir) --cpp_out=$(builddir)/$(protobuf_dir) $^


BASE_LIBS = $(PTHREAD_LIBS) $(PROTOBUF_LIBS) \
    -lboost_system -lboost_program_options

# binary
bin_PROGRAMS = your_own_tool

your_own_tool_SOURCES = \
    src/main.cpp \
    src/debug_option.cpp \
    src/cmdrunner.cpp \
    $(protobuf_dir)/your_own_tool.pb.cc


your_own_tool_CPPFLAGS = $(BASE_CPPFLAGS)
your_own_tool_LDFLAGS = $(PTHREAD_LDFLAGS)
your_own_tool_LDADD = ${BASE_LIBS}


6 工程目录

转载请注明出处或者链接地址:https://www.qianduange.cn//article/11111.html
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!