简体   繁体   English

循环中带/不带硬的单元测试

[英]Unit Testing With/Without hardward in the loop

Background背景

I am working on a project that interfaces with various cameras and their unique APIs.我正在开发一个与各种相机及其独特 API 交互的项目。 All cameras are abstracted into one interface, and the camera-specific API calls are contained within their own implementing classes.所有相机都被抽象为一个接口,相机特定的 API 调用包含在它们自己的实现类中。

Problem Explanation问题说明

I am trying to test a factory method that parses a form of configuration, and returns a pointer to the newly created camera.我正在尝试测试一种解析配置形式并返回指向新创建的相机的指针的工厂方法。 Here is a high-level view of what that looks like:这是外观的高级视图:

// camerafactory.cpp 

Camera* make_camera(Config& config) {
    switch (config.type) {
        case CameraTypes.A : {
            int param = config.param;
            return new CameraA(param);
        }
        case CameraTypes.B : {
            string param = config.param;
            return new CameraB(param);
        }
        case CameraTypes.C : {
            float param1 = config.param1;
            float param2 = config.param2;
            return new CameraC(param1, param2);
        }
        default: {
            throw std::invalid_argument("Invalid configuration!");
        }

    }
}

I want to test that these constructors are getting called correctly, however this would require the cameras to be physically connected to the computer to allow for the camera API's within the camera constructors to work.我想测试这些构造函数是否被正确调用,但这需要相机物理连接到计算机,以允许相机构造函数中的相机 API 工作。 This does make sense for some testing situations, ie demonstrating hardware in the loop, however in my specific case, it is unrealistic to have all types of cameras connected at a single time.这对于某些测试情况是有意义的,即在循环中演示硬件,但是在我的特定情况下,一次连接所有类型的相机是不现实的。

I would like to test that these constructors are at least getting called with the correct parameters , demonstrating that the parsing is working correctly.我想测试这些构造函数是否至少被正确的参数调用,证明解析工作正常。 I have considered abstracting out the parsing and testing that separately, but technically there is still the constructor call that is not being tested.我考虑过分别抽象出解析和测试,但从技术上讲,仍然存在未测试的构造函数调用。 I was also always told that adding functionality for the sake of testing is usually something that should be avoided, so that is another thing I am considering with my solution.我也总是被告知,为了测试而添加功能通常是应该避免的,所以这是我在解决方案中考虑的另一件事。 It would also be interesting to find a solution that allows easy compile-time switching between mocks/concrete implementations to test hardware in the loop.找到一种解决方案也很有趣,该解决方案允许在模拟/具体实现之间轻松进行编译时切换以在循环中测试硬件。

Versions版本

Note, I am using C++ 17, CMake, and the latest version of gtest.注意,我使用的是 C++ 17、CMake 和最新版本的 gtest。

Solutions being considered正在考虑的解决方案

Using macros to change the constructor calls to Mock constructor calls.使用宏将构造函数调用更改为 Mock 构造函数调用。

At the top of camerafactory.cpp , adding something like:camerafactory.cpp的顶部,添加如下内容:

// camerafactory.cpp

#ifndef PRODUCTION
#include "MockCamera.hpp"
#define CameraA MockCameraA
#define CameraB MockCameraB
#define CameraC MockCameraC
#endif

Where MockCameraN are defined in a separate hpp/cpp file with identical constructors, and also implement the same Camera Interface, which I can then use to test correct construction.其中MockCameraN在具有相同构造函数的单独 hpp/cpp 文件中定义,并且还实现相同的相机接口,然后我可以使用它来测试正确的构造。

This would allow me to test that the constructors are getting called correctly, but does feel like it goes against a cleaner solution that could be done with a language like Java.这将允许我测试构造函数是否被正确调用,但确实感觉它违背了可以使用 Java 之类的语言完成的更清洁的解决方案。

Using GMock使用 GMock

I have not looked too deeply into it yet, but GMock seems to be promising: http://google.github.io/googletest/gmock_cook_book.html我还没有深入研究它,但 GMock 似乎很有希望: http://google.github.io/googletest/gmock_cook_book.ZFC35FDC70D5FC69D2639883A82EZC7A

My advice with and without gmock is to use virtual methods that can be overridden in test code.我的建议是使用和不使用 gmock 的方法是使用可以在测试代码中覆盖的虚拟方法。

First, convert your factory funciton, make_camera , into its own class with virtual methods for things you want to validate.首先,将您的工厂make_camera转换为它自己的 class ,其中包含您想要验证的东西的虚拟方法。 For brevity, I'm using an inline approach.为简洁起见,我使用的是内联方法。 In production code, you would probably want to separate this out into its own.h file for the class declaration and.cpp file for the implementation.在生产代码中,您可能希望将其分离到自己的.h 文件中用于 class 声明和用于实现的.cpp 文件。

class CameraFactory
{
public:

    virtual Camera* make_camera_a(int param) {
        return new CameraA(param);
    }

    virtual Camera* make_camera_b(const string& param) {
        return new CameraB(param);
    }

    virtual Camera* make_camera_c(double param1, param2) {
        return new CameraC(param1, param2);
    }

    virtual Camera* make_camera(const Config& config) {
        switch (config.type) {
            case CameraTypes::A: {
                return make_camera_a(config.intParam);
            }
            case CameraTypes::B: {
                return make_camera_b(config.strParam);
            }
            case CameraTypes::C: {
                return make_camera_c(config.param1, config.param2);
            }
            default: {
                throw std::invalid_argument("Invalid configuration!");
            }
        }
    }
};

In your product code, where you would ordinarily invoke make_camera , you can just use a CameraFactory off the stack:在您通常会调用make_camera的产品代码中,您可以只使用堆栈外的 CameraFactory:

Replace lines like this in your product code:在您的产品代码中替换这样的行:

Camera* cam = make_camera(config); // old code

With this:有了这个:

CameraFactory factory;
Camera* cam = factory.make_camera(config);

Testing without gmock:没有 gmock 的测试:

Old way of testing this would be for your unit test to override the individual make functions in a class that partially mocks out the virtual methods you want to validate getting called correctly.对此进行测试的旧方法是让您的单元测试覆盖 class 中的各个 make 函数,该函数部分模拟了您想要验证的虚拟方法是否被正确调用。

class MockCamera : public Camera
{
};

class MockCameraFactory : public CameraFactory
{
    virtual Camera* make_camera_a(int param) {
        _camera_a_created = true;
        _intParm = param;
        return new MockCamera();
    }

    virtual Camera* make_camera_b(const string& param) {
        _camera_b_created = true;
        _strParam = param;
        return new MockCamera();
    }

    virtual Camera* make_camera_c(double param1, param2) {
        _camera_c_created = true;
        _param1 = param1;
        _param2 = param2;
        return new MockCamera();
    }

    bool _camera_a_created;
    bool _camera_b_created;
    bool _camera_c_created;
    int _intParam;
    string _strParam;
    double _param1;
    double _param2;

    CameraFactoryTest() {
        resetState();
    }
    void resetState() {
        _camera_a_created = false;
        _camera_b_created = false;
        _camera_c_created = false;
        _intParam = 0;
        _strParam = "";
        _param1 = 0;
        _param2 = 0;
    }
};

Notice that CameraFactoryTest doesn't override make_camera , but it does inherit from it.请注意, CameraFactoryTest 不会覆盖make_camera ,但它确实继承自它。

Then your unit test code can validate that the right parameters are getting passed to your constructors.然后,您的单元测试代码可以验证是否将正确的参数传递给您的构造函数。 I'm just guessing your ut framework has some validation macro called UNIT_TEST_ASSERT .我只是猜测您的 ut 框架有一些名为UNIT_TEST_ASSERT的验证宏。 I'm sure you have something equivalent.我敢肯定你有同等的东西。

void test_make_camera()
{
    MockCameraFactory factory;
    Config config;

    // test camera A
    config.type=CameraConfig::A;
    config.intParam = 42;
    factory.make_camera(config);
    UNIT_TEST_ASSERT(factory._camera_a_created == true);
    UNIT_TEST_ASSERT(factory._camera_b_created == false);
    UNIT_TEST_ASSERT(factory._intParam == 42);

    // test camera B
    factory.resetState();
    config = {};
    config.type=CameraConfig::B;
    config.strPara = "USB1";
    factory.make_camera(config);
    UNIT_TEST_ASSERT(factory._camera_b_created == true);
    UNIT_TEST_ASSERT(factory._strParam == "USB1");

    ...

}

With Google Test (GMock)使用谷歌测试(GMock)

It's even easier with gmock and enables your mock class to be more usable in other tests.使用 gmock 更容易,并使您的模拟 class 在其他测试中更有用。 Instead of explicitly coding up a MockCameraFactory class, you use the GMock way.您无需显式编码 MockCameraFactory class,而是使用 GMock 方式。

Start by declaring a mock class that inherits from your actual class.首先声明一个模拟 class 继承自您的实际 class。

class MockCameraFactory : public CameraFactory
{
public:
    MOCK_METHOD1(make_camera_a, Camera*(int param));
    MOCK_METHOD1(make_camera_b, Camera*(const string& param));
    MOCK_METHOD2(make_camera_c, Camera*(const string& param));
};

Then your test code can do something like this:然后你的测试代码可以做这样的事情:

class test_make_camera()
{
    MockCameraFactoryTest factory;
    MockCamera cameramock;

    EXPECT_CALL(factory, make_camera_a(42)).WillOnce(Return(&cameramock));

    Config config = {};
    config.type = CameraType::A;
    config.intParam = 42;

    factory.make_camera(config);

    Mock::VerifyAndClearExpectations(&factory);

}

I'm skimming over the details of how to setup Google Test such that it asserts through your own unit test framework.我正在浏览有关如何设置 Google Test 的详细信息,以便它通过您自己的单元测试框架进行断言。 But you get the idea.但你明白了。

Also of note - I originally declared make_camera to be virtual in the CameraFactory class.另外值得注意的是 - 我最初在 CameraFactory class 中声明make_camera是虚拟的。 It doesn't actually have to be virtual since your tests are only validating that the make_camera_x methods are getting called correctly.它实际上不必是虚拟的,因为您的测试只是验证make_camera_x方法是否被正确调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM