你将如何对内存分配器进行单元测试?

时间:2008-09-23 06:36:32

标签: c++ c unit-testing testing

今天有很多人将单元测试作为发展的基础。这甚至可能适用于强大的面向算法的例程。但是,如何进行单元测试,例如内存分配器(想想malloc()/ realloc()/ free())。生成满足指定接口的工作(但绝对无用)的内存分配器并不难。但是如何提供绝对需要的单元测试功能的适当上下文,但不是合同的一部分:合并空闲块,在下一次分配时重用空闲块,将过多的空闲内存返回给系统,断言分配策略(例如真正受到尊重等等。

我的经验是断言,即使复杂和耗时(例如遍历整个空闲列表以检查不变量),工作要少得多,并且比单元测试更可靠,尤其是。编码复杂的,时间相关的算法时。

有什么想法吗?

8 个答案:

答案 0 :(得分:11)

高度可测试的代码的结构往往与其他代码不同。

您描述了分配器要执行的几项任务:

  • 合并免费积木
  • 接下来重用免费区块 分配
  • 将多余的可用内存返回给 系统
  • 断言分配政策 (例如,先适合)真的受到尊重

虽然您可能会将分配代码编写为非常耦合,但在一个函数体内执行其中的一些操作时,您也可以将每个任务分解为可测试块的代码。这几乎是你可能习惯的倒置。我发现可测试代码往往非常透明,并且由更小的部分构建。

接下来,我要说的是,在任何类型的自动化测试中,优于没有自动化测试。我肯定会更专注于确保你的测试做一些有用的事情而不是担心如果你正确使用了模拟,你是否确保它被正确隔离以及它是否是真正的单元测试。这些都是令人钦佩的目标,有望使99%的测试更好。另一方面,请使用常识和最佳工程判断来完成工作。

没有代码示例,我认为我不能更具体。

答案 1 :(得分:10)

如果有任何逻辑,可以进行单元测试。
如果您的逻辑涉及做出决策并调用OS /硬件/系统API,则伪造/模拟设备相关的调用并对您的逻辑进行单元测试,以验证是否在给定的一组前置条件下做出了正确的决策。在单元测试中遵循Arrange-Act-Assert三元组 断言不能替代自动化单元测试。他们不告诉你哪个场景失败了,他们在开发过程中没有提供反馈,他们不能用来证明代码满足所有规范。

非模糊更新: 我不知道确切的方法调用..我想我会“滚动自己的” 假设您的代码检查当前条件,做出决定并根据需要调用操作系统。让我们说你的操作系统调用是(你可能还有更多):

void* AllocateMemory(int size); 
bool FreeMemory(void* handle);
int MemoryAvailable();

首先将其转换为界面I_OS_MemoryFacade。创建此接口的实现以实际调用OS。现在让您的代码使用此接口 - 您现在已将代码/逻辑与设备/操作系统分离。接下来在你的单元测试中,你使用一个模拟框架(它的目的是为你提供一个指定接口的模拟实现。然后你可以告诉模拟框架期望这些调用,这些调用params并在它们生成时返回。在测试结束时,你可以要求模拟框架验证是否满足所有期望。(例如,在此测试中,AllocateMemory应该被调用三次,10,30,50作为params然后是3个FreeMemory调用。检查MemoryAvailable是否返回初始值。)
由于您的代码依赖于接口,因此它不知道实际实现与您用于测试的伪/模拟实现之间的区别。 Google提供“模拟框架”以获取更多信息。

答案 2 :(得分:1)

两件事都有自己的位置。使用单元测试检查接口的行为是否符合预期,并使用断言来检查合同是否得到遵守。

答案 3 :(得分:1)

您可能还希望包括性能测试,压力测试等。它们不是单元测试,因为它会测试整个事情,但它们在内存分配器的情况下非常有价值。

单元测试不排除这些类型的测试。最好同时使用这两种测试。

答案 4 :(得分:1)

我还认为单元测试被高估了。它们有用,但真正提高程序质量的是审查它。另一方面,我真的很喜欢断言,但它们并没有取代单元测试。

我不是在谈论同行评审,而是简单地重读你所写的内容,可能是在用调试器踩过它并检查每一行是否完成它应该做的事情时,天空火箭的软件质量。

我建议使用“高级”单元测试来测试一大块功能而不是一个微小的方法调用。后者倾向于使任何代码更改都非常痛苦和昂贵。

答案 5 :(得分:0)

我个人认为大多数单元测试都像别人的愿望而不是我的。我认为任何单元测试都应该像普通程序一样编写,除了它除了测试库/算法或代码的任何部分之外什么都不做。

我的单元测试通常不使用CUnitCppUnit等工具和类似软件。 我创建自己的测试。例如,不久前我需要在通常情况下测试容器的新实现是否存在内存泄漏。单元测试对提供一个不错的测试没有帮助。相反,我创建了自己的分配器,并且在某些(可调整的)分配数量之后无法分配内存,以查看我的应用程序是否在这些情况下有内存泄漏(并且它有:))。

如何通过单元测试进行此操作?通过更多努力使您的代码适合单元测试“模式”。

因此我强烈建议不要每次都使用单元测试,因为它是“时髦的”,但只有在将它们与您要测试的代码集成时才非常容易。

答案 6 :(得分:0)

单元测试不仅仅是为了确保您的代码正常运行。它也是一种非常好的设计方法。要使测试有用,如前所述,代码需要尽可能地解耦,例如在需要时使用接口。

我并不总是首先编写测试,但是如果我在开始学习时遇到困难,我会写一个简单的测试,尝试设计并从那里开始。此外,良好的单元测试可作为良好的文档。在我需要查看如何使用特定类或类似的工作时,我会查看它的单元测试。

请记住,单元测试是不是集成测试。单元测试确实有其局限性,但总的来说我认为这是一个非常好的工具,可以知道如何正确使用。

答案 7 :(得分:0)

因此,您遇到了一个问题,即您的分配器被测试框架使用,这可能会在您尝试测试时导致分配器状态出现问题。考虑为分配器函数添加前缀(请参阅dlmalloc)。你写了

prefix_malloc();
prefix_free();

然后

#ifndef USE_PREFIX
#define prefix_malloc malloc
#define prefix_free free
#endif

现在,设置构建系统以使用-DUSE_PREFIX编译库的版本。编写单元测试以调用prefix_malloc和prefix_free。这使您可以将分配器的状态与系统分配器的状态分开。

如果您正在使用sbrk并且系统分配器使用sbrk,那么如果任一分配器假定它完全控制断点,则可能会遇到错误的时间。在这种情况下,您可能希望链接另一个分配器,您可以将其配置为仅使用mmap,以便分配器可以具有断点。