【问题标题】:How to follow TDD for testing failure on this malloc wrapper?如何在此 malloc 包装器上遵循 TDD 测试失败?
【发布时间】:2012-09-20 09:56:18
【问题描述】:

我正在试验 TDD 和 C。我想按照 TDD 方法编写一个简单的 malloc 包装器。我正在尝试遵循 Bob Martin 的 TDD 三大定律

  1. 不要编写生产代码,除非是为了使失败的单元测试通过。
  2. 不要编写超过足以导致失败的单元测试,构建失败就是失败。
  3. 不要编写超过足以通过一个失败的单元测试的生产代码。

这是我到目前为止的代码:

    J_STATUS MemAlloc(long Size, void **OutPtr) {
        J_STATUS ReturnStatus;
        void *Ptr;

        Ptr = NULL;

        if (Size >= 0) {
            Ptr = malloc(Size);
            *OutPtr = Ptr;
            ReturnStatus = SUCCESS;
            //TODO controllare malloc error
        } else {
            ReturnStatus = ERROR;
        }
        return ReturnStatus;
    }

这些是我的测试(我正在使用 Unity 测试框架):

    #include "../unity/unity.h"
    #include "../src/jMem.h"
    #include "stdlib.h"

    static int *ptr;
    static J_STATUS Result;
    static long Size;
    static long Count;

    void setUp() {
        ptr = NULL;
        Size = 10;
        Count = 5;
    }

    void tearDown() {
        if (ptr != NULL) {
            free(ptr);
        }
    }

    void test_MemAllocShouldAllocateMemoryAndReturnSuccess(void) {
        Result = MemAlloc(Size, (void **) &ptr);
        TEST_ASSERT_EQUAL(SUCCESS, Result);
        TEST_ASSERT_NOT_NULL(ptr);

    }

    void test_MemAllocShouldReturnErrorIfSizeIsNegative(void) {
        Size = -4;

        Result = MemAlloc(Size, (void **) &ptr);

        TEST_ASSERT_EQUAL(ERROR, Result);
    }

现在如何为失败的 malloc 编写测试?我知道我可以链接不同版本的 malloc 或者我可以编写一个宏来使 malloc 失败,但这应该只适用于这个测试,而在另一个我想使用真正的 malloc。

有什么想法吗? 谢谢

【问题讨论】:

  • 为 malloc 引入一个接缝。在 c#/java 中,这是 MemoryManager.Allocate(size) 中的接口。如果插入调用类,则 MemoryManager 的实现。这允许您交换可以控制的兼容实现以模拟分配失败..例如在 MockMemoryManager 中引发 OutOfMemoryException。在 C 中,我认为完成此操作的一种方法是使用函数指针而不是接口。相同的概念。

标签: c testing tdd malloc


【解决方案1】:

要替换malloc(),您可以使用如下函数指针:

void* (*pMalloc)(size_t size)

然后你使用pMalloc(some size) 而不是malloc(some size)

如果您将指针设置为指向malloc(),如pMalloc = &malloc;,您将使用真正的malloc()。如果您将其设置为指向您自己的函数,那么您将使用您的该函数。

在您的函数中,您可以通过返回 NULL 来模拟 malloc() 故障。如果测试需要,您也可以从中调用malloc()

同样,您可以替换自己的free()

在这些替代函数中,您可以记录正在发生的事情。

例如,如果你将LONG_MAX 传递给你的包装函数(我假设它仍然将大小作为long 参数),你的假malloc() 可以记录它实际收到的大小。在测试中,您可以注意到LONG_MAX 已从包装器传播到malloc(),为(size_t)LONG_MAX,如果size_tlong 短(就位数而言),您可以检测到差异通过比较值 (LONG_MAX != (size_t)LONG_MAX)。

您还可以记录您的假 malloc() 返回的指针值(从自身内部),并将其与包装器返回的值进行比较,看看它们是否不同。

您可以进一步阐述这个想法以提出更多测试(例如,包装器返回一个非 NULL 值,但根本不调用假的 malloc()(因此是真正的)。

在执行所有这些操作时,您不必登录到文件或 stdout。您可以为此目的专用一个数据结构,然后对其进行检查。

【讨论】:

    【解决方案2】:

    也许这是您不需要测试的边缘情况。只写代码。 :-)

    另一方面,由于我们都喜欢练习良好的 TDD,另一种选择是将您的 malloc 调用提升为函数参数。作为函数指针,可以在一次测试中传递正则malloc的地址,在错误测试用例中传递malloc_that_errors的地址。

    请注意,它弄脏了包装器的界面,我个人不喜欢这种做法。它打破了你想要的抽象,我不想在任何地方传递malloc。鉴于此,我会回去不测试它。 :-D

    希望有帮助!

    布兰登

    【讨论】:

      【解决方案3】:

      当你的 malloc 包装器使用 long 而不是 size_t 作为 size 参数时,它不是很有说服力。

      您应该能够通过分配非常大量的内存使其失败,使用size_t 并找到最大值。大多数真实的malloc()s 不会成功分配它,所以它们会失败。

      当然,这仍然是一种相当假的让它失败的方法,我想唯一合适的方法是提供挂钩,以便您可以替换不同的malloc()

      【讨论】:

      • 谢谢。我在 D. Hanson 的书“C 接口和实现”(我的包装器受到启发)中读到了使用 long 而不是 size_t 的内容。我将尝试通过 malloc 大尺寸并对其进行测试。
      • 不起作用:分配大量内存不会使malloc失败。
      • 对于未来的 Google 员工,我来到这里是因为我正是这样做的:在 malloc 包装器中投入大量内存以使其失败。结果失败,但不是我预期的。我的错误处理代码从未运行过,因为原始malloc 失败,首先来自mmap 的错误。所以这种方法行不通。正如@AlexeyFrunze 所说,模拟malloc 的结果可能更好。
      【解决方案4】:

      老问题,但我觉得,根据您的编译器,缺少一个答案。 GNU LD 有一个--wrap symbol 选项,可让您在链接时覆盖某些函数。

      void *
      __wrap_malloc (int c)
      {
        printf ("malloc called with %ld\n", c);
        return __real_malloc (c);
      }
      

      与此代码一样,对malloc 的每次调用都将替换为对__wrap_malloc 的调用,如果您想调用真正的malloc,可以通过__real_malloc 获得。

      我不知道其他链接器是否也可以使用此功能,但对于 GCC,这是一个不错的选择。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-13
        • 1970-01-01
        • 2010-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多