C语言 如何对内存分配器进行单元测试?

93ze6v8z  于 2023-10-16  发布在  其他
关注(0)|答案(8)|浏览(149)

今天有很多人把单元测试作为开发的面包和黄油。这甚至可能适用于强算法导向的例程。但是,如何对内存分配器进行单元测试(想想malloc()/realloc()/free())。生成一个满足指定接口的可用(但绝对无用)内存分配器并不难。但是,如何为单元测试功能提供适当的上下文,这是绝对需要的,但不是合同的一部分:合并空闲块,在下一次分配时重用空闲块,将多余的空闲存储器返回给系统,Assert分配策略(例如,第一个适合)真的是尊重,等等。
我的经验是,Assert,即使复杂和耗时(例如,遍历整个自由列表来检查不变量)比单元测试工作量少得多,而且更可靠,特别是。当编码复杂的、时间依赖的算法时。
有什么想法吗?

sf6xfgos

sf6xfgos1#

就我个人而言,我发现大多数单元测试都像是别人的愿望,而不是我的愿望。我认为任何单元测试都应该像普通程序一样编写,除了它不做任何事情,除了测试库/算法或代码的任何部分。
我的单元测试通常不使用像CUnitCppUnit和类似软件这样的工具。我创建自己的测试。例如,不久前,我需要测试一个容器的新实现,在通常情况下是否存在内存泄漏。单元测试不足以提供一个好的测试。相反,我创建了自己的分配器,并使其在一定(可调整)数量的分配后无法分配内存,以查看我的应用程序在这些情况下是否有内存泄漏(它有:))。
如何通过单元测试实现这一点?更多的努力使您的代码适合单元测试“模式”。
因此,我强烈建议不要每次都使用单元测试,只是因为它是“时髦”的,而只是当它真的很容易与你喜欢测试的代码集成时。

pbpqsu0x

pbpqsu0x2#

单元测试不仅仅是为了确保你的代码工作。这也是一种很好的设计方法。如前所述,为了使测试有用,代码需要尽可能地解耦,比如在需要的地方使用接口。
我并不总是先写测试,但通常如果我在开始做某件事时遇到困难,我会写一个简单的测试,对设计进行实验,然后从那里开始。同样,好的单元测试也是好的文档。在工作中,当我需要了解如何使用一个特定的类或类似的类时,我会查看它的单元测试。
请记住,单元测试不是集成测试。单元测试确实有其局限性,但总的来说,我认为这是一个非常好的工具,可以知道如何正确使用。

y0u0uwnf

y0u0uwnf3#

所以你遇到了一个问题,你的分配器被测试框架使用,这可能会在你尝试测试时导致分配器状态的问题。考虑给分配器函数添加前缀(参见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,以便您的分配器可以拥有断点。

64jmpszr

64jmpszr4#

高度可测试的代码往往与其他代码的结构不同。
您描述了希望分配器执行的几项任务:

  • 合并自由块
  • 在下一次分配时重用空闲块
  • 将多余的空闲内存返回给系统
  • Assert分配策略(例如,第一个适合)真的是尊重

虽然您可能会将分配代码编写得非常耦合,例如在一个函数体内执行其中的几项任务,但您也可以将每个任务分解为可测试的代码块。这几乎是你可能习惯的倒置。我发现可测试的代码往往是非常透明的,并且是由更多的小片段构建的。
接下来,我想说的是,在合理范围内,任何类型的自动化测试都比没有自动化测试好。我肯定会更关注确保测试做一些有用的事情,而不是担心是否正确使用了mock,是否确保了它的正确隔离以及它是否是真正的单元测试。这些都是令人钦佩的目标,有望使99%的测试更好。另一方面,请使用常识和您最好的工程判断来完成工作。
如果没有代码示例,我想我不能更具体。

2nc8po8w

2nc8po8w5#

如果里面有逻辑,可以进行单元测试

如果你的逻辑涉及决策和调用操作系统/硬件/系统API,伪造/模拟设备相关调用,并对你的逻辑进行单元测试,以验证在给定的前提条件下是否做出了正确的决策。在您的单元测试中遵循“验证-行为-Assert”三步骤。
Assert不能代替自动化单元测试。它们不会告诉你哪个场景失败了,它们不会在开发过程中提供反馈,它们不能用来证明代码满足所有规范。

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

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

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

qlvxas9a

qlvxas9a6#

这两件事都有自己的位置。使用单元测试来检查接口是否按预期运行,使用Assert来检查合同是否得到遵守。

xiozqbni

xiozqbni7#

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

**单元测试并不排除这类测试。**最好两者都有。

im9ewurl

im9ewurl8#

我也认为单元测试被高估了。他们有他们的用处,但真正提高程序质量的是审查它。另一方面,我真的很喜欢Assert,但它们不能取代单元测试。
我不是在谈论同行评审,而是简单地重读你写的东西,可能的话,同时用调试器单步调试,检查每一行都做了它应该做的事情,这将使软件质量突飞猛进。
我建议使用“高级”单元测试来测试一大块功能,而不是一个很小的方法调用。后者往往使任何代码更改都非常痛苦和昂贵。

相关问题