你会如何单元测试一个内存分配器?

问题描述:

今天有很多人将单元测试作为发展的面包和黄油销售。这甚至可能适用于强大的面向算法的例程。然而,你将如何单元测试,例如,内存分配器(think malloc()/ realloc()/ free())。生成满足指定接口的工作(但绝对无用)内存分配器并不困难。但是,如何为单元测试功能提供合适的上下文,这是绝对需要的,但不是合同的一部分:合并空闲块,在下一次分配时重用空闲块,向系统返回多余的空闲内存,断言分配策略(例如首先适合)真的被尊重,等等。你会如何单元测试一个内存分配器?

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

有什么想法?

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

你描述你想要的分配做几个任务:

  • 凝聚空闲块
  • 旁边 分配
  • 返回多余的空闲内存的 系统
  • 断言重用的空闲块分配政策 (例如第一次适用)确实受到尊重

尽管您可能会将您的分配代码编写为非常耦合,就像在一个函数体内执行其中的几项操作一样,您也可以将每个任务分解为可测试块的代码。这几乎是你可能习惯的反转。我发现可测试代码往往非常透明,并且从更小的部分构建。

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

没有代码示例我不认为我可以更具体。

这两件事都有它们的位置。使用单元测试检查接口的行为是否符合预期,并使用断言来检查合同是否受到尊重。

如果在那里有任何逻辑,它可以进行单元测试。
如果您的逻辑涉及决策并调用操作系统/硬件/系统API,假设/模拟出与设备相关的调用,并对您的逻辑进行单元测试,以验证在给定的前置条件下是否做出正确决策。在单元测试中遵循Arrange-Act-Assert三元组。
断言不能替代自动化单元测试。他们不会告诉你哪个方案失败,他们在开发过程中不提供反馈,他们不能用来证明代码在其他方面符合所有规范。

非模糊更新: 我不知道确切的方法调用..我想我会“推出自己的” 比方说,你的代码审视了当前的条件下,作出决定,并向电话OS根据需要。比方说,你的OS电话是(可能有更多):

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

首先把它变成一个接口,I_OS_MemoryFacade。创建此接口的实现,以实际调用OS。现在让你的代码使用这个接口 - 你现在已经从设备/操作系统中解耦了你的代码/逻辑。接下来在你的单元测试中,你使用一个模拟框架(它的目的是给你一个指定接口的模拟实现,然后你可以告诉模拟框架期望这些调用,这些参数并返回这个时候在测试结束时,您可以要求模拟框架验证是否满足所有期望(例如,在此测试中,应以10,30,50作为参数,随后3次FreeMemory调用将AllocateMemory调用三次。检查MemoryAvailable是否返回初始值。)
因为你的代码依赖于一个接口,所以它不知道真正的实现和你用于测试的虚拟/模拟实现之间的区别 Google out'mock frameworks'欲了解更多信息,请点击这里

+0

您能否介绍一下如何编写单元测试的更多细节。你提倡他们很多,但我读了这个问题,然后回答了你的答案,我仍然不明白你提出的是什么。 – 2008-09-23 07:08:50

我个人认为大部分的单元测试都是像别人的愿望而不是我的。我认为任何单元测试都应该像正常程序一样书写,除了测试库/算法或代码的任何部分之外,它不会做任何事情。

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

这怎么能通过单元测试?更多的努力使你的代码适合单元测试“模式”。

所以我强烈建议不要每次都使用单元测试,因为它是“新潮”的,但只有当它们很容易与你喜欢测试的代码集成在一起时。

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

单元测试不排除这些类型的测试。最好是有他们两个。

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

我不是在谈论同行评审,而是简单地重读你写的内容,可能在用调试器遍历它并检查每条线做它应该做什么的时候,将会增加软件的质量。

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

单元测试不仅仅是为了确保你的代码的工作。这也是一个非常好的设计方法。为了使测试有用,如前所述,代码需要尽可能地分离,比如在需要的地方使用接口。

我并不总是先写测试,但很多时候如果我在开始使用某些东西时遇到困难,我会写一个简单的测试,试验设计并从那里开始。另外,良好的单元测试可以作为很好的文档。在工作中,当我需要了解如何使用特定的类或类似的东西时,我会查看它的单元测试。

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

因此,当您尝试测试时,遇到一个问题,您的分配器被测试框架使用,这可能会导致分配器状态出现问题。考虑为分配器功能添加前缀(请参阅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,那么如果任何一个allocator假定它完全控制了断点,则可能会有一段糟糕的时间。在这种情况下,你想链接另一个分配器,你可以配置它只使用mmap,因此你的分配器可以有断点。