19

有什么好的方法来单元测试析构函数吗?就像说我有一个像这样(人为)的例子:

class X
{
private:
    int *x;

public:
    X()
    {
         x = new int;
    }

    ~X()
    {
         delete x;
    }

    int *getX() {return x;}
    const int *getX() const {return x;}
};

有什么好的方法可以对此进行单元测试,以确保 x 被删除,而不会用#ifdef TESTs 弄乱我的 hpp 文件或破坏封装?我看到的主要问题是很难判断 x 是否真的被删除了,特别是因为在调用析构函数时对象超出了范围。

4

7 回答 7

10

对于依赖注入可能有一些话要说。不是在其构造函数中创建对象(在本例中为 int,但在非人为的情况下更可能是用户定义的类型),而是将对象作为参数传递给构造函数。如果稍后创建对象,则将工厂传递给 X 的构造函数。

然后,当您进行单元测试时,您传入一个模拟对象(或创建模拟对象的模拟工厂),析构函数记录它已被调用的事实。如果不是,则测试失败。

当然,您不能模拟(或以其他方式替换)内置类型,因此在这种特殊情况下它不好,但是如果您使用接口定义对象/工厂,那么您可以。

正如其他人所说,检查单元测试中的内存泄漏通常可以在更高级别完成。但这仅检查是否调用析构函数,并不能证明调用了正确的析构函数。因此,它不会例如在 x 成员类型的析构函数上捕获丢失的“虚拟”声明(同样,如果它只是一个 int,则不相关)。

于 2008-11-23T02:40:05.473 回答
10

我认为您的问题是您当前的示例不可测试。由于您想知道是否x已删除,因此您确实需要能够x用模拟替换。对于 int 来说,这可能有点 OTT,但我想在您的真实示例中,您还有其他类。为了使其可测试,X构造函数需要请求实现int接口的对象:

template<class T>
class X
{
  T *x;
  public:
  X(T* inx)
    : x(inx)
  {
  }

  // etc
};

现在模拟 for 的值变得很简单x,并且模拟可以处理检查是否正确销毁。

请不要注意那些说你应该打破封装或诉诸可怕的黑客以产生可测试代码的人。虽然经过测试的代码确实比未经测试的代码更好,但可测试的代码是最好的,它总是会产生更清晰的代码,更少的黑客攻击和更低的耦合。

于 2008-11-23T02:41:15.987 回答
2

我倾向于采用“以任何必要的方式”进行测试。如果它需要测试,我愿意泄露抽象、破坏封装和破解……因为经过测试的代码比漂亮的代码更好。我经常将打破这种情况的方法命名为 VaildateForTesting 或 OverrideForTesting,以明确违反封装仅用于测试。

除了将析构函数调用到单例中以注册它已被销毁之外,我不知道在 C++ 中执行此操作的任何其他方法。我想出了一种在 C# 中使用弱引用做类似事情的方法(我不违反这种方法的封装或抽象)。我没有足够的创造力来提出与 C++ 的类比,但你可能是。如果有帮助,那就太好了,如果没有,对不起。

http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx

于 2008-11-23T01:16:19.743 回答
1

在示例中,定义和检测您自己的全局 new 和 delete。

为了避免#ifdefs,我交了测试班的朋友。您可以根据需要设置/保存/获取状态以验证调用结果。

于 2008-11-23T01:21:39.250 回答
1

它与提出问题的人无关,但可能对阅读此问题的其他人有所帮助。我在一次工作面试中被问到类似的问题。

假设内存有限,可以试试这个方法:

  1. 分配内存直到分配失败并显示内存不足消息(在对析构函数运行任何相关测试之前)并在运行测试之前保存可用内存的大小。
  2. 运行测试(调用构造函数并根据需要对新实例执行一些操作)。
  3. 运行析构函数。
  4. 再次运行分配部分(如步骤 1 所示),如果您可以分配与在运行测试之前设法分配的内存完全相同的内存,则析构函数可以正常工作。

当您的内存有限时,这种方法(合理地)有效,否则它似乎不合理,至少在我看来。

于 2012-08-27T12:12:41.003 回答
0

一些编译器会在调试模式下使用已知模式覆盖已删除的内存,以帮助检测对悬空指针的访问。我知道 Visual C++ 曾经使用 0xDD,但我已经有一段时间没有使用它了。

在您的测试用例中,您可以存储 x 的副本,让它超出范围并确保 *x == 0xDDDDDDDD:

void testDestructor()
{
    int *my_x;
    {
        X object;
        my_x = object.getX();
    }
    CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}
于 2008-11-23T01:13:08.440 回答
0

不是与平台无关的建议,但过去我在单元测试期间调用了 CRT 的堆检查函数,以验证在测试(或者可能是整个测试集)结束时分配的内存是否比开始。您可能还可以对平台的仪器执行类似的操作,以检查句柄计数等。

于 2008-11-23T01:21:09.470 回答