编写可测试代码-lambda函数和unique_ptr中的basic_istream工厂

问题描述 投票:0回答:1

简介

这是在我还不确定的现代C ++功能(例如lambda函数和basic_istream)的背景下评估我的方法的要求。最后,我列出了一些具体问题,所有这些都与同一问题有关。

背景

我正在使用Boost::program_options(1.69),特别是boost::program_options::parse_config_file()函数在C ++ 14中编写配置文件解析器。此功能(在boost/program_options/parsers.hpp中定义)有两个替代。

第一个从磁盘上的文件读取配置:

basic_parsed_options<charT>
parse_config_file(const char* filename, const options_description&,
                  bool allow_unregistered = false);

第二个从std::basic_istream<charT>引用中读取配置:

basic_parsed_options<charT>
parse_config_file(std::basic_istream<charT>&, const options_description&,
                  bool allow_unregistered = false);

我正在编写单元测试(使用Google Test),以便可以将配置文件的内容传递给函数,而不是像在生产中那样从实际文件中读取测试。这是为了避免创建临时配置文件,避免在并行运行测试时发生冲突等。也许这种方法可能会更好,但是能够从字符串而不是从文件中提供配置似乎并没有错。

我还将补充一点,我不能简单地将istream传递给process_config函数,因为在完整的实现中,文件名实际上是在函数本身内确定的。即首先解析命令行,然后从中获取配置文件名。因此,在执行函数之前文件名是未知的。

实施

被测函数原本看起来像这样:

struct AppConfig {
    bool myoption1 {false};
};

void process_config(int argc, char * argv[], AppConfig & config) {
    // ...
    po::parse_config_file("config.cfg", config_file_options);
    // ...
}

问题是此接口没有提供注入配置文件内容的方法,所以我考虑传递一个process_config()可以调用的回调以返回可以传递给basic_istream<charT>po::parse_config_file() 。这样,测试可以传入仅提供预加载了配置文件内容的std::istringstreambasic_istream模板的子类)的回调,而生产客户端可以传入简单的回调,该回调仅打开指定的文件放在磁盘上,并返回相应的istream。

为此,我创建了这个:

template <typename funcF>
void process_config(int argc, char * argv[], AppConfig & config, funcF get_istream) {

    namespace po = boost::program_options;

    po::options_description config_file_options;
    config_file_options.add_options() /* ... */;

    auto istr_up = get_istream("config.cfg");
    auto & istr = *istr_up.get();
    po::parse_config_file(istr, config_file_options);
}

然后在生产代码中,我可以使用它来从配置文件中返回ifstream:

auto make_config_istream(const std::string & s) {
    return std::unique_ptr<std::ifstream>(s.c_str());
}

并且在测试代码中,我可以使用它从测试中定义的字符串中返回配置文件的内容:

std::unique_ptr<std::basic_istream<char>> make_istream(const std::string & s) {
    return std::make_unique<std::istringstream>(s);
}

process_config(0, nullptr, config,
               [](const std::string &){ return make_istream("foo=1\nbar=2");} );

这似乎可以按照我的期望进行编译和执行。但是,由于lambda签名必须存在被忽略的const std::string &才能匹配process_config模板,因此显得有些笨拙,因此我尝试添加此附加的辅助函数以将其隐藏起来:

auto make_config(const std::string & config) {
    // the std::string parameter is ignored:
    return [config](const std::string &) { return make_istream(config); };
}

然后将测试代码更改为:

process_config(0, nullptr, config, make_config("foo=1\nbar=2"));

此语法将满足我的要求。

注意,我要传回唯一指针内的istream对象。我不是100%肯定这样做是正确的方法,但是由于istream的默认构造函数已禁用,因此我无法按值传递它们,因此看来我必须在堆上分配它们并传递它们在指针周围。

问题

感谢您阅读本文。我的问题是:

  1. 通过使用istream在堆上返回新构造的unique_ptr是否合适/最佳实践?有更好的方法吗?
  2. 使用istream取消引用unique_ptr以获得对auto & istr = *istr_up.get()的引用的访问方法是否正确?这对我来说很难闻。
  3. 使用模板化的process_config函数是否是处理get_istream参数所涉及类型的最佳方法? [auto]还有更好的方法吗?
  4. 最后,是否有更好的方法来解决整个问题?
c++ unit-testing lambda c++14 boost-program-options
1个回答
0
投票

通过使用unique_ptr在堆上返回新建的istream是否适当/最佳实践?有更好的方法吗?

[basic_istream是可移动的,您不需要unique_ptr

auto make_config_istream(const std::string & s) {
    return std::ifstream(s.c_str());
}

auto make_istream(const std::string & s) {
    return std::istringstream(s);
}

其余的可以原样保留。

使用auto&istr = * istr_up.get()取消引用unique_ptr以获得对istream引用的访问的方法正确吗?这对我来说很难闻。

这是正确的,但是不必要,因为1. auto & = *istr_up;。您也可以像使用原始指针一样使用istr_up(即->*)。

使用模板化的process_config函数是处理get_istream参数中涉及的类型的最佳方法吗?有没有更好的方法可以使用auto?

是,是这样做的正确方法,但是需要将parse_config移到所有使用它的翻译单元共享的标头中,否则需要格外小心,以在需要时正确地显式实例化。

© www.soinside.com 2019 - 2024. All rights reserved.