嵌入式C开发人员的良好单元测试示例


20

下周,我将与我的部门进行有关单元测试和测试驱动开发的演讲。作为其中的一部分,我将展示一些我最近编写的代码中的真实示例,但我也想展示一些我将在演讲中编写的非常简单的示例。

我一直在网上寻找良好的例子,但一直在努力寻找任何特别适合我们开发领域的例子。我们编写的几乎所有软件都是在小型微控制器上运行的深层控制系统。只要您远离“底层”层,就有很多C代码很容易适用于单元测试(我将在PC上而不是在目标本身上谈论单元测试):直接对话的东西到微控制器外设。但是,我发现的大多数示例都倾向于基于字符串处理(例如出色的Dive Into Python罗马数字示例),并且由于我们几乎从未使用过字符串,因此这实际上并不适合(关于我们的代码通常使用的唯一库函数)是memcpymemcmpmemsetstrcat 或正则表达式不太正确)。

那么,问题就来了:请问有人可以提供一些很好的功能示例,这些功能可以用来在实时会话中演示单元测试吗?在我的观点(可能会发生变化)中,一个好的答案可能是:

  • 一个足够简单的功能,任何人(甚至只是偶尔写代码的人)都可以理解;
  • 看起来没有意义的函数(即计算奇偶校验或CRC可能比将两个数字相乘并添加随机常数的函数更好);
  • 一个足够短的函数,可以在一个人的房间里书写(我可能会利用Vim的许多剪贴板来减少错误……);
  • 该函数以数字,数组,指针或结构为参数,并返回相似的内容,而不是处理字符串。
  • 具有简单错误(例如>而不是>=)的函数易于插入,在大多数情况下仍然可以使用,但在某些特殊情况下会中断:易于通过单元测试进行识别和修复。

有什么想法吗?

尽管可能无关紧要,但是测试本身可能会使用Google Test Framework以C ++编写:我们所有的标头都已经包含了#ifdef __cplusplus extern "C" {包装器;到目前为止,这与我已经完成的测试效果很好。


以这里的“问题”为例,提出了将TDD出售给管理层的演示文稿,在我看来,这似乎很好地适合了所需的格式。OP似乎正在要求针对此问题的现有解决方案。
Technophile

Answers:


15

这是一个简单的函数,应该会生成len个字节的校验和。

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

它有一个fencepost错误:在for语句中,测试应为i < len

有趣的是,如果您将其应用于这样的文本字符串...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

您将获得“正确答案”!那是因为校验和的额外字节是零字符串终止符。因此,您可以将这个校验和函数放入代码中,甚至可以将其附带在代码中,并且永远不会注意到问题-也就是说,除非您开始将其应用于文本字符串以外的其他内容。

这是一个简单的单元测试,它将标记此错误(大多数情况下... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}

很好!这只是我希望得到的答案:谢谢。
DrAl 2011年

创建缓冲区时,该内存块中已经有垃圾,是否真的有必要使用随机数对其进行初始化?
蛇桑德斯

@SnakeSanders我会说是,因为您希望单元测试尽可能具有确定性。如果您使用的编译器恰好在开发人员计算机上放置了0,在测试计算机上放置了10,那么您将很难找到该错误。我确实认为,出于相同的原因,让它依赖于时间而不是固定的种子是一个坏主意。
安德鲁说莫妮卡

在单元测试中依赖非确定性的行为是一个坏主意。片状测试迟早会让您头痛...
sigy

2

如何实现像冒泡排序这样的排序功能呢?一旦排序功能开始工作,您就可以继续进行二进制搜索,这对于引入单元测试和TDD一样好。

排序和搜索取决于比较,这很容易出错。它还涉及交换指针,必须谨慎处理。两者都容易出错,所以请随意搞乱:)

其他一些想法:

  • 进行重构时,单元测试有很大帮助。因此,一旦您的冒泡排序成功了,您就可以将其更改为功能更强大的排序,例如qsort,并且测试仍应通过,证明您的新排序功能也可以正常工作。
  • 排序很容易测试,可以对结果进行排序,也可以不进行排序,这使其成为不错的选择。
  • 搜索相同;它要么存在,要么不存在。
  • 编写用于排序的测试会展开讨论,例如用于测试的输入类型(零元素,随机输入,重复项,巨大数组等)。

对于一个简单的错误,您是否有任何具体建议,以表明测试如何使生活更轻松?
DrAl 2011年

@DrAl:以此更新了我的答案。
马丁·威克曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.