// test_framework.h
#define BEGIN_TESTING int main(int argc, char **argv) {
#define END_TESTING return 0;}
#define TEST(TEST_NAME) if (run_test(TEST_NAME, argc, argv))
int run_test(const char* test_name, int argc, char **argv) {
// we run every test by default
if (argc == 1) { return 1; }
// else we run only the test specified as a command line argument
for (int i = 1; i < argc; i++) {
if (!strcmp(test_name, argv[i])) { return 0; }
}
return 0;
}
9条答案
按热度按时间mzsu5hc01#
单元测试只需要“切面”或边界,在此边界上可以进行测试。测试不调用其他函数或只调用其他被测试函数的C函数是非常直接的。这方面的一些例子是执行计算或逻辑运算的函数。并且在本质上是功能性的。功能性是指相同的输入总是导致相同的输出。测试这些功能可以有巨大的好处,尽管它只是通常被认为是单元测试的一小部分。
更复杂的测试,例如使用模拟或存根也是可能的,但是它远不如在更动态的语言中,甚至不如在面向对象的语言(例如C++)中那么容易。实现这一点的一种方法是使用#defines。这篇文章Unit testing OpenGL applications就是一个例子。它展示了如何模拟OpenGL调用。这允许您测试是否进行了有效的OpenGL调用序列。
另一种选择是利用弱符号。例如,所有MPI API函数都是弱符号,因此如果您在自己的应用程序中定义相同的符号,则您的实现会覆盖库中的弱实现。如果库中的符号不是弱符号,则会在链接时得到重复的符号错误。然后,您可以实现整个MPI C API的有效模拟。这允许你确保调用正确匹配,并且没有任何额外的调用会导致死锁。也可以使用
dlopen()
和dlsym()
加载库的弱符号,并在必要时传递调用。MPI实际上提供了PMPI符号,这些符号很强,因此不必使用dlopen()
和friends。您可以认识到C单元测试的许多好处。它稍微困难一些,并且可能无法获得您期望从Ruby或Java编写的东西中获得的相同级别的覆盖率,但它绝对值得一试。
ngynwnxp2#
在最基本的级别上,单元测试只是执行其他代码位并告诉您它们是否按预期工作的代码位。
你可以简单地创建一个新的控制台应用程序,使用main()函数执行一系列测试函数,每个测试都会调用应用程序中的一个函数,如果成功,则返回0,如果失败,则返回另一个值。
我想给予你一些示例代码,但我对C语言真的很生疏。我相信有一些框架也会让这变得更容易。
f0brbegy3#
您可以使用libtap,它提供了许多函数,可以在测试失败时提供诊断用途:
它类似于其他语言中的tap库。
7eumitmz4#
下面是一个示例,说明如何在单个测试程序中为可能调用库函数的给定函数实现多个测试。
假设我们要测试以下模块:
然后我们创建以下测试程序:
通过重新定义
assert
来更新一个布尔变量,如果Assert失败,您可以继续并运行多个测试,跟踪成功和失败的次数。在每个测试开始时,将
rslt
(assert
宏使用的变量)设置为1,并设置所有控制存根函数的变量。如果某个存根函数被多次调用,则可以设置控制变量数组,以便存根函数可以检查不同调用的不同条件。由于许多库函数都是弱符号,因此可以在测试程序中重新定义它们,以便调用它们。在调用要测试的函数之前,可以设置一些状态变量来控制存根函数的行为,并检查函数参数的条件。
如果你不能像这样重新定义,给stub函数一个不同的名字,并重新定义代码中的符号来测试。例如,如果你想stub
fopen
,但发现它不是一个弱符号,定义你的stub为my_fopen
,并编译文件来测试-Dfopen=my_fopen
。在这种特殊情况下,要测试的函数可能会调用
exit
。这很棘手,因为exit
无法返回到要测试的函数。这是使用setjmp
和longjmp
有意义的少数情况之一。在进入要测试的函数之前使用setjmp
。然后在存根化的exit
中调用longjmp
直接返回到测试用例。还要注意,重新定义的
exit
有一个特殊的变量,它会检查你是否真的想退出程序,并调用_exit
来退出,如果你不这样做,你的测试程序可能不会干净地退出。此测试套件还计算尝试和失败的测试数,如果所有测试都通过,则返回0,否则返回1。这样,
make
就可以检查测试失败并采取相应的措施。上述测试代码将输出以下内容:
并且返回代码将为0。
vuv7lop35#
孤立地测试小段代码本质上并不是面向对象的,在过程语言中,你测试函数及其集合。
如果你绝望了,你必须绝望,我把一个小的C预处理器和基于gmake的框架组合在一起。它开始是一个玩具,从来没有真正成长起来,但我 * 已经 * 用它开发和测试了几个中等规模(10,000多行)的项目。
Dave's Unit Test的侵入性最小,但它可以做一些我最初认为基于预处理器的框架不可能做的测试(您可以要求某段代码在某些条件下抛出分段错误,它会为您测试)。
这也是一个例子,说明了为什么大量使用预处理器是“困难”的。
cfh9epnr6#
进行单元测试最简单的方法是构建一个简单的驱动程序代码,它与其他代码链接,并在每种情况下调用每个函数...Assert函数结果的值,然后一点一点地构建...反正我就是这么做的
希望这个有用。
wkyowqbh7#
对于C语言,它必须比简单地在现有代码上实现一个框架更进一步。
我一直在做的一件事就是创建一个测试模块(带有main),你可以在其中运行一些小的测试来测试你的代码,这允许你在代码和测试周期之间做非常小的增量。
更大的问题是编写可测试的代码。关注那些不依赖于共享变量或状态的小的、独立的函数。尝试以“函数”的方式编写(没有状态),这将更容易测试。如果你有一个依赖关系,不能总是存在或很慢(像数据库),你可能不得不编写一个完整的“模拟”层,可以在测试过程中替换你的数据库。
主要单元测试目标仍然适用:确保受试代码始终重置为给定状态,持续测试等。
当我用C写代码时(回到Windows之前)我有一个批处理文件,它会调出一个编辑器,然后当我完成编辑并退出时,它会编译、链接、执行测试,然后调出带有构建结果的编辑器,测试结果和代码在不同的窗口.休息后(一分钟到几个小时取决于编译的内容)我可以只查看结果,然后直接回去编辑。我相信这个过程可以在这些天得到改进:)
zbdgwd5y8#
我使用了assert,但它不是一个真正的框架。
r7knjye29#
您可以自己编写一个简单的最小化测试框架:
现在在实际测试文件中执行以下操作:
如果要运行所有测试,请执行不带命令行参数的
./binary
,如果只想运行特定测试,请执行./binary MyFailingTest