我们将如何对内存分配器进行单元测试?
今天有很多人将单元测试作为发展的基础。这甚至可能对高度基于算法的例程起作用。但是,如何对内存分配器进行单元测试(请考虑malloc()/ realloc()/ free())。产生满足指定接口的工作(但绝对无用)的内存分配器并不难。但是,如何为绝对需要但不是合同一部分的单元测试功能提供适当的上下文:合并空闲块,在下一次分配中重用空闲块,将过多的空闲内存返回给系统,声明分配策略(例如首先适合)确实受到尊重,等等。
我的经验是,即使是复杂且费时的断言(例如遍历整个自由列表以检查不变量)也要比单元测试少得多,并且更可靠。在编写复杂的,与时间有关的算法时。
有什么想法吗?
解决方案
这两件事都有自己的位置。使用单元测试检查接口的行为是否符合预期,并使用断言检查合同是否得到遵守。
如果其中有任何逻辑,可以对其进行单元测试。
如果逻辑涉及制定决策并调用OS /硬件/系统API,请伪造/模拟与设备相关的调用,并对逻辑进行单元测试,以验证是否在给定的一组前提条件下做出了正确的决策。在单元测试中遵循Arrange-Act-Assert三合会。
断言不能替代自动单元测试。他们没有告诉我们哪种情况失败了,他们在开发过程中没有提供反馈,也不能用来证明代码满足了所有规范。
不明确的更新:
我不知道确切的方法调用..我想我会"自己动手"
void* AllocateMemory(int size); bool FreeMemory(void* handle); int MemoryAvailable();
假设代码检查了当前情况,做出了决定并根据需要对操作系统进行了调用。可以说OS调用是(我们可能还有更多):
首先将其转换为接口" I_OS_MemoryFacade"。创建此接口的实现,以对操作系统进行实际调用。现在,使代码使用此接口,我们现在已将代码/逻辑与设备/ OS分离。接下来,在单元测试中,我们将使用模拟框架(其目的是为我们提供指定接口的模拟实现。然后,我们可以使用这些参数告诉模拟框架期望进行这些调用,并在它们被返回时返回在测试结束时,我们可以要求模拟框架验证是否满足所有期望(例如,在该测试中,应将AllocateMemory调用三次,并以10、30、50作为参数,再调用3次FreeMemory。如果MemoryAvailable返回初始值。)
由于代码取决于接口,因此它不知道实际实现与用于测试的假/模拟实现之间的区别。
Google提供了"模拟框架"以获取更多信息。
我个人认为,大多数单元测试都像别人的愿望一样,而不是我的愿望。我认为任何单元测试都应该像普通程序一样编写,除了它除了测试库/算法或者代码的任何部分外不会做任何事情。
我的单元测试通常不使用CUnit,CppUnit和类似软件之类的工具。
我创建自己的测试。例如不久前,我需要在通常情况下测试容器的全新实现以防止内存泄漏。单元测试不足以提供良好的测试。相反,我创建了自己的分配器,并使其在经过一定数量(可调)的分配后无法分配内存,以查看我的应用程序在这种情况下是否存在内存泄漏(并且它具有:))。
如何通过单元测试来做到这一点?用更多的精力使代码适合单元测试"模式"。
因此,我强烈建议不要每次都使用单元测试,只是因为它很"时髦",而不仅仅是在将它们与我们要测试的代码真正集成起来时才使用。
高度可测试的代码往往与其他代码的结构不同。
- 合并空闲块
- 在下一个分配中重用空闲块
- 将多余的可用内存返回给系统
- 断言分配政策(例如first-fit)确实受到尊重
我们描述了希望分配器执行的几个任务:
尽管我们可能编写分配代码时会非常耦合,例如在一个函数体内执行多个操作时,我们还可以将每个任务分解为可测试的代码块。这几乎是我们可能习惯的一种反转。我发现可测试的代码往往是非常透明的,并且由更多的小片段组成。
接下来,我要说的是,在某种程度上,任何类型的自动化测试总比没有自动化测试要好。我肯定会更加着重于确保测试有用,而不用担心是否正确使用了模拟程序,是否已正确隔离模拟程序以及真正的单元测试。这些都是令人钦佩的目标,有望使99%的测试变得更好。另一方面,请运用常识和最佳工程判断来完成工作。
没有代码示例,我想我不会更加具体。
我们可能还希望包括性能测试,压力测试等。它们不是单元测试,因为它可以测试整个事情,但是对于内存分配器来说,它们非常有价值。
单元测试不排除此类测试。最好同时拥有它们。
我还认为单元测试被高估了。它们有其用处,但是真正提高程序质量的是对其进行审查。另一方面,我真的很喜欢断言,但是它们不能代替单元测试。
我不是在谈论同行评审,而是简单地重新阅读我们编写的内容,可能是在通过调试器逐步检查它并检查每一行是否按预期执行时,都会使软件质量飞涨。
我建议使用"高级"单元测试来测试功能块,而不是微小的方法调用。后者往往使任何代码更改都非常痛苦和昂贵。
单元测试不仅仅是确保代码正常工作。这也是一种非常好的设计方法。如前所述,为了使测试有用,代码需要尽可能地解耦,例如在需要的地方使用接口。
我不一定总是先编写测试,但是如果我在入门方面遇到困难,通常会编写一个简单的测试,尝试设计并从那里开始。而且,良好的单元测试可以作为良好的文档。在工作中,当我需要查看如何使用特定的类或者类似的类时,我会看一下它的单元测试。
段落数量不匹配