简体   繁体   English

编写单元测试C代码

[英]Writing unit tests for C code

I'm a C++ developer and when it comes to testing, it's easy to test a class by injecting dependencies, overriding member functions, and so on, so that you can test edge cases easily. 我是一名C ++开发人员,在测试时,通过注入依赖项,覆盖成员函数等来测试类很容易,这样您就可以轻松地测试边缘案例。 However, in C, you can't use those wonderful features. 但是,在C中,您无法使用这些精彩的功能。 I'm finding it hard to add unit tests to code because of some of the 'standard' ways that C code is written. 我发现很难将单元测试添加到代码中,因为编写C代码的一些“标准”方法。 What are the best ways to tackle the following: 解决以下问题的最佳方法是:

Passing around a large 'context' struct pointer: 传递一个大的'context'结构指针:

void some_func( global_context_t *ctx, .... )
{
  /* lots of code, depending on the state of context */
}

No easy way to test failure on dependent functions: 没有简单的方法来测试依赖函数的失败:

void some_func( .... )
{
  if (!get_network_state() && !some_other_func()) {
    do_something_func();
    ....
  }
  ...
}

Functions with lots of parameters: 具有大量参数的函数:

void some_func( global_context_t *, int i, int j, other_struct_t *t, out_param_t **out, ...)
{
  /* hundreds and hundreds of lines of code */
}

Static or hidden functions: 静态或隐藏功能:

static void foo( ... )
{
  /* some code */
} 

void some_public_func( ... }
{
  /* call static functions */
  foo( ... );
}

In general, I agree with Wes's answer - it is going to be much harder to add tests to code that isn't written with tests in mind. 一般来说,我同意Wes的回答 - 将测试添加到不是用测试编写的代码中会更加困难。 There's nothing inherent in C that makes it impossible to test - but, because C doesn't force you to write in a particular style, it's also very easy to write C code that is difficult to test. C中没有任何内在的东西使其无法测试 - 但是,由于C不会强迫您以特定的方式编写,因此编写难以测试的C代码也非常容易。

In my opinion, writing code with tests in mind will encourage shorter functions, with few arguments, which helps alleviate some of the pain in your examples. 在我看来,编写带有测试的代码会鼓励更短的函数,只需要很少的参数,这有助于减轻示例中的一些痛苦。

First, you'll need to pick a unit testing framework. 首先,您需要选择一个单元测试框架。 There are a lot of examples in this question (though sadly a lot of the answers are C++ frameworks - I would advise against using C++ to test C). 这个问题中有很多例子(尽管很多答案都是C ++框架 - 我建议不要使用C ++来测试C)。

I personally use TestDept , because it is simple to use, lightweight, and allows stubbing. 我个人使用TestDept ,因为它使用简单,重量轻,并且允许存根。 However, I don't think it is very widely used yet. 但是,我认为它还没有被广泛使用。 If you're looking for a more popular framework, many people recommend Check - which is great if you use automake. 如果您正在寻找更受欢迎的框架,很多人会推荐Check - 如果您使用automake,那就太棒了。

Here are some specific answers for your use cases: 以下是您的用例的一些具体答案:

Passing around a large 'context' struct pointer 传递一个大的“上下文”结构指针

For this case, you can build an instance of the struct with the pre conditions manually set, then check the status of the struct after the function has run. 对于这种情况,您可以在手动设置pre条件的情况下构建struct的实例,然后在函数运行后检查struct的状态。 With short functions, each test will be fairly straightforward. 使用短函数,每个测试都相当简单。

No easy way to test failure on dependent functions 没有简单的方法来测试依赖函数的失败

I think this is one of the biggest hurdles with unit testing C. I've had success using TestDept , which allows run time stubbing of dependent functions. 我认为这是单元测试C的最大障碍之一。我已经成功使用了TestDept ,它允许依赖函数的运行时存根。 This is great for breaking up tightly coupled code. 这对于分解紧密耦合的代码非常有用。 Here's an example from their documentation: 以下是他们的文档中的示例:

void test_stringify_cannot_malloc_returns_sane_result() {
  replace_function(&malloc, &always_failing_malloc);
  char *h = stringify('h');
  assert_string_equals("cannot_stringify", h);
}

Depending on your target environment, this may or may not work for you. 根据您的目标环境,这可能适用于您,也可能不适合您。 See their documentation for more details. 有关详细信息,请参阅其文档

Functions with lots of parameters 具有大量参数的函数

This probably isn't the answer you're looking for, but I would just break these up into smaller functions with fewer parameters. 这可能不是您正在寻找的答案,但我只是将它们分解为具有较少参数的较小函数。 Much much easier to test. 更容易测试。

Static or hidden functions 静态或隐藏功能

It's not super clean, but I have tested static functions by including the source file directly, enabling calls of static functions. 它不是超级干净,但我通过直接包含源文件测试静态函数,启用静态函数调用。 Combined with TestDept for stubbing out anything not under test, this works fairly well. 结合TestDept来删除任何未经测试的内容,这种方法效果相当不错。

 #include "implementation.c"

 /* Now I can call foo(), defined static in implementation.c */

A lot of C code is legacy code with few tests - and in those cases, it is generally easier to add integration tests that test large parts of the code first, rather than finely grained unit tests. 许多C代码是遗留代码,几乎没有测试 - 在这些情况下,通常更容易添加首先测试大部分代码的集成测试,而不是细粒度的单元测试。 This allows you to start refactoring the code underneath the integration test to a unit-testable state - though it may or may not be worth the investment, depending on your situation. 这允许您开始将集成测试下的代码重构为可单元测试的状态 - 尽管根据您的具体情况,它可能或可能不值得投资。 Of course, you'll want to be able to add unit tests to any new code written during this period, so having a solid framework up and running early is a good idea. 当然,您希望能够将单元测试添加到在此期间编写的任何新代码中,因此建立一个可靠的框架并提前运行是一个好主意。

If you are working with legacy code, this book (Working effectively with legacy code by Michael Feathers) is great further reading. 如果您正在使用旧代码的工作, 这本书 (由迈克尔羽毛遗留代码有效的工作)是伟大的进一步阅读。

That was a very good question designed to lure people into believing that C++ is better than C because it's more testable. 这是一个非常好的问题,旨在吸引人们相信C ++比C更好,因为它更容易测试。 However, it's hardly that simple. 然而,这并不是那么简单。

Having written lots of testable C++ and C code both, and an equally impressive amount of untestable C++ and C code, I can confidentially say you can wrap crappy untestable code in both languages. 编写了大量可测试的C ++和C代码,以及同样令人印象深刻的不可测试的C ++和C代码,我可以保密地说,你可以用两种语言包装糟糕的不可测试代码。 In fact the majority of the issues you present above are equally as problematic in C++. 实际上,您在上面提出的大多数问题在C ++中同样存在问题。 EG, lots of people write non-object encapsulated functions in C++ and use them inside classes (see the extensive use of C++ static functions within classes, as an example, such as MyAscii::fromUtf8() type functions). EG,很多人用C ++编写非对象封装函数并在类中使用它们(参见类中C ++静态函数的广泛使用,例如MyAscii :: fromUtf8()类型函数)。

And I'm quite sure that you've seen a gazillion C++ class functions with too many parameters. 而且我很确定您已经看到了具有太多参数的大量C ++类函数。 And if you think that just because a function only has one parameter it's better, consider the case that internally it's frequently masking the passed in parameters by using a bunch of member variables. 如果你认为仅仅因为一个函数只有一个参数就更好了,那么考虑一下它在内部经常使用一堆成员变量屏蔽传入参数的情况。 Let alone "static or hidden" functions (hint, remember that "private:" keyword) being just as big of a problem. 更别说“静态或隐藏”功能(提示,请记住“private:”关键字)同样大的问题。

So, the real answer to your question isn't "C is worse for exactly the reasons you state" but rather "you need to architect it properly in C, just as you would in C++". 所以,对你的问题的真正答案不是“因为你说的理由,C会更糟”,而是“你需要在C中正确地构建它,就像在C ++中一样”。 For example, if you have dependent functions, then put them in a different file and return the number of possible answers they might provide by implementing a bogus version of that function when testing the super-function. 例如,如果您有依赖函数,则将它们放在不同的文件中,并在测试超级函数时通过实现该函数的伪版本来返回它们可能提供的可能答案的数量。 And that's the barely-getting-by change. 而这几乎没有变化。 Don't make static or hidden functions if you want to test them. 如果要测试它们,请不要创建静态或隐藏功能。

The real problem is that you seem to state in your question that you're writing tests for someone else's library that you didn't write and architect for proper testability. 真正的问题是,你似乎在你的问题中说明你正在为别人的库编写测试,而你没有编写测试,而架构师则需要适当的可测试性。 However, there are a ton of C++ libraries that exhibit the exact same symptoms and if you were handed one of them to test, you'd be just as equally annoyed. 然而,有大量的C ++库表现出完全相同的症状,如果你被交给其中一个进行测试,你就会同样恼火。

The solution to all problems like this is always the same: write the code properly and don't use someone else's improperly written code. 像这样的所有问题的解决方案总是一样的:正确编写代码,不要使用别人不正确编写的代码。

When unit testing C you normally include the .c file in the test so you can first test the static functions before you test the public ones. 在单元测试C时,通常在测试中包含.c文件,这样您就可以在测试公共函数之前先测试静态函数。

If you have complex functions and you want to test code calling them then it is possible to work with mock objects. 如果您有复杂的函数并且想要测试调用它们的代码,则可以使用模拟对象。 Take a look at the cmocka unit testing framework which offers support for mock objects. 看看cmocka单元测试框架,它提供了对模拟对象的支持。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM