【问题标题】:How to use GMock check base class function is called如何使用GMock检查基类函数被调用
【发布时间】:2016-06-19 14:20:59
【问题描述】:

这与How to use gmock to test that a class calls it's base class' methods 密切相关,但我很难通过我的示例来实现这一点。

我正在使用 GTest 和 GMock 来测试一个新功能,所以我有一个基类...

class SimpleObject
{
public:
    explicit SimpleObject() {}

    virtual void moveX(int dX)
    {
        // Do important stuff like updating position, bounding box etc.
    }

    // ...
};

基于其他 TDD,我有一个派生类,新功能是,当我在派生对象上调用 moveX 时,它会做一些特定的事情,但是它还需要在 SimpleObject::moveX 中做重要的事情。

我已经有与 SimpleObject::moveX 函数相关的测试驱动单元测试,所以我不想为派生类重复它们。只要我知道 SimpleObject::moveX 被调用,那么一切都很好。

无论如何,基于上面的链接和遵循 TDD,我最终得到了以下结果。

派生类:

    class ComplexObject : public SimpleObject
    {
    public:
        virtual void moveX(int dX)
        {
            // Do something specific
        }
    };

“可测试”类:

class TestableComplexObject : public ComplexObject
{
public:
    MOCK_METHOD1(moveX, void(int dX));

    void doMoveX(int dX)
    {
        SimpleObject::moveX(dX);
    }
};

测试:

TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled)
{
    int dX(8);
    TestableComplexObject obj;

    EXPECT_CALL(obj, moveX(dX))
                .Times(1)
                .WillRepeatedly(testing::Invoke(&obj, &TestableComplexObject::doMoveX));

    obj.moveX(dX);
}

如果我运行测试,那么一切都会通过。这是不正确的,因为您可以看到 ComplexObject::moveX 没有做任何事情。

此外,无论我在 doMoveX 中添加了什么(我认为这是为了建立我的期望),测试仍然会通过。

我显然在这里遗漏了一些简单的东西,所以有什么想法吗?

【问题讨论】:

    标签: c++ unit-testing tdd googletest googlemock


    【解决方案1】:

    感谢 cmets,通过对设计的调整,我能够测试我想要的。

    首先,为 SimpleObject 创建一个接口:

    class ISimpleObject
    {
    public:
        virtual void moveX(int dX) = 0;
    };
    

    然后我的 SimpleObject 类实现了这个:

    class SimpleObject : public ISimpleObject
    {
    public:
        explicit SimpleObject() {}
    
        virtual void moveX(int dX)
        {
            (void) dX;
            // Do important stuff like updating position, bounding box etc.
        }
    };
    

    ComplexObject 不是从 SimpleObject 继承,而是从接口继承并拥有一个 SimpleObject(即它“拥有 a”而不是“是 a”)。构造函数确保我们传入一个 SimpleObject,正是这种注入使模拟变得更容易。

    class ComplexObject : public ISimpleObject
    {
    public:
        ComplexObject(SimpleObject *obj)
        {
            _obj = obj;
        }
    
        virtual void moveX(int dX)
        {
            _obj->moveX(dX);
        }
    
    private:
        SimpleObject *_obj;
    };
    

    现在我只是从 SimpleObject 模拟我感兴趣的调用

    class SimpleObjectMock : public SimpleObject
    {
    public:
            MOCK_METHOD1(moveX, void(int dX));
            // Do important stuff like updating position, bounding box etc.
    };
    

    测试也简化了

    TEST_F(AComplexObject, CallsBaseClassMoveXWhenMoveX)
    {
        int dX(8);
    
        SimpleObjectMock mock;
        ComplexObject obj(&mock);
    
        EXPECT_CALL(mock, moveX(dX)).Times(1);
    
        obj.moveX(dX);
    }
    

    行为符合预期。如果 ComplexObject::moveX 函数为空(因为它将在开始时),则测试失败。它只会在你调用 SimpleObject::moveX 时通过。

    【讨论】:

      【解决方案2】:

      您需要设计您的ComplexObject,以便检查SimpleObject::moveX 是否被调用。一种方法:用其他可以模拟的函数封装这个基调用:

      class ComplexObject : public SimpleObject
      {
      public:
          virtual void moveX(int dX)
          {
              // Call base function
              baseMoveX(dx);
              // Do something specific
          }
      protected: 
          virtual void baseMoveX(int dX)
          {
              SimpleObject::moveX(dx);
          }
      };
      

      然后在你的Testable 类中模拟这个基本函数:

      class TestableComplexObject : public ComplexObject
      {
      public:
          MOCK_METHOD1(baseMoveX, void(int dX));
      };
      

      您不能只模拟 moveX,因为 - 在这种情况下,无法区分基类和派生类。

      所以 - 您的测试可能如下所示:

      TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled)
      {
          int dX(8);
          TestableComplexObject obj;
      
          EXPECT_CALL(obj, baseMoveX(dX))
                      .WillOnce(testing::Invoke([&obj] (auto dx) {obj.SimpleObject::moveX(dx); }));
      
          obj.moveX();
      }
      

      [更新]

      正如在 cmets 中发现的那样 - 如何确保 ComplexObject::baseMoveX() 调用 SimpleObject::moveX 仍然存在问题。

      可能的解决方案是在 ComplexObject 和 SimpleObject 之间再添加一个类。

      template <typename BaseObject>
      class IntermediateObject : public BaseObject
      {
      public:
          virtual void baseMoveX(int dX)
          {
              BaseObject::moveX(dx);
          }
      };
      

      通过测试确保这确实发生:

      class TestableBaseMock
      {
      public:
          MOCK_METHOD1(moveX, void(int dX));
      };
      
      TEST(IntermediateObject Test, shallCallBaseMoveX)
      {
          const int dX = 8;
          IntermediateObject<TestableBaseMock> objectUnderTest;
          TestableBaseMock& baseMock = objectUnderTest;
      
          EXPECT_CALL(baseMock, moveX(dX));
      
          objectUnderTest.baseMoveX(dx);
      }
      

      然后 - 把它放在简单类和复杂类之间:

      class ComplexObject : public IntermediateObject<SimpleObject>
      {
      public:
          virtual void moveX(int dX)
          {
              // Call base function
              baseMoveX(dx);
              // Do something specific
          }
      };
      

      最后——我只想强调改变你的原始设计——使用聚合而不是继承(也就是装饰器设计模式)是最好的方法。首先 - 正如您在我的回答中看到的那样 - 如果我们想测试它,试图保持继承会使设计变得更糟。第二 - 对装饰器类型的设计的测试要简单得多,因为它在一个答案中显示......

      【讨论】:

      • 好的,这是有道理的,我可以看到它是如何工作的。但是,如果我有更多的派生基类函数怎么办。有效地复制所有这些功能仍然是一个可以接受的好设计吗?
      • 实施您的更改会导致它在 obj.moveX(dX) 行出现段错误,大概是因为它是一个模拟?
      • 我已经尝试过这个示例,并且 ComplexObject::baseMoveX 和 ComplexObject::moveX 为空。第一次运行测试失败,所以我将 baseMoveX 调用放入通过的 ComplexObject::moveX 中。它不应该通过,因为我的 ComplexObject::baseMoveX 什么都不做。所以这和我之前的情况一样
      • 所以可能我没有得到您确切的要求...从您提出的问题的 TC 中,我很清楚您希望从 ComplexObject::moveX 调用 SimpleObject::moveX(dx)。如果不是这种情况-请澄清您的问题-例如显示您在ComplexObject::moveX 中的代码以及您想在 TC 中检查的内容...
      • 没错,我希望 SimpleObject::moveX(dX) 在 ComplexObject::moveX 被调用时被调用。因为我正在测试这个 ComplexObject::moveX 是空的
      【解决方案3】:

      这里的主要问题是,在方法@​​987654321@ 中,您正在调用方法SimpleObject::moveX。这就是为什么一切都会过去的原因。您应该调用属于类ComplexObject 的方法moveX。因此,将方法doMoveX更改为:

      void doMoveX(int dX)
      {
          ComplexObject::moveX(dX);
      }
      

      会解决你的问题。

      您发布的代码还有一个问题。测试正文中的最后一条语句应该是:

      obj.moveX(dX);
      

      但我猜这只是写问题时犯的错误?

      希望这会有所帮助!

      【讨论】:

      • 无论我在 doMoveX 中放了什么,测试都通过了,这就是混乱的来源。另外,如果我调用 ComplexObject::doMoveX 那么这肯定会调用派生函数而不是我需要确保调用的基函数?
      • 是的,最后一个语句是一个错字。我将编辑原帖
      猜你喜欢
      • 2013-11-08
      • 1970-01-01
      • 2011-09-08
      • 2011-06-11
      • 2016-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多