【问题标题】:How Do You Create Test Objects For Third Party Legacy Code如何为第三方遗留代码创建测试对象
【发布时间】:2009-01-07 16:56:46
【问题描述】:

我有一个代码库,其中我实现的许多类都派生自我公司其他部门提供的类。与这些其他部门合作通常具有工作关系,就好像他们是第三方中间件供应商一样。

我正在尝试在不修改这些基类的情况下编写测试代码。但是,创建有意义的测试存在问题 由于缺少接口而导致的对象:

//ACommonClass.h
#include "globalthermonuclearwar.h" //which contains deep #include dependencies...
#include "tictactoe.h" //...and need to exist at compile time to get into test...

class Something //which may or may not inherit from another class similar to this...
{
public:
  virtual void fxn1(void);  //which often calls into many other classes, similar to this
  //...
  int data1;  //will be the only thing I can test against, but is often meaningless without fxn1 implemented
  //...
};

我通常会提取一个界面并从那里工作,但由于这些是“第三方”,我无法提交这些更改。

目前,我创建了一个单独的文件,其中包含在需要知道的基础上在第三方提供的基类头文件中定义的函数的假实现,如“使用遗留代码”一书中所述.

我的计划是继续使用这些定义,并为我需要的每个第三方类提供替代测试实现:

//SomethingRequiredImplementations.cpp
#include "ACommonClass.h"
void CGlobalThermoNuclearWar::Simulate(void) {};  // fake this and all other required functions...
// fake implementations for otherwise undefined functions in globalthermonuclearwar.h's #include files...
void Something::fxn1(void) { data1 = blah(); } //test specific functionality.

但在我开始这样做之前,我想知道是否有人尝试在类似于我的代码库上提供实际对象,这将允许创建新的测试特定类来代替实际的第三方类。

请注意,所有有问题的代码库都是用 C++ 编写的。

【问题讨论】:

    标签: c++ unit-testing legacy


    【解决方案1】:

    Mock objects 适合这种任务。它们允许您模拟其他组件的存在而不需要它们存在。您只需在测试中定义预期的输入和输出。

    Google 有一个很好的 C++ 模拟框架。

    【讨论】:

      【解决方案2】:

      我现在遇到了一个非常相似的问题。我不想添加一堆只是为了测试而存在的接口,所以我不能使用任何现有的模拟对象库。为了解决这个问题,我做了同样的事情,用假实现创建一个不同的文件,让我的测试链接假行为,生产代码链接真实行为。

      我希望此时我能做的,是采用另一个模拟框架的内部结构,并在我的假对象中使用它。它看起来有点像这样:

      Production.h

      class ConcreteProductionClass { // regular everyday class
      protected:
          ConcreteProductionClass(); // I've found the 0 arg constructor useful
      public:
          void regularFunction(); // regular function that I want to mock
      }
      

      Mock.h

      class MockProductionClass 
          : public ConcreteProductionClass
          , public ClassThatLetsMeSetExpectations 
      {
          friend class ConcreteProductionClass;
          MockTypes membersNeededToSetExpectations;
      public:
          MockClass() : ConcreteProductionClass() {}
      }
      
      ConcreteProductionClass::regularFunction() {
          membersNeededToSetExpectations.PassOrFailTheTest();
      }
      

      ProductionCode.cpp

      void doSomething(ConcreteProductionClass c) {
          c.regularFunction();
      }
      

      Test.cpp

      TEST(myTest) {
          MockProductionClass m;
          m.SetExpectationsAndReturnValues();
          doSomething(m);
          ASSERT(m.verify());
      }
      

      所有这一切中最痛苦的部分是其他模拟框架非常接近这一点,但没有完全做到这一点,而且宏非常复杂,以至于调整它们并非易事。我已经开始在业余时间研究这个问题,但进展并不快。即使我的方法按照我想要的方式工作,并且期望设置代码到位,这种方法仍然有几个缺点,其中一个是如果你必须链接,你的构建命令可能会有点长很多.o 文件而不是一个.a,但这是可以管理的。也不可能通过默认实现,因为我们没有链接它。无论如何,我知道这并不能回答这个问题,甚至不能告诉你任何你不知道的事情,但它显示了 C++ 社区能够模拟没有纯虚拟接口的类是多么接近。

      【讨论】:

      • 我在这方面犯了一个错误,但我不想编辑它。使基类成为朋友是行不通的,需要对 mock 的内部进行 extern 声明,以便它们可以在基类函数的新主体中使用。然后在您的 main.cpp 或任何运行测试套件的文件中,您拥有内部对象的实例。
      【解决方案3】:

      您可能需要考虑 mocking 而不是伪装作为一种潜在的解决方案。在某些情况下,如果原始类不是,您可能需要编写可模拟的包装类。我已经用 C#/.Net 中的框架类完成了这个,但不是 C++ 所以 YMMV。

      【讨论】:

        【解决方案4】:

        如果我有一个在测试中需要的类源自我不能(或不想)在测试中运行的东西,我会:

        1. 创建一个新的纯逻辑类。
        2. 将 code-i-wanna-test 移至逻辑类。
        3. 使用接口与真实类对话,以与基类和/或我不能或不会放入逻辑的事物进行交互。
        4. 使用相同的接口定义一个测试类。这个测试类可能只有 noops 或模拟真实类的花哨代码。

        如果我有一个只需要在测试中使用的类,但使用真正的类是一个问题(依赖关系或不需要的行为):

        1. 我将定义一个新接口,该接口看起来像我需要调用的所有公共方法。
        2. 我将创建一个支持该接口进行测试的对象的模拟版本。
        3. 我将创建另一个类,该类由该类的“真实”版本构成。它还支持该接口。所有接口调用一个转发给真实对象的方法。
        4. 我只会对我实际调用的方法执行此操作,而不是所有公共方法。我会在编写更多测试时添加到这些类中。

        例如,我像这样包装 MFC 的 GDI 类来测试 Windows GDI 绘图代码。模板可以使其中一些更容易 - 但由于各种技术原因(与 Windows DLL 类导出有关的东西......),我们经常最终没有这样做。

        我确信所有这些都在 Feather 的 Working with Legacy Code 一书中 - 我所描述的内容具有实际意义。只是不要让我把书从书架上拿下来......

        【讨论】:

          【解决方案5】:

          您在问题中没有指出的一件事是您的类从另一个部门的基类派生的原因。这种关系真的是一种 IS-A 关系吗?

          除非您的类需要被框架使用,否则您可以考虑优先考虑委托而不是继承。然后,您可以使用依赖注入在单元测试中为您的类提供类的模拟。

          否则,一个想法是编写一个脚本,从它们提供的标头中提取和创建您需要的接口,并将其集成到编译过程中,以便您的单元测试可以签入。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-03-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多