在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,微控制器的单元测试框架的更多相关文章

  1. 走进JavaWeb技术世界11:单元测试框架Junit

    JUnit你不知道的那些事儿 转自 老刘 码农翻身 2016-02-24 话说有一次Eric Gamma 坐飞机的时候偶遇Kent Beck(对,就是极限编程和TDD的发起人) ,  两位大牛见面寒暄 ...

  2. c++ 单元测试框架 gmock 深度剖析

    c++ 单元测试框架 gmock 深度剖析 随着微服务和CI的流行,在目前的软件工程领域中单元测试可以说是必不可少的一个环节,在TDD中,单元测试更是被提高到了一个新的高度.但是很多公司由于很多不同的 ...

  3. Java单元测试框架 JUnit

    Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...

  4. Micro Python:运行在微控制器上的Python

    Micro Python运行在微控制器上的Python.遵守MIT协议.由剑桥大学的理论物理学家乔治·达明设计.和Arduino类似,但Micro Python更强大. Micro Python的软件 ...

  5. i.MX rt 系列微控制器的学习记录

    杂记 前言 我总是很希望自己能产生一种感知电压变化的能力,就像B站上的教学动图中,电流从电源流出时导线就像LED亮起来一样,我将指尖触到导线上就能感受到实时的电压变化.我在上学和工作时经常由于无法理解 ...

  6. javascript单元测试框架mochajs详解

    关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...

  7. Google C++单元测试框架GoogleTest(总)

    之前一个月都在学习googletest框架,对googletest的文档都翻译了一遍,也都发在了之前的博客里,另外其实还有一部分的文档我没有发,就是GMock的CookBook部分:https://g ...

  8. Google C++单元测试框架GoogleTest---GMock的CheatSheet文档

    CheatSheet文档中包含了GMock所有常用的东西,看了这个基本上就可以用它了,本文接上篇博文:Google C++单元测试框架GoogleTest---Google Mock简介--概念及基础 ...

  9. Google C++单元测试框架GoogleTest---AdvancedGuide(译文)下

    因为AdvancedGuide文档太长,分上下两部分,本文档接googletest--AdvancedGuide(译文)上:Google C++单元测试框架GoogleTest---AdvancedG ...

随机推荐

  1. List、Map、set的加载因子,默认初始容量和扩容增量

    首先,这三个概念说下.初始大小,就是创建时可容纳的默认元素个数:加载因子,表示某个阀值,用0~1之间的小数来表示,当已有元素占比达到这个阀值后,底层将进行扩容操作:扩容方式,即指定每次扩容后的大小的规 ...

  2. TensorFlow从入门到理解(一):搭建开发环境【基于Ubuntu18.04】

    *注:教程及本文章皆使用Python3+语言,执行.py文件都是用终端(如果使用Python2+和IDE都会和本文描述有点不符) 一.安装,测试,卸载 TensorFlow官网介绍得很全面,很完美了, ...

  3. Scala思维导图

  4. Python文件读写之r+/w+/a+

    读模式 r 打开不存在的文件会报错.不能写:如不指定模式则默认是r 写模式 w 打开不存在的文件会,会新建一个文件:打开存在的文件会先清空后覆盖原有文件:不能读 追加模式 a 打开不存在的文件会,会新 ...

  5. 生成器的throw和close方法

    def gen_func(): try: yield 1 except Exception as e: pass yield 2 yield 3 yield 4 yield 5 return &quo ...

  6. Python笔记(三)继承和多态、动态语言

    一.继承 先定义一个A类 class A(object): def fun(self): print "Run A fun()" 在定义一个B类 class B(A): pass ...

  7. linux中如何使用终端裁剪图片?

    1,首先要安装支持图片裁剪的包: sudo apt-get install imagemagick 需要的话可以update一下, 2,在图片所在位置打开终端,我的我的截图叫screenshot.pn ...

  8. win10家庭版多用户

    1.Windows 找不到gpedit.msc https://jingyan.baidu.com/article/54b6b9c08b08382d593b4747.html 2.win10家庭版 创 ...

  9. 20165231 2017-2018-2 《Java程序设计》第3周学习总结

    教材学习内容总结 对象(Object):存在的具体实体,具有明确的状态和行为 类(Class):具有相同属性和行为的一组对象的集合,用于组合各个对象所共有操作和属性的一种机制 从类看对象:类定义可以视 ...

  10. 二、Java神经网络框架Neuroph的使用和架构分析

    一.使用Neuroph Studio构造感知机处理逻辑与 新建项目 接着,输入名字和地址,点击“完成” 在工程的神经网络文件下新建神经网络 准备训练数据 开始训练 误差展示 也可以测试神经元 或者输入 ...