9

这是我的问题,我想模拟一个在初始化时创建线程并在销毁时关闭它的类。我的模拟类没有理由实际创建和关闭线程。但是,为了模拟一个类,我继承了它。当我创建模拟类的新实例时,会调用基类构造函数,从而创建线程。当我的模拟对象被销毁时,基类析构函数被调用,试图关闭线程。

如何在不必处理实际资源的情况下模拟 RAII 类?

4

4 回答 4

12

相反,您创建了一个描述类型的接口,并让真实类和模拟类都继承自该接口。所以如果你有:

class RAIIClass {
 public:
  RAIIClass(Foo* f);
  ~RAIIClass();
  bool DoOperation();

 private:
  ...
};

你会做一个像这样的界面:

class MockableInterface {
 public:
  MockableInterface(Foo* f);
  virtual ~MockableInterface();
  virtual bool DoOperation() = 0;
};

然后从那里走。

于 2008-10-12T17:34:37.543 回答
6

首先,您的类可能为它们的使用而设计得很好,但为测试而设计得不好,这不一定是不合理的事情。并非一切都容易测试。

大概您想使用另一个函数或类来使用您要模拟的类(否则解决方案是微不足道的)。让我们称前者为“用户”,后者为“Mocked”。以下是一些可能性:

  1. 更改 User 以使用 Mocked 的抽象版本(您可以选择使用哪种抽象:继承、回调、模板等......)。
  2. 为您的测试代码编译不同版本的 Mocked(例如,在编译测试时 #def out the RAII code)。
  3. 让 Mocked 接受构造函数标志以关闭其行为。我个人会避免这样做。
  4. 只是吸收分配资源的成本。
  5. 跳过测试。

如果您不能修改 User 或 Mocked,最后两个可能是您唯一的办法。如果您可以修改 User 并且您认为设计可测试的代码很重要,那么您应该在其他任何选项之前探索第一个选项。请注意,在使代码通用/灵活和保持简单之间可以进行权衡,这两者都是令人钦佩的品质。

于 2008-10-12T17:35:55.370 回答
1

pimpl 成语可能也适合你。创建您的 Thread 类,并在其下方引入具体实现。如果您输入正确的#defines 和#ifdefs,您的实现可能会在您启用单元测试时发生变化,这意味着您可以根据您要完成的任务在真实实现和模拟实现之间切换。

于 2008-10-13T02:50:01.650 回答
0

我使用的一种技术是使用某种形式的装饰器。您的最终代码有一个方法,该方法在堆栈上创建其实例,然后调用相同的方法,但在一个成员上,该成员是指向您的基类的指针。当该调用返回时,您的方法返回销毁您创建的实例。

在测试时,您交换一个不创建任何线程的模拟,而只是转发到您要测试的方法。

class Base{
 protected:
  Base* decorated;
 public:
  virtual void method(void)=0;
};
class Final: public Base{
  void method(void) { Thread athread; decorated->method(); } // I expect Final to do something with athread
};
class TestBase: public Base{
  void method(void) { decorated->method(); }
};
于 2008-10-12T21:22:07.707 回答