zoukankan      html  css  js  c++  java
  • μ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卡上的日志文本。

     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)==’’, "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!
    ");
    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!
    ");
    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!='') {
    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('', *endOfString(""));
    10 UCUNIT_CheckIsEqual('', *endOfString("hello"));
    11 str = endOfString("world");
    12 UCUNIT_CheckIsNotNull(str);
    13 UCUNIT_CheckIsEqual('', *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('',*endOfString(""))

    ../source/Application.c:84: passed:IsEqual('',*endOfString("hello"))

    ../source/Application.c:86: passed:IsNotNull(str)

    ../source/Application.c:87: passed:IsEqual('',*str)

    概要

      μCUnit是一个非常简单但功能强大的嵌入式设备和微控制器单元测试框架。它易于使用,只需要极少的资源,并通过自动化单元测试帮助提高嵌入式软件的质量。我希望你也觉得它很有用。

    链接

    欢迎关注:

  • 相关阅读:
    解决IE8不兼容通过class名获取元素的方法
    移动端页面遇到过的各种坑
    强大的正则表达式
    弹性盒子布局
    vue环境搭建
    fullpage.js使用指南
    ES5原生api(1)
    双色球中奖率分析(python)
    使用python脚本的3D引擎Panda3d
    Python lambda介绍
  • 原文地址:https://www.cnblogs.com/foxclever/p/9573026.html
Copyright © 2011-2022 走看看