-
10-07-2019 - |
题
单元测试析构函数有什么好方法吗?比如说我有一个这样的类(人为的)例子:
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 TEST或打破封装来混乱我的hpp文件?我看到的主要问题是很难判断x是否真的被删除了,特别是因为在调用析构函数时对象超出了范围。
解决方案
依赖注入可能有些话要说。该构造函数不是在构造函数中创建一个对象(在这种情况下是一个int,而是在非设计的情况下,更可能是用户定义的类型),而是将该对象作为参数传递给构造函数。如果稍后创建对象,则将工厂传递给X的构造函数。
然后,当您进行单元测试时,传入模拟对象(或创建模拟对象的模拟工厂),析构函数记录它已被调用的事实。如果不是,则测试失败。
当然你不能模拟(或以其他方式替换)内置类型,所以在这种特殊情况下它没有用,但是如果用界面定义对象/工厂那么你可以。
正如其他人所说,检查单元测试中的内存泄漏通常可以在更高级别进行。但是只检查 a 析构函数是否被调用,它不能证明正确的析构函数被调用。所以它不会例如抓住一个缺失的<!>“虚拟<!>”; x成员类型的析构函数声明(再次,如果它只是一个int则不相关)。
其他提示
我认为您的问题是您当前的示例无法测试。既然你想知道x
是否被删除了,你真的需要能够用模拟替换X
。对于一个int来说这可能有点OTT,但我想在你真实的例子中你还有其他类。为了使其可测试,int
构造函数需要请求实现<=>接口的对象:
template<class T>
class X
{
T *x;
public:
X(T* inx)
: x(inx)
{
}
// etc
};
现在,模拟<=>的值变得很简单,而mock可以处理正确销毁的检查。
请不要注意那些说你应该打破封装或诉诸可怕的黑客以便产生可测试代码的人。虽然经过测试的代码确实比未经测试的代码更好,但可测试的代码是最好的,并且它总是能够产生更清晰的代码,并且具有更少的hack和更低的耦合。
我倾向于使用<!>“以任何必要的方式<!>”;测试方法。如果它需要测试,我愿意泄露抽象,打破封装和破解...因为测试代码比漂亮的代码更好。我经常将这些方法命名为VaildateForTesting或OverrideForTesting,以明确说明破坏封装只是为了测试。
我不知道在C ++中有什么其他方法可以让析构函数调用单例来注册它已被销毁。我已经提出了一种方法,可以使用弱引用在C#中执行与此类似的操作(我没有违反使用此方法的封装或抽象)。我没有足够的创造力来对C ++进行类比,但你可能会。如果有帮助的话,很好,如果没有,抱歉。
http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx
在示例中,定义并检测您自己的全局new和delete。
为了避免#ifdefs,我让测试类成为朋友。您可以根据需要设置/保存/获取状态以验证呼叫结果。
与提出问题的人无关,但可能对阅读此内容的其他人有所帮助。在求职面试中我被问到了类似的问题。
假设内存有限,您可以尝试以下方法:
- 分配内存,直到分配失败并显示内存不足消息(在对析构函数运行任何相关测试之前)并在运行测试之前保存可用内存的大小。
- 运行测试(调用构造函数并根据需要在新实例上执行某些操作)。
- 运行析构函数。
- 再次运行分配部分(如步骤1) 如果你可以在运行测试之前分配完全相同的内存,那么析构函数就可以正常工作。 醇>
当你的记忆力有限时,这种方法可以(合理地)起作用,否则这似乎是不合理的,至少在我看来是这样。
某些编译器将在调试模式下使用已知模式覆盖已删除的内存,以帮助检测对悬空指针的访问。我知道Visual C ++曾经使用过0xDD,但我有一段时间没用过它。
在您的测试用例中,您可以存储x的副本,让它超出范围并确保* x == 0xDDDDDDDD:
void testDestructor()
{
int *my_x;
{
X object;
my_x = object.getX();
}
CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}
不是平台无关的建议,但过去我在单元测试期间调用CRT的堆检查函数,以验证在测试结束时没有分配更多内存(或者可能是一整套测试)比开始。您也可以使用平台的工具执行类似操作,检查句柄计数等。