如何为第三方遗留代码创建测试对象

发布于 2024-07-10 22:59:05 字数 1319 浏览 5 评论 0原文

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

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

//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++ 编写的。

I have a code base where many of the classes I implement derive from classes that are provided by other divisions of my company. Working with these other devisions often have the working relationship as though they are third party middle ware vendors.

I'm trying to write test code without modifying these base classes. However, there are issues with creating meaningful test
objects due to the lack of interfaces:

//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
  //...
};

I'd normally extract an interface and work from there, but as these are "Third Party", I can't commit these changes.

Currently, I've created a separate file that holds fake implementations for functions that are defined in the third-party supplied base class headers on a need to know basis, as has been described in the book "Working with Legacy Code".

My plan was to continue to use these definitions and provide alternative test implementations for each third party class that I needed:

//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.

But before I start doing that I was wondering if any one has tried providing actual objects on a code base similar to mine, which would allow creating new test specific classes to use in place of actual third-party classes.

Note all code bases in question are written in C++.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

2024-07-17 22:59:05

模拟对象适合此类任务。 它们允许您模拟其他组件的存在,而无需它们存在。 您只需在测试中定义预期的输入和输出。

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

Mock objects are suitable for this kind of task. They allow you to simulate the existence of other components without needing them to be present. You simply define the expected input and output in your tests.

Google have a good mocking framework for C++.

疯狂的代价 2024-07-17 22:59:05

我现在遇到了一个非常类似的问题。 我不想添加一堆仅用于测试目的的接口,因此我无法使用任何现有的模拟对象库。 为了解决这个问题,我做了同样的事情,创建一个具有虚假实现的不同文件,并让我的测试链接虚假行为,而生产代码链接真实行为。

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

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++ 社区距离模拟没有纯虚拟接口的类有多近。

I'm running into a very similar problem at the moment. I don't want to add a bunch of interfaces that are only there for the purpose of testing, so I can't use any of the existing mock object libraries. To get around this I do the same thing, creating a different file with fake implementations, and having my tests link the fake behaviour, and production code links the real behaviour.

What I wish I could do at this point, is take the internals of another mock framework, and use it inside my fake objects. It would look a little something like this:

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());
}

The most painful part of all this is that the other mock frameworks are so close to this, but don't do it exactly, and the macros are so convoluted that it's not trivial to adapt them. I've begun looking into this on my spare time, but it's not moving along very quickly. Even if I got my method working the way I want, and had the expectation setting code in place, this method still has a couple drawbacks, one of them being that your build commands can get to be kind of long if you have to link against a lot of .o files rather than one .a, but that's manageable. It's also impossible to fall through to the default implementation, since we're not linking it. Anyway, I know this doesn't answer the question, or really even tell you anything you don't already know, but it shows how close the C++ community is to being able to mock classes that don't have a pure virtual interface.

不美如何 2024-07-17 22:59:05

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

You might want to consider mocking instead of faking as a potential solution. In some cases you may need to write wrapper classes that are mockable if the original classes aren't. I've done this with framework classes in C#/.Net, but not C++ so YMMV.

浅听莫相离 2024-07-17 22:59:05

如果我有一个需要测试的类,该类派生自我无法(或不想)在测试下运行的类,我将:

  1. 创建一个新的纯逻辑类。
  2. 将 code-i-wanna-test 移至逻辑类。
  3. 使用接口与真实的类进行对话,以与基类和/或我不能或不会放入逻辑中的东西进行交互。
  4. 使用相同的接口定义一个测试类。 这个测试类除了 noop 或模拟真实类的奇特代码之外什么都没有。

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

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

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

我确信所有这些都在 Feather 的《Working with Legacy Code》一书中 - 而且我所描述的内容都有实际的术语。 只是别让我把书从书架上拿下来......

If I have a class that I need under test that derives from something I can't (or don't want to) run under test I'll:

  1. Make a new logic-only class.
  2. Move the code-i-wanna-test to the logic class.
  3. Use an interface to talk back to the real class to interact with the base class and/or things I can't or won't put in the logic.
  4. Define a test class using that same interface. This test class could have nothing but noops or fancy code that simulates the real classes.

If I have a class that I just need to use in testing, but using the real class is a problem (dependencies or unwanted behaviors):

  1. I'll define a new interface that looks like all of the public methods I need to call.
  2. I'll create a mock version of the object that supports that interface for testing.
  3. I'll create another class that is constructed with a "real" version of that class. It also supports that interface. All interface calls a forwarded to the real object methods.
  4. I'll only do this for methods I actually call - not ALL the public methods. I'll add to these classes as I write more tests.

For example, I wrap MFC's GDI classes like this to test Windows GDI drawing code. Templates can make some of this easier - but we often end up not doing that for various technical reasons (stuff with Windows DLL class exporting...).

I'm sure all this is in Feather's Working with Legacy Code book - and what I'm describing has actual terms. Just don't make me pull the book off the shelf...

§对你不离不弃 2024-07-17 22:59:05

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

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

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

One thing you did not indicate in your question is the reason why your classes derive from base classes from the other division. Is the relationship really a IS-A relationshiop ?

Unless your classes needs to be used by a framework, you could consider favoring delegation over inheritance. Then you can use dependency injection to provide your class with a mock of their class in the unit tests.

Otherwise, an idea would be to write a script to extract and create the interface your need from the header they provide, and integrate this to the compilation process so your unit test can ve checked in.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文