【问题标题】:Mocking a boost shared memory derived class with gtest使用 gtest 模拟 boost 共享内存派生类
【发布时间】:2020-10-29 17:51:03
【问题描述】:

我有一个简单的 CPP 类来存储我的项目的一些配置。

这个类是使用boost进程间共享内存存储的,因此可以从我服务器上运行的不同进程访问。

现在,我想在我的程序上运行一些测试——所以我想模拟我的共享内存对象的功能。 为了使用 gtest 做到这一点,我创建了一个基本配置类,我的模拟类和共享内存类将从该类派生。

为了正确使用gtest,我想模拟的基类函数-必须是虚拟的,但是根据boost documentation,共享内存不能包含虚拟函数,所以这是一种僵局。

我的基类示例:

class Configuration {
protected:
    YAML::Node YmlFile_;
public:
    struct System {
    private:
        float num1;
        float num2;
    public:
        virtual ~System(){}
        virtual float GetNum1() const {
            return num1;
        }

        virtual float GetNum2() const {
            return num2;
        }
    struct Logs{
    private:
        float num3;
        float num4;
    public:
        virtual ~Logs(){}
        virtual float GetNum3() const {
            return num3;
        }

        virtual float GetNum4() const {
            return num4;
        }
    Logs logs;
    System system;
    virtual System* GetSystem(){}
    virtual Logs* GetLogs(){}

我希望在模拟类的哪个位置模拟函数以获取结构(GetSystem、GetLogs),然后模拟它们的返回值,但仍然能够保存将保存在共享内存。

有什么想法吗..?

【问题讨论】:

    标签: c++ boost googletest boost-interprocess


    【解决方案1】:

    原则第一:

    你不必使用虚函数来模拟。

    在你不能使用运行时多态类型的地方你可以使用静态多态。

    但在这种情况下,最好将配置接口与实现完全分离。

    实现您的接口,而不是派生来自共享内存容器(“配置源是-A 共享内存对象”)。而是说“配置源有共享内存对象”。

    其他关键问题:

    • 是什么让 YAML::Node 对共享内存安全?可能不是,因为我没有看到指定的分配器,而且它肯定涉及动态分配的内存以及内部指针。

      我认为这种方法很容易因此而死在水中。

    • 如果实际来源是 YAML,为什么不直接共享文件,而不是高度复杂的共享内存? (我们只是在表面上吃草。我们甚至没有提到同步)。

      文件系统从一开始就是计算机上进程事实上的“共享内存”。

    解耦接口和实现示例

    接口使得实现可以解耦,但正如您所注意到的,继承通常使得它们不是。

    为什么不写这样的东西:

    struct ConfigData {
        struct System {
            float num1;
            float num2;
    
            struct Logs {
                float num3;
                float num4;
            } logs;
        } system;
    };
    

    现在制作一个共享界面(我会为演示简化它):

    struct IConfiguration {
        virtual ConfigData const& getData() const = 0;
    };
    

    所以你可以有你的 YAML 后端:

    class YAMLConfiguration : public IConfiguration {
      public:
        YAMLConfiguration(std::istream& is) : _node(YAML::Load(is)) {
            parse(_node, _data);
        }
    
        virtual ConfigData const& getData() const override {
            return _data;
        }
      private:
        YAML::Node _node;
        ConfigData _data;
    };
    

    或共享内存实现:

    #include <boost/interprocess/managed_shared_memory.hpp>
    namespace bip = boost::interprocess;
    
    class SharedConfiguration : public IConfiguration {
      public:
        SharedConfiguration(std::string name) 
            : _shm(bip::open_or_create, name.c_str(), 10ul << 10),
              _data(*_shm.find_or_construct<ConfigData>("ConfigData")())
        { }
    
        virtual ConfigData const& getData() const override {
            return _data;
        }
      private:
        bip::managed_shared_memory _shm;
        ConfigData& _data;
    };
    

    完整演示

    Live On Coliru¹

    struct ConfigData {
        struct System {
            float num1 = 77;
            float num2 = 88;
    
            struct Logs {
                float num3 = 99;
                float num4 = 1010;
            } logs;
        } system;
    };
    
    struct IConfiguration {
        virtual ConfigData const& getData() const = 0;
    };
    
    ///////// YAML Backend
    #include <yaml-cpp/yaml.h>
    static bool parse(YAML::Node const& node, ConfigData::System::Logs& data) {
        data.num3 = node["num3"].as<float>();
        data.num4 = node["num4"].as<float>();
        return true;
    }
    static bool parse(YAML::Node const& node, ConfigData::System& data) {
        data.num1 = node["num1"].as<float>();
        data.num2 = node["num2"].as<float>();
        parse(node["Logs"], data.logs);
        return true;
    }
    static bool parse(YAML::Node const& node, ConfigData& data) {
        parse(node["System"], data.system);
        return true;
    }
    
    class YAMLConfiguration : public IConfiguration {
      public:
        YAMLConfiguration(std::istream& is) : _node(YAML::Load(is)) {
            parse(_node, _data);
        }
    
        virtual ConfigData const& getData() const override {
            return _data;
        }
      private:
        YAML::Node _node;
        ConfigData _data;
    };
    
    ///////// Shared Memory Backend
    #include <boost/interprocess/managed_shared_memory.hpp>
    namespace bip = boost::interprocess;
    
    class SharedConfiguration : public IConfiguration {
      public:
        SharedConfiguration(std::string name) 
            : _shm(bip::open_or_create, name.c_str(), 10ul << 10),
              _data(*_shm.find_or_construct<ConfigData>("ConfigData")())
        { }
    
        virtual ConfigData const& getData() const override {
            return _data;
        }
      private:
        bip::managed_shared_memory _shm;
        ConfigData& _data;
    };
    
    
    #include <iostream>
    void FooFunction(IConfiguration const& cfg) {
        std::cout << "Logs.num3:" << cfg.getData().system.logs.num3 << "\n";
    }
    
    void FakeApplication() {
        std::cout << "Hello from FakeApplication\n";
        std::istringstream iss(R"(
    System:
        num1: 0.1
        num2: 0.22
        Logs:
            num3: 0.333
            num4: 0.4444
        )");
    
        YAMLConfiguration config(iss);
        FooFunction(config);
    }
    
    void FakeTests() {
        std::cout << "Hello from FakeTests\n";
        SharedConfiguration config("shared_memory_name");
        FooFunction(config);
    }
    
    int main() {
        FakeApplication();
        FakeTests();
    }
    

    打印

    Hello from FakeApplication
    Logs.num3:0.333
    Hello from FakeTests
    Logs.num3:99
    

    总结和注意事项

    简而言之,在使用共享内存之前要三思而后行。没有你想的那么简单。

    您的某些配置值很可能不是 POD 数据类型(您知道,可能是字符串),突然之间您将不得不关心分配器:

    另外,不要忘记访问共享内存的进程之间的同步。


    ¹Coliru 没有 yaml-cpp,但您可以使用 managed_mapped_file 显示共享实现:Live On Coliru

    【讨论】:

      【解决方案2】:

      似乎hi-perf dependency injection 可以在这里解决问题。这个想法是您不会通过 ctor 或一些 setter 注入 Configuration 对象,但您的测试类将使用模板。在生产中,它将使用Configuration,在测试中它将使用ConfigurationStub。这两个不共享基类,但只要具有相同的方法签名 - 它就可以正常工作。

      【讨论】:

      • 是的。这就是我所说的“静态多态性”。但是,我认为您需要仔细考虑为什么要嘲笑什么,然后再进行这些长度。在这种情况下,存在更简单的方法。
      猜你喜欢
      • 1970-01-01
      • 2021-07-26
      • 1970-01-01
      • 1970-01-01
      • 2018-08-25
      • 2011-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多