μCUnit,微控制器的单元测试框架
在MCU on Eclipse网站上看到Erich Styger在8月26日发布的博文,一篇关于微控制器单元测试的文章,有很高的参考价值,特将其翻译过来以备学习。原文网址:https://mcuoneclipse.com/2018/08/26/tutorial-%CE%BCcunit-a-unit-test-framework-for-microcontrollers/
单元测试是主机开发的常见做法。但对于嵌入式开发,这似乎仍然是一个“空白”领域。主要是因为嵌入式工程师不习惯单元测试,或者因为单元测试的通常框架需要嵌入式目标上的太多资源?
我使用的是μCUnit框架,它是一个小巧易用的框架,面向小型微控制器应用。
uCUnit
框架非常简单:两个头文件和一个.c文件:
uCUnit框架文件
使用uCUnit GitHub站点中的原始站点或使用我从GitHub稍微调整和修改的站点,以与MCUXpresso SDK和IDE一起使用。
概念是单元测试包括提供测试宏的uCunit.h头文件。
头文件中的#define将输出配置为详细或正常:
UCUNIT_MODE_NORMAL或UCUNIT_MODE_VERBOSE
System.c和System.h是系统的连接,主要用于启动,关闭和打印测试结果到控制台。下面是使用printf()方法写入输出的实现,但是这可以被任何写入例程替换或扩展到SD卡上的日志文本。
- /* Stub: Transmit a string to the host/debugger/simulator */
- void System_WriteString(char * msg) {
- PRINTF(msg);
- }
- void System_WriteInt(int n) {
- PRINTF("%d", n);
- }
框架概述
首先,我必须包含单元测试框架头文件:
#include "uCUnit.h"
接着,我必须初始化框架
UCUNIT_Init();
/* initialize framework */
还有一个测试用例包含在UCUNIT_TestcaseBegin()和UCUNIT_TestcaseEnd()中:
UCUNIT_TestcaseBegin(
"Crazy Scientist"
);
/* test cases ... */
UCUNIT_TestcaseEnd();
在最后使用时写一个摘要
UCUNIT_WriteSummary();
如果系统应该关闭使用a
UCUNIT_Shutdown();
测试
该框架提供了多种测试方法,例如:
UCUNIT_CheckIsEqual(x, 0);
/* check if x == 0 */
UCUNIT_CheckIsInRange(x, 0, 10);
/* check 0 <= x <= 10 */
UCUNIT_CheckIsBitSet(x, 7);
/* check if bit 7 set */
UCUNIT_CheckIsBitClear(x, 7);
/* check if bit 7 cleared */
UCUNIT_CheckIs8Bit(x);
/* check if not larger then 8 bit */
UCUNIT_CheckIs16Bit(x);
/* check if not larger then 16 bit */
UCUNIT_CheckIs32Bit(x);
/* check if not larger then 32 bit */
UCUNIT_CheckIsNull(p);
/* check if p == NULL */
UCUNIT_CheckIsNotNull(s);
/* check if p != NULL */
UCUNIT_Check((*s)==’\0’,
"Missing termination"
,
"s"
);
/* generic check: condition, msg, args */
通过几个例子可以解释这一点。
示例:疯狂的科学家
下面是一个'crazyScientist'功能,它结合了不同的材料:
- typedef enum {
- Unknown, /* first, generic item */
- Hydrogen, /* H */
- Helium, /* He */
- Oxygen, /* O */
- Oxygen2, /* O2 */
- Water, /* H2O */
- ChemLast /* last, sentinel */
- } Chem_t;
- Chem_t crazyScientist(Chem_t a, Chem_t b) {
- if (a==Oxygen && b==Oxygen) {
- return Oxygen2;
- }
- if (a==Hydrogen && b==Oxygen2) {
- return Water;
- }
- return Unknown;
- }
对此的测试可能如下所示:
- void Test(void) {
- Chem_t res;
- UCUNIT_Init(); /* initialize framework */
- UCUNIT_TestcaseBegin("Crazy Scientist");
- res = crazyScientist(Oxygen, Oxygen);
- UCUNIT_CheckIsEqual(res, Oxygen2);
- UCUNIT_CheckIsEqual(Unknown, crazyScientist(Water, Helium));
- UCUNIT_CheckIsEqual(Water, crazyScientist(Hydrogen, Oxygen2));
- UCUNIT_CheckIsEqual(Water, crazyScientist(Oxygen2, Hydrogen));
- UCUNIT_CheckIsInRange(crazyScientist(Unknown, Unknown), Unknown, ChemLast);
- UCUNIT_TestcaseEnd();
- /* finish all the tests */
- UCUNIT_WriteSummary();
- UCUNIT_Shutdown();
- }
通过不同的检查,我们可以验证功能是否正在按照我们的预期进行。它产生以下输出:
======================================
Crazy Scientist
======================================
../source/Application.c:60: passed:IsEqual(res,Oxygen2)
../source/Application.c:61: passed:IsEqual(Unknown,crazyScientist(Water, Helium))
../source/Application.c:62: passed:IsEqual(Water,crazyScientist(Hydrogen, Oxygen2))
../source/Application.c:63: failed:IsEqual(Water,crazyScientist(Oxygen2, Hydrogen))
../source/Application.c:64: passed:IsInRange(crazyScientist(Unknown, Unknown),Unknown,ChemLast)
======================================
../source/Application.c:65: failed:EndTestcase()
======================================
**************************************
Testcases: failed: 1
passed: 0
Checks: failed: 1
passed: 4
**************************************
System shutdown.
我建议在执行之前编写单元测试*,因为这样我就可以考虑所有不同的极端情况并改进要求。
以上输出设置为UCUNIT_MODE_VERBOSE。使用UCUNIT_MODE_NORMAL,它使用更紧凑的格式并仅打印失败的测试:
======================================
Crazy Scientist
======================================
../source/Application.c:63: failed:IsEqual(Water,crazyScientist(Oxygen2, Hydrogen))
======================================
../source/Application.c:65: failed:EndTestcase()
======================================
**************************************
Testcases: failed: 1
passed: 0
Checks: failed: 1
passed: 4
**************************************
System shutdown.
跟踪点
在上面的例子中,我们只是从外部测试函数的功能。如何检查以下函数中的测试确实检查除以零的情况?
- int checkedDivide(int a, int b) {
- if (b==) {
- PRINTF("division by zero is not defined!\n");
- return ;
- }
- return a/b;
- }
要检查是否真的输入了if()条件,我可以添加一个跟踪点。跟踪点的数量在μCUnit.h中配置为:
/**
* Max. number of checkpoints. This may depend on your application
* or limited by your RAM.
*/
#define UCUNIT_MAX_TRACEPOINTS 16
和
UCUNIT_ResetTracepointCoverage();
我可以重置跟踪点。
我用跟踪标记执行跟踪点(在0..UCUNIT_MAX_TRACEPOINTS-1范围内)
UCUNIT_Tracepoint(id);
和
UCUNIT_CheckTracepointCoverage(0);
我可以检查是否触摸了给定的跟踪点。在要测试的功能下面有一个跟踪点:
- int checkedDivide(int a, int b) {
- if (b==) {
- UCUNIT_Tracepoint(); /* mark trace point */
- PRINTF("division by zero is not defined!\n");
- return ;
- }
- return a/b;
- }
相应的单元测试代码:
- UCUNIT_TestcaseBegin("Checked Divide");
- UCUNIT_CheckIsEqual(/, checkedDivide(,));
- UCUNIT_ResetTracepointCoverage(); /* start tracking */
- UCUNIT_CheckIsEqual(, checkedDivide(,));
- UCUNIT_CheckTracepointCoverage(); /* check coverage of point 0 */
- UCUNIT_TestcaseEnd();
然后生成:
======================================
Checked Divide
======================================
../source/Application.c:69: passed:IsEqual(100/5,checkedDivide(100,5))
division by zero is not defined!
../source/Application.c:71: passed:IsEqual(0,checkedDivide(1024,0))
../source/Application.c:72: passed:TracepointCoverage(1)
字符串测试
还有许多其他方法可以使用检查,最多可以使用用户配置的检查和消息。以下是要测试的函数的示例:
- char *endOfString(char *str) {
- if (str==NULL) {
- return NULL;
- }
- while(*str!='\0') {
- str++;
- }
- return str;
- }
使用以下测试代码:
- UCUNIT_TestcaseBegin("Strings");
- UCUNIT_CheckIsNull(endOfString(NULL));
- str = endOfString("abc");
- UCUNIT_Check(
- (str!=NULL), /* condition to check */
- "string shall be not NULL", /* message */
- "str" /* argument as string */
- );
- UCUNIT_CheckIsEqual('\0', *endOfString(""));
- UCUNIT_CheckIsEqual('\0', *endOfString("hello"));
- str = endOfString("world");
- UCUNIT_CheckIsNotNull(str);
- UCUNIT_CheckIsEqual('\0', *str);
- UCUNIT_TestcaseEnd();
其输出:
======================================
Strings
======================================
../source/Application.c:76: passed:IsNull(endOfString(NULL))
../source/Application.c:82: passed:string shall be not NULL(str)
../source/Application.c:83: passed:IsEqual('\0',*endOfString(""))
../source/Application.c:84: passed:IsEqual('\0',*endOfString("hello"))
../source/Application.c:86: passed:IsNotNull(str)
../source/Application.c:87: passed:IsEqual('\0',*str)
概要
μCUnit是一个非常简单但功能强大的嵌入式设备和微控制器单元测试框架。它易于使用,只需要极少的资源,并通过自动化单元测试帮助提高嵌入式软件的质量。我希望你也觉得它很有用。
链接
- μCUnit网页:http://www.ucunit.org/
- μCUnit文档:http://www.ucunit.org/_documentation.html
- μCUnitGithub网站:https://github.com/ucunit/ucunit
- μCUnit示例用法:https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_uCUnit
- 适用于MCUXpresso的μCUnit端口:https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_uCUnit/uCUnit
欢迎关注:
μCUnit,微控制器的单元测试框架的更多相关文章
- 走进JavaWeb技术世界11:单元测试框架Junit
JUnit你不知道的那些事儿 转自 老刘 码农翻身 2016-02-24 话说有一次Eric Gamma 坐飞机的时候偶遇Kent Beck(对,就是极限编程和TDD的发起人) , 两位大牛见面寒暄 ...
- c++ 单元测试框架 gmock 深度剖析
c++ 单元测试框架 gmock 深度剖析 随着微服务和CI的流行,在目前的软件工程领域中单元测试可以说是必不可少的一个环节,在TDD中,单元测试更是被提高到了一个新的高度.但是很多公司由于很多不同的 ...
- Java单元测试框架 JUnit
Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...
- Micro Python:运行在微控制器上的Python
Micro Python运行在微控制器上的Python.遵守MIT协议.由剑桥大学的理论物理学家乔治·达明设计.和Arduino类似,但Micro Python更强大. Micro Python的软件 ...
- i.MX rt 系列微控制器的学习记录
杂记 前言 我总是很希望自己能产生一种感知电压变化的能力,就像B站上的教学动图中,电流从电源流出时导线就像LED亮起来一样,我将指尖触到导线上就能感受到实时的电压变化.我在上学和工作时经常由于无法理解 ...
- javascript单元测试框架mochajs详解
关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...
- Google C++单元测试框架GoogleTest(总)
之前一个月都在学习googletest框架,对googletest的文档都翻译了一遍,也都发在了之前的博客里,另外其实还有一部分的文档我没有发,就是GMock的CookBook部分:https://g ...
- Google C++单元测试框架GoogleTest---GMock的CheatSheet文档
CheatSheet文档中包含了GMock所有常用的东西,看了这个基本上就可以用它了,本文接上篇博文:Google C++单元测试框架GoogleTest---Google Mock简介--概念及基础 ...
- Google C++单元测试框架GoogleTest---AdvancedGuide(译文)下
因为AdvancedGuide文档太长,分上下两部分,本文档接googletest--AdvancedGuide(译文)上:Google C++单元测试框架GoogleTest---AdvancedG ...
随机推荐
- C#基础_MD5
MD5加密 1创建Md5 2.开始加密,需要将字符转换为字节数组 3.返回一个加密好的字节数组 4.将字节数组中每个元素按照指定的编码格式解析成字符串 1 static void Main(strin ...
- 【BZOJ】2286: [Sdoi2011]消耗战 虚树+DP
[题意]给定n个点的带边权树,每次询问给定ki个特殊点,求隔离点1和特殊点的最小代价.n<=250000,Σki<=500000. [算法]虚树+DP [题解]考虑普通树上的dp,设f[x ...
- 数据库设计理论与实践·<五>常见疑难杂症
- sap部署
SAP部署 连接sap系统需要通过sap javaconnect来连接,对于sapjco.jar系列文件有32位与64位之分.即对jdk有严格要求.现说明客户端部署及服务端部署两种情况: 一. 部署客 ...
- Python概念(八)字符串格式化:%和.format
https://www.cnblogs.com/nulige/p/6115793.html
- Maven 传递依赖冲突解决(了解)
1 传递依赖冲突解决(了解) 传递依赖:A(项目)依赖B,B依赖C(1.1版本),B是A的直接依赖,C就是A的传递依赖 导入依赖D,D依赖C(1.2版本) 1.1 Maven自己调解原则 1.1.1 ...
- ClassNotFoundException和NoClassDeFoundError
ClassNotFoundException:反射时(类加载时)类名写错了(属于可捕获的异常) NoClassDeFoundError:编译时依赖的类(jar包)在运行环境机器中不存在(属于无法处理的 ...
- MySql cmd下的学习笔记 —— 引擎和事务(engine,transaction)
engine 引擎就是MySQL存储数据的不同方式 myisam 插入速度快 支持全文索引 innoDB 插入速度慢 支持事务安全 假设两人同时购买火车票,两人同时看到只有一张火车票,几乎同时下单 或 ...
- 异常:已引发: "设置 connectionId 时引发了异常。" (System.Xaml.XamlObjectWriterException) 引发了一个 System.Xaml.XamlObjectWriterException: "
项目中,引用一个富文本编辑器,SmithHtmlEditor,进入页面的时候异常. 在View和ViewModel所在的类库引用. 还需要在Main中引用.
- linux 下为qtcreator 添加排版工具
1. 下载astyle. http://sourceforge.net/projects/astyle 这里可以下载最新版本, 目前是3.1 下载文件astyle_3.1_linux.tar.g ...