0
点赞
收藏
分享

微信扫一扫

μCUnit,微控制器的单元测试框架

  在MCU on Eclipse网站上看到Erich Styger在8月26日发布的博文,一篇关于微控制器单元测试的文章,有很高的参考价值,特将其翻译过来以备学习。原文网址:https://mcuoneclipse.com/2018/08/26/tutorial-%CE%BCcunit-a-unit-test-framework-for-microcontrollers/


  单元测试是主机开发的常见做法。但对于嵌入式开发,这似乎仍然是一个“空白”领域。主要是因为嵌入式工程师不习惯单元测试,或者因为单元测试的通常框架需要嵌入式目标上的太多资源?

  我使用的是​​μCUnit框架​​,它是一个小巧易用的框架,面向小型微控制器应用。

 μCUnit,微控制器的单元测试框架_微控制器

uCUnit

  框架非常简单:两个头文件和一个.c文件:

μCUnit,微控制器的单元测试框架_单元测试_02 

uCUnit框架文件


  使用​​uCUnit GitHub站点中​​​的原始​​站点​​​或使用我从​​GitHub​​​稍微调整和修改的​​站点​​​​,以与MCUXpresso SDK和IDE一起使用​​。

  概念是单元测试包括提供测试宏的uCunit.h头文件。

  头文件中的#define将输出配置为详细或正常:

μCUnit,微控制器的单元测试框架_单元测试_03 

UCUNIT_MODE_NORMAL或UCUNIT_MODE_VERBOSE


  System.c和System.h是系统的连接,主要用于启动,关闭和打印测试结果到控制台。下面是使用printf()方法写入输出的实现,但是这可以被任何写入例程替换或扩展到SD卡上的日志文本。

1 /* Stub: Transmit a string to the host/debugger/simulator */
2 void System_WriteString(char * msg) {
3
4 PRINTF(msg);
5
6 }
7
8 void System_WriteInt(int n) {
9
10 PRINTF("%d", n);
11
12 }

框架概述

  首先,我必须包含单元测试框架头文件:

#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'功能,它结合了不同的材料:

1 typedef enum {
2 Unknown, /* first, generic item */
3 Hydrogen, /* H */
4 Helium, /* He */
5 Oxygen, /* O */
6 Oxygen2, /* O2 */
7 Water, /* H2O */
8 ChemLast /* last, sentinel */
9 } Chem_t;
10
11 Chem_t crazyScientist(Chem_t a, Chem_t b) {
12 if (a==Oxygen && b==Oxygen) {
13 return Oxygen2;
14 }
15
16 if (a==Hydrogen && b==Oxygen2) {
17 return Water;
18 }
19
20 return Unknown;
21
22 }

  对此的测试可能如下所示:

1 void Test(void) {
2 Chem_t res;
3 UCUNIT_Init(); /* initialize framework */
4
5 UCUNIT_TestcaseBegin("Crazy Scientist");
6 res = crazyScientist(Oxygen, Oxygen);
7 UCUNIT_CheckIsEqual(res, Oxygen2);
8 UCUNIT_CheckIsEqual(Unknown, crazyScientist(Water, Helium));
9 UCUNIT_CheckIsEqual(Water, crazyScientist(Hydrogen, Oxygen2));
10 UCUNIT_CheckIsEqual(Water, crazyScientist(Oxygen2, Hydrogen));
11 UCUNIT_CheckIsInRange(crazyScientist(Unknown, Unknown), Unknown, ChemLast);
12 UCUNIT_TestcaseEnd();
13
14 /* finish all the tests */
15 UCUNIT_WriteSummary();
16 UCUNIT_Shutdown();
17 }

  通过不同的检查,我们可以验证功能是否正在按照我们的预期进行。它产生以下输出:

======================================

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.

跟踪点

  在上面的例子中,我们只是从外部测试函数的功能。如何检查以下函数中的测试确实检查除以零的情况?

1 int checkedDivide(int a, int b) {
2 if (b==0) {
3 PRINTF("division by zero is not defined!\n");
4 return 0;
5 }
6 return a/b;
7 }

  要检查是否真的输入了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);

  我可以检查是否触摸了给定的跟踪点。在要测试的功能下面有一个跟踪点:

1 int checkedDivide(int a, int b) {
2 if (b==0) {
3 UCUNIT_Tracepoint(0); /* mark trace point */
4 PRINTF("division by zero is not defined!\n");
5 return 0;
6 }
7 return a/b;
8 }

  相应的单元测试代码:

1 UCUNIT_TestcaseBegin("Checked Divide");
2 UCUNIT_CheckIsEqual(100/5, checkedDivide(100,5));
3 UCUNIT_ResetTracepointCoverage(); /* start tracking */
4 UCUNIT_CheckIsEqual(0, checkedDivide(1024,0));
5 UCUNIT_CheckTracepointCoverage(0); /* check coverage of point 0 */
6 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)

字符串测试

  还有许多其他方法可以使用检查,最多可以使用用户配置的检查和消息。以下是要测试的函数的示例:

1 char *endOfString(char *str) {
2 if (str==NULL) {
3 return NULL;
4 }
5 while(*str!='\0') {
6 str++;
7 }
8 return str;
9 }

  使用以下测试代码:

1 UCUNIT_TestcaseBegin("Strings");
2 UCUNIT_CheckIsNull(endOfString(NULL));
3 str = endOfString("abc");
4 UCUNIT_Check(
5 (str!=NULL), /* condition to check */
6 "string shall be not NULL", /* message */
7 "str" /* argument as string */
8 );
9 UCUNIT_CheckIsEqual('\0', *endOfString(""));
10 UCUNIT_CheckIsEqual('\0', *endOfString("hello"));
11 str = endOfString("world");
12 UCUNIT_CheckIsNotNull(str);
13 UCUNIT_CheckIsEqual('\0', *str);
14 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,微控制器的单元测试框架_微控制器_04



如果您希望更方便且及时的阅读相关文章,关注我的微信公众号【木南创智




举报

相关推荐

0 条评论