单元测试析构函数?

发布于 2024-07-09 10:49:05 字数 388 浏览 6 评论 0原文

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

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 测试弄乱我的 hpp 文件或破坏封装? 我看到的主要问题是很难判断 x 是否真的被删除,特别是因为在调用析构函数时该对象超出了范围。

Is there any good way to unit test destructors? Like say I have a class like this (contrived) example:

class X
{
private:
    int *x;

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

    ~X()
    {
         delete x;
    }

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

Is there any good way to unit test this to make sure x gets deleted without cluttering up my hpp file with #ifdef TESTs or breaking encapsulation? The main problem that I'm seeing is that it's difficult to tell if x really got deleted, especially because the object is out of scope at the time the destructor is called.

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

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

发布评论

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

评论(7

我要还你自由 2024-07-16 10:49:05

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

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

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

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

There may be something to be said for dependency injection. Instead of creating an object (in this case an int, but in a non-contrived case more likely a user-defined type) in its constructor, the object is passed as a parameter to the constructor. If the object is created later, then a factory is passed to the constructor of X.

Then when you're unit testing, you pass in a mock object (or a mock factory which creates mock objects), and the destructor records the fact that it has been called. The test fails if it isn't.

Of course you can't mock (or otherwise replace) a builtin type, so in this particular case it's no good, but if you define the object/factory with an interface then you can.

Checking for memory leaks in unit tests can often be done at a higher level, as others have said. But that only checks that a destructor was called, it doesn't prove that the right destructor was called. So it wouldn't e.g. catch a missing "virtual" declaration on the destructor of the type of the x member (again, not relevant if it's just an int).

夏天碎花小短裙 2024-07-16 10:49:05

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

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

  // etc
};

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

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

I think your problem is that your current example isn't testable. Since you want to know if x was deleted, you really need to be able to replace x with a mock. This is probably a bit OTT for an int but I guess in your real example you have some other class. To make it testable, the X constructor needs to ask for the object implementing the int interface:

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

  // etc
};

Now it becomes simple to mock in the value for x, and the mock can handle checking for correct destruction.

Please pay no attention to the people who say you should break encapsulation or resort to horrible hacks in order to result in testable code. While it is true that tested code is better than untested code, testable code is the best of all and it always results in clearer code with fewer hacks and lower coupling.

‘画卷フ 2024-07-16 10:49:05

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

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

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

I tend to go with a "By any means necessary" approach to testing. If it needs a test, I am willing to leak abstractions, break encapsulation, and hack... because tested code is better than pretty code. I will often name the methods that break this up something like VaildateForTesting or OverrideForTesting to make it clear that the breach of encapsulation is meant for testing only.

I don't know of any other way to do this in C++ than having the destructor call into a singleton to register that it has been destroyed. I have come up with a method for doing something similar to this in C# using a weak reference (I don't violate encapsulation or abstractions with this approach). I am not creative enough to come up with an analogy to C++, but YOU might be. If it helps, great, if not, sorry.

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

半仙 2024-07-16 10:49:05

在示例中,定义并检测您自己的全局新建和删除。

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

In the example, define and instrument your own global new and delete.

To avoid #ifdefs, I've make test classes friends. You can set/save/get state as required to verify the results of a call.

ゝ偶尔ゞ 2024-07-16 10:49:05

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

假设内存有限,您可以尝试以下方法:

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

当你的内存有限时,这种方法(合理)有效,否则它看起来不合理,至少在我看来是这样。

It won't be relevant to the guy who asked the question but might be helpful to others who reads this. I was asked a similar question in a job interview.

Assuming the memory is limited, you can try this method:

  1. Allocate memory until the allocation fails with out of memory message (before running any relevant test to the destructor) and save the size of the memory available before running the test.
  2. Run the test (call the constructor and do some actions as you wish on the new instance).
  3. Run the destructor.
  4. Run the allocation part again (as in step 1)
    if you can allocate exactly the same memory as you manage to allocate before running the test, the destructor works fine.

This method works (reasonably) when you have small limited memory, otherwise it seems unreasonable, at least for my opinion.

鹿港小镇 2024-07-16 10:49:05

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

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

void testDestructor()
{
    int *my_x;
    {
        X object;
        my_x = object.getX();
    }
    CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}

Some compilers will overwrite deleted memory with a known pattern in debug mode to help detect access to dangling pointers. I know Visual C++ used to use 0xDD, but I haven't used it in a while.

In your test case, you can store a copy of x, let it go out of scope and make sure that *x == 0xDDDDDDDD:

void testDestructor()
{
    int *my_x;
    {
        X object;
        my_x = object.getX();
    }
    CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}
若水微香 2024-07-16 10:49:05

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

Not a platform agnostic suggestion, but in the past I have made calls into the CRT's heap checking functions during unit testing, to verify that there is no more memory allocated at the end of a test (or perhaps a whole set of tests) than the start. You might also be able to do something similar with your platform's instrumentation, to check on handle-counts, etc.

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