【问题标题】:How to test classes that is derived from external base class which depends on external system如何测试从依赖于外部系统的外部基类派生的类
【发布时间】:2016-08-30 06:35:30
【问题描述】:

我有一个类是我无法控制的外部类的子类。外部类依赖于系统资源。例如

class MyClass : public ExternalBase // This class is from external framework and framework requires it to derive from this class.
{
    int doSomePrivateThing(int );
public: 

    virtual int DoSomething(int );
    virtual ~MyClass();
}

int MyClass::doSomePrivateThing(int )
{
    // do some private task
}

int MyClass::DoSomething(int n)
{
    // Do MyClass Specific task
    int k = doSomePrivateThing(n);
    return ExternalBase::DoSomething(k); // This function depends on external system resources.
                                         // Probably try to communicate with remote server 
                                         // or attempt access Storage or Display device etc.
}

MyClass::~MyClass()
{}

如何打破 MyClass 的依赖并为 MyClass::DoSomething() 编写单元测试。使用组合代替继承不是一种选择,因为框架需要从这个基类派生类。

我正在使用 C++ 和 GoogleTest/Mock。但任何通用的解决方案都值得赞赏。

提前致谢。

【问题讨论】:

  • @SergeyA 我不能就这样放弃。我不能做一些宏和/或模板技巧来打破依赖关系吗?我有一个想法,但我不确定它是否好。我会将其发布为答案。但是如果其他人有更好的想法,我正在等待其他人。
  • 我的经验是使用 CppUTest,所以这些条款可能会被取消。但最好的办法是检查 MyClass::DoSomething 在外部调用之前是否按预期运行,此时您可以测试您是否按照预期传递了它。在 CppUTest 中,这是通过模拟完成的,我不确定 GoogleTest 等效项是什么。
  • @SergeyA 你说得对,我不能在这里真正应用宏和/或模板魔法。我试图模板化基类,以便我可以向它提供一个模拟。但它没有用。
  • @Aumnayan GoogleTest 有模拟。而您期望来自 MyClass::DoSomething() 的外部调用的想法似乎是唯一的方法。这当然意味着我需要外部类的源代码才能知道它会进行哪些外部调用。如果它调用任何静态或全局函数,那么我们将再次注定失败。

标签: c++ unit-testing mocking googletest


【解决方案1】:

有两种方法。我称它们为“更正确”的方式和“非常丑陋”的方式。


“更正确”的方式:

用一些额外的层来封装外部类函数,而不是部分模拟。

class MyClass : public ExternalBase // This class is from external framework and framework requires it to derive from this class.
{
    int doSomePrivateThing(int );
public: 
    virtual void BaseDoSomething(int) { return ExternalBase::DoSomething(v); }
    virtual int DoSomething(int v);
    virtual ~MyClass();
};

int MyClass::DoSomething(int n)
{
    // Do MyClass Specific task
    int k = doSomePrivateThing(n);
    return BaseDoSomething(k); 
}

并以这种方式在 UT 中进行部分模拟:

class TestableMyClass : public MyClass
{
public:
    using MyClass::MyClass;
    MOCK_METHOD1(BaseDoSomething, int(int));
}; 

TEST(A,A)
{
    TestableMyClass objectUnderTest;
    EXPECT_CALL(objectUnderTest, BaseDoSomething(112));

    objectUnderTest.DoSomething(112);
}

当您还需要在测试中调用真正的基类方法时 - 使用 WillOnce(Invoke...) 和 EXPECT_CALL。


“非常丑陋”的方式:

提供您自己的 ExternalBase 的 UnitTest 实现并将其链接到您的测试。 ExternalBase 的这种“UnitTest”实现应该基于一些全局 Mocks 对象。

ExternalBaseMock.hpp:

class ExternalBaseMock 
{
public:
    MOCK_METHOD1(DoSomething, int(int));
};
extern ExternalBaseMock  externalBaseMock;

ExternalBaseMock.cpp:

ExternalBaseMock  externalBaseMock;

int ExternalBase::DoSomething(int n)
{
    return externalBaseMock.DoSomething(n);
}

然后你的测试:

#include "ExternalBaseMock.hpp"

TEST(A,A)
{
    MyClass objectUnderTest;
    EXPECT_CALL(externalBaseMock, DoSomething(112));

    objectUnderTest.DoSomething(112);
}

【讨论】:

  • 非常感谢。这两种方法都非常有效,它解决了我的问题。我最喜欢“丑陋的方式”。因为我可以使用条件编译并将ExternalBase 类替换为这个假类。在“更正确的方式”中,每个基类方法(我想测试)都必须在派生类中有相应的方法。 @PiotrNycz 有什么特别的理由称它为“丑陋的方式”吗?这种方法可能会出现什么问题吗?
  • 问题是它的线程不友好,你可以有效地只拥有这样一个“丑陋的模拟”类的对象。在大多数情况下,在 UT - 它们不是一个大问题。更重要的是它有点违背嘲笑bla bla bla的哲学......如果它最适合你的需要就使用它......
  • 顺便说一句,这两个问题都有解决方法 - 但它会使您正在测试的内容更加复杂 - 所以可能没什么大不了
  • 是的,对我来说这些都不是问题。我打算测试 MFC 的CWnd 派生类。我不想创建窗口,因为这需要有一个消息循环,这在测试中是有问题的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多