0

我有一个基于不透明结构的 API 编写的库。使用不透明结构有很多好处,我对此非常满意。

现在我的 API 在规范方面已经稳定了,我想写一个完整的单元测试电池,以确保在发布之前有一个坚实的基础。

我的担心很简单,你如何基于主要目标是隐藏内部逻辑的不透明结构对 API 进行单元测试?

例如,让我们以一个非常简单的对象,一个带有非常简单测试的数组为例:

WSArray a = WSArrayCreate();
int foo = 5;
WSArrayAppendValue(a, &foo);
int *bar = WSArrayGetValueAtIndex(a, 0);

if(&foo != bar)
    printf("Eroneous value returned\n");
else
    printf("Good value returned\n");

WSRelease(a);

当然,这会测试一些事实,比如数组实际上是用 1 个值执行的,但是当我编写单元测试时,至少在 C 中,我通常会将我的数据结构的内存占用与已知状态进行比较。

在我的示例中,我不知道数组的某些内部状态是否损坏。

你会怎么处理?我真的很想避免在实现文件中添加仅用于单元测试的代码,我非常强调模块的松散耦合,并且将单元测试注入实现对我来说似乎相当有侵略性。

我的第一个想法是将实现文件包含到我的单元测试中,将我的单元测试静态链接到我的库。

例如:

#include <WS/WS.h>
#include <WS/Collection/Array.c>

static void TestArray(void)
{
    WSArray a = WSArrayCreate();
    /* Structure members are available because we included Array.c */
    printf("%d\n", a->count);     
}

这是一个好主意吗?

当然,单元测试不会从封装中受益,但它们在这里是为了确保它实际工作。

4

3 回答 3

2

我只会测试 API,并专注于测试每一个可能的极端情况。

我可以看到人们对检查内存结构是否符合您的期望感兴趣。如果你这样做,你会将测试与实现的细节紧密耦合,我认为会产生大量的长期维护。

我的想法是 API 是合同,如果你实现了,那么你的代码就可以工作了。如果您稍后更改实现,那么您可能需要知道的一件事是维护合同。您的单元测试将验证这一点。

于 2009-12-27T07:54:48.000 回答
0

你的单元测试不应该依赖于他们正在测试的代码的内部细节。你最初的例子实际上是一个很好的测试。它做一件事,然后验证对象的状态是否符合预期。

当然,您还想创建测试来验证 API 其他部分的行为。例如,在数组案例中,如果在添加和删除项目后正确报告了数组,您希望有测试用例来验证长度。

编写依赖于与已知良好内存快照的精确匹配的单元测试通常是一个非常糟糕的主意,因为每次实现更改都会导致测试失败。如果您决定使用基于快照的测试,请确保可以轻松地重新生成“已知良好”的快照。

于 2009-12-27T08:13:49.443 回答
0

我建议将单元测试分为白盒黑盒单元测试。白盒测试侧重于 API 接口和结果的正确性,而黑盒测试侧重于内部。

为了方便这一点,我使用了一个私有头文件(例如example_priv.h),以及一个#ifdef TESTING其他内部/私有的函数原型。因此,您可以出于单元测试的目的使用内部函数,而无需在一般情况下公开它们。

这种方法的唯一损失是失去了static在源文件中显式标记内部函数的能力。

我希望这会有所帮助。

于 2010-03-28T16:38:47.547 回答