用gtest模拟一个Boost共享内存派生类 [英] Mocking a boost shared memory derived class with gtest

查看:66
本文介绍了用gtest模拟一个Boost共享内存派生类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的CPP类,用于存储项目的某些配置.

I have a simple CPP class storing some configuration of my project.

此类使用Boost进程间共享内存存储,因此可以从服务器上运行的不同进程进行访问.

This class is stored using boost interprocess shared memory, and so can be accessed from different processes running on my server.

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

Now, I would like to run some tests on my program- so I wish to mock the functionality of my shared-memory-object. In order to do that with gtest, I've created a base configuration class, which my mock class and my shared memory class will derive from.

为了正确使用gtest,我想模拟的基类函数必须是虚拟的,但是根据

In order to use gtest correctly, the base's class functions I would like to mock- must be virtual, but according to boost documentation, the shared memory can't include virtual functions, so it's kind of a deadlock.

我的基础班的例子:

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),然后模拟其返回值,但仍具有保存真实的"派生类Configuration的功能,该类将保存在共享内存.

Where on the mocked class I wish to mock the functions to get the structs (GetSystem, GetLogs) and then mock their return values, but still having the ability to holds a 'real' derived class of Configuration that will be saved in the shared memory.

有什么想法吗??

推荐答案

原则优先:

您不必使用虚拟函数进行模拟.

You don't have to use virtual functions to mock.

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

In places where you can't use runtime-polymorphic types you can use static polymorphism.

但是在这种情况下,似乎更好了,将配置接口与实现完全脱钩.

But in this case it seems better yet, decouple the configuration interface from the implementation altogether.

不要从共享内存容器(配置源Is-A共享内存对象")派生来实现您的界面.改为说配置源Has-A共享内存对象".

Implement your interface not deriving from a shared memory container ("configuration source Is-A shared memory object"). Instead say "configuration source Has-A shared memory object".

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

  • What makes YAML::Node safe for shared memory? Likely it isn't, because I don't see an allocator specified, and it most certainly involves dynamically allocated memory, as well as internal pointers.

我认为这种方法很容易就此死掉.

I think the approach could easily be dead in the water just for that.

如果实际来源是YAML,为什么不共享文件而不是高度复杂的共享内存呢?(我们只是在这里放草.我们甚至没有提到同步).

If the actual source is YAML, why not just share the file, instead of highly complicated shared memory? (We're only grazing the surface here. We did't even mention synchronization).

文件系统是实际的共享内存".从一开始就在计算机上处​​理进程.

The filesystem is the de-facto "shared memory" of processes on a computer, since the beginning of time.

接口使之能够分离实现 ,但是正如您所注意到的,继承常常使它们不耦合.

Interfaces make it so that implementations can be decoupled, but as you noticed, inheritance often makes it so that they aren't.

为什么不这样写:

struct ConfigData {
    struct System {
        float num1;
        float num2;

        struct Logs {
            float num3;
            float num4;
        } logs;
    } system;
};

现在创建一个共享界面(我将在演示中对其进行简化):

Now make a shared interface (I'll simplify it for the demo):

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

因此您可以拥有一个YAML后端:

So you can have either your YAML backend:

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 在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

摘要和注意事项

简而言之,在使用共享内存之前,请三思而后行.这并不像您想的那么简单.

Summary And Caution

In short, think thrice before using the shared memory. It's not as simple as you think.

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

In all likelihood, some of your config values will be something else than POD data types (you know, maybe a string) and suddenly you'll have to care about allocators:

  • see these answers of mine to see what that looks like and to see whether it's worth it for you purposes

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

Also, don't forget about synchronization between processes that access shared memory.

¹Coliru没有yaml-cpp,但是您可以显示带有Managed_mapped_file的共享实现:

¹ Coliru doesn't have yaml-cpp, but you can show the shared implementation with managed_mapped_file: Live On Coliru

这篇关于用gtest模拟一个Boost共享内存派生类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆