当前位置:网站首页>使用 GoogleTest 框架对 C 代码进行单元测试
使用 GoogleTest 框架对 C 代码进行单元测试
2022-08-10 00:02:00 【独正己身】
在上一篇文章中
https://meekrosoft.wordpress.com/2009/11/09/2009/10/04/testing-c-code-with-the-googletest-framework/,我描述了如何开始使用 Google 测试框架测试 C++ 代码。在本文中,我将分享一些测试 C 代码的技巧和窍门。
那么有什么大不了的,不就是和C++一样吗?
是的,在某种程度上确实如此,但一如既往,魔鬼在细节中。以下是我们在尝试测试过程代码时面临的一些挑战:
- 我们无法创建被测代码的实例。这意味着我们不能轻易地为每个测试获取带有初始化数据的新对象。
- 依赖项是硬编码的。这意味着我们不能使用依赖注入技术来模拟/伪造模块依赖项。
- 我们不能使用多态来打破依赖关系
所以这只给我们留下了语言中可用的两个依赖破坏工具:预处理器和链接器。
需要注意的事项
静态初始化:在运行每个测试用例之前,您需要能够将数据重置为已知状态。这是将测试彼此隔离的唯一方法。
全局变量:您的模块是否访问全局变量?您需要为此提供一个虚假的实现。
硬件访问:在嵌入式系统中,我们经常有内存映射的硬件寄存器访问。您绝对不想在测试中取消引用随机内存地址。一个很好的解决方法是定义一个通用函数来获取给定寄存器的地址。然后,您可以定义此函数的版本以用于测试目的。
一个例子
那么在实践中看起来如何呢?假设我们有一个用于控制设备的虚构嵌入式软件应用程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #include <stdio.h>#include <unistd.h>#define IOMEM_BASE 0x2FF#define VALUE_REG (IOMEM_BASE + 3)// This must be a power of 2!#define BUFFER_SIZE 8#define MAX_ITEMS (BUFFER_SIZE-1)staticintmy_filter[BUFFER_SIZE];staticintreadIdx = 0;staticintwriteIdx = 0;intfilter_len(){ return(BUFFER_SIZE + writeIdx - readIdx) % BUFFER_SIZE; }voidfilter_add(intval) { my_filter[writeIdx] = val; writeIdx = (writeIdx+1) & BUFFER_SIZE-1; if(writeIdx == readIdx) readIdx = (readIdx+1) & BUFFER_SIZE-1;}#ifndef TESTINGintmyapp_do_dangerous_io(){ // lets dereference an io mapped register // - on the target it is at address IOMEM_BASE + 3 return*((int*)VALUE_REG);}#endifintmyapp_get_average(){ intlen = filter_len(); if(0 == len) return0; intsum = 0; for(inti = 0; i < len; i++){ sum += my_filter[(i+readIdx)%BUFFER_SIZE]; } returnsum/len;}intmyapp_task(){ // get value from register intnextval = myapp_do_dangerous_io(); // add to filter line filter_add(nextval); // return the average value as the next delay returnmyapp_get_average();}intmyapp_mainloop(){ for(;;){ intnextloopdelay = myapp_task(); sleep(nextloopdelay); }}#ifndef TESTINGintmain() { printf("!!!Hello World!!!\n"); returnmyapp_mainloop();}#endif |
我们如何测试这种讨厌的东西?
测试这种性质的代码存在一些挑战,但我们也可以使用一些方法来克服它们。
- 更改我们取消引用的地址,
- 更改我们调用的函数(在链接时)
- 隐藏我们在测试期间使用#ifdefs 调用的函数并提供一个测试假(这是我在这里采取的方法)
不兼容的函数名称:您不能链接两个主要函数。你需要隐藏一个…
静态内存:这确实会损害测试的独立性。您确实应该为每个测试用例重新初始化所有静态数据,幸运的是,有一种简单的方法可以实现这一点。所有主要的测试框架都有一个测试夹具的概念,它允许您在执行每个测试用例之前调用 SetUp 函数。使用它来初始化您的静态数据。请记住:独立测试是很好的测试!
一般测试模式
1. 为您想要存根的依赖项定义假函数
2. 如果模块依赖于全局(喘气!),您需要定义您的假
函数 3. 包含您的模块实现(#include module.c)
4. 定义一个方法将所有静态数据重置为已知状态。
5. 定义你的测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #include <gtest/gtest.h> // Hide main #define TESTING // Hide the io function since this will segfault in testing intfake_register; intmyapp_do_dangerous_io() { returnfake_register; } #include "myapp.c" classMyAppTestSuite : publictesting::Test { voidSetUp(){ memset(&my_filter, 0, sizeof(my_filter)); readIdx = 0; writeIdx = 0; } voidTearDown(){} }; TEST_F(MyAppTestSuite, myapp_task_should_return_correct_delay_for_one_element) { fake_register = 10; EXPECT_EQ(10, myapp_task()); } TEST_F(MyAppTestSuite, myapp_task_should_return_correct_delay_for_two_elements) { fake_register = 10; myapp_task(); fake_register = 20; EXPECT_EQ(15, myapp_task()); } TEST_F(MyAppTestSuite, get_average_should_return_zero_on_empty_filter) { ASSERT_EQ(0, myapp_get_average()); } TEST_F(MyAppTestSuite, addFirstFilterValAddsVal) { filter_add(42); ASSERT_EQ(42, my_filter[readIdx]); } TEST_F(MyAppTestSuite, addFirstReturnsCorrectAverage) { filter_add(42); ASSERT_EQ(42, myapp_get_average()); } TEST_F(MyAppTestSuite, addTwoValuesReturnsCorrectAverage) { filter_add(42); filter_add(40); ASSERT_EQ(41, myapp_get_average()); } TEST_F(MyAppTestSuite, get_average_should_return_average_of_full_filter) { for(inti = 0; i < MAX_ITEMS; i++){ filter_add(i); } ASSERT_EQ((0+1+2+3+4+5+6)/MAX_ITEMS, myapp_get_average()); } TEST_F(MyAppTestSuite, get_average_should_return_average_of_wrapped_filter) { for(inti = 0; i < BUFFER_SIZE; i++){ filter_add(i); } ASSERT_EQ((1+2+3+4+5+6+7)/MAX_ITEMS, myapp_get_average()); } /// ....test buffer operations... ... |
这一切都很好,但是<困难的事情>呢?
在谈论测试 C 代码(尤其是嵌入式)时,我经常听到“但是……”
- 时间问题。没错,单元测试不能神奇地模拟系统的运行时属性。
- 中断。这是最后一点的特例,但这是所有开发人员在使用多线程时遇到的相同问题。
- 位正确操作。如果您在 32 位架构上运行 24 位代码,您将不会看到各种溢出、下溢、位移和算术运算的完全相同的行为。
- 我不可能测试这个!好吧,有些类的代码根本无法使用单元测试方法进行测试。然而,根据我的经验,这适用于大多数代码库中的极少数。秘诀是尽可能多地排除不可能测试的代码,这样你就不会污染代码库的其余部分。
概括
测试 C 代码很难。测试遗留的 C 代码更加困难。但是利用我们在 C 中有限的破坏依赖的语言特性(链接器和预处理器),我们可以完成很多工作。
您可以在 GitHub 上查看原始源代码。
边栏推荐
猜你喜欢

西安生物素-四聚乙二醇-酰胺-4苯酚 浅黄色半固态

GBJ1510-ASEMI机器人电源整流桥GBJ1510
![[C language] Address book](/img/56/a72900c22b965947ee88256d8f6c21.jpg)
[C language] Address book "Static Memory Version"

PEG derivative Biotin-PEG1-OH (cas: 95611-10-2, 2-biotinaminoethanol) advantage description

2022中高级Android面试题汇总来助你通过面试

labelme标注的json标签转txt格式

02| operator

Why don't suggest you run in Docker Mysql?

聚焦热点 | ISC 2022软件供应链安全治理与运营论坛圆满落幕

渗透测试与攻防对抗——漏洞扫描&逻辑漏洞(Part1)
随机推荐
Leecode-205. 同构字符串
Fury:一个基于JIT动态编译的高性能多语言原生序列化框架
Involved in PEG-Biotin (CAS: 1778736-18-7) Biotin-PEG4-OH is widely used in molecular target detection
-red and black-
3.1 - 程序设计语言 3.2 - 高级语言的特点及引用 3.3 - 静态/动态类型语言
你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06
快速响应性智能型/智能响应性聚乙二醇纳米/还原响应型水凝胶的研究与制备
deepstream学习笔记(三):deepstream-imagedata-multistream解析与接入适配yolov5模型测试
abicc 知:API compatibility report 介绍
What do you know about FITC-labeled biotin (FITC-biotin|CAS: 134759-22-1)?
-向量点积-
【毕业设计】 基于Stm32的家庭智能监控系统 - 单片机 图像识别 人体检测 AI
重估HR SaaS:一体化后的新三年
2022金九银十工作潮,怎么样才能成功跳槽面试拿到高薪呢?
OSS-访问oss生成的url无法访问,直接下载问题
-Vector Dot Product-
02| operator
Pagoda measurement - building LightPicture open source map bed system
JVM :运行时数据区-虚拟机栈
03|Process Control