使用Ceedling进行单元测试(嵌入式)C应用程序

使用Ceedling进行单元测试(嵌入式)C应用程序

https://dmitryfrank.com/articles/unit_testing_embedded_c_applications

就像许多其他嵌入式软件工程师一样,我过去常常将嵌入式应用程序交付生产,而没有对其进行适当的测试。仅进行了一些手动测试。我给人的印象是没有真正的方法可以对其进行测试:您知道,嵌入式应用程序在自定义硬件中运行并与该特定硬件进行大量交互,这使得它们不容易自动测试。

indicator1.jpg?w=400&tok=24d9fb

但是我在这一领域工作的时间越长,我就越认为应该有某种方法可以使我的应用程序更可靠。当然,我努力开发正确的应用程序设计并编写良好的实现,但是会发生错误(有时非常愚蠢的错误)。有一天,我遇到了一本很棒的书:James W. Grenning 撰写的《嵌入式C测试驱动开发》。这是一本非常好的书,它对主题进行了详尽的解释。强烈推荐。

尽管本文主要基于我从本书中学到的内容,但它不仅仅是其中的简短版本:在尝试将我的知识付诸实践时,我发现了一些新工具,这些工具甚至可以进一步简化流程。希望本文能帮助您快速入门。

我们将测试什么

嵌入式开发与其他软件工程领域的不同之处在于,嵌入式应用程序必须与硬件交互,并且每个应用程序之间的硬件可能有很大的不同。

不幸的是,我已经看到了大量代码,这些代码在直接与硬件交互时执行一些应用程序逻辑。有时以效率为名进行操作(在非常低端的芯片上,甚至函数调用也可能被认为是昂贵的),但最终它可能会导致一个人的习惯,当然应该避免这种习惯。

主要思想(它本身非常好,不仅对於单元测试而言是很好的)是尽可能地分离硬件交互和应用程序逻辑。然后,我们得到一堆单独的模块,可以在硬件外部进行测试。

当我们在编写应用程序时考虑到可测试性时,我们实际上必须将各部分分开。因此,编写可测试代码的“副作用”是模块化,这是一件好事。可测试的代码都很好!

因此,在本文中,我们将不会测试硬件层。它必须尽可能薄,我仍然手动对其进行测试。我们将测试的其他所有内容:使用硬件层的模块以及使用其他模块的模块。

尽管存在在目标硬件上运行测试的技术(James W. Grenning的书尤其涉及到这些技术),但本文重点是在主机上进行测试。

也许值得一提的是,单元测试并不是将项目变成完全没有错误的神奇的灵丹妙药。即使对于桌面编程也不是这样,在桌面编程中,我们使用相同的编译器进行测试和生产。在嵌入式世界中,情况甚至更糟,因为:

  • 我们无法测试编译器的错误(不幸的是,嵌入式编译器往往……不是很好)。
  • 软硬件交互中可能存在细微问题。
  • 等等

不过,根据我的个人经验,经过仔细测试的项目比未经测试的项目要有更好的最终结果。至少,编写得当的测试可以使您摆脱自己的愚蠢错误。而且,如果您像我一样,您一定会想要的。

我们将使用的工具

前面提到的书利用了几个很棒的工具:

Unity是C语言中功能强大的C语言单元测试。它旨在支持大多数嵌入式编译器,从8位微型处理器到64位庞然大物。Unity被设计得很小,但仍然为您提供了丰富的表达断言集。
CMock是一个实用程序,用于自动生成Unity测试的存根和模拟。它是Ruby中脚本的集合,这些脚本调查您的C标头并为您的测试创建可重用的伪造版本。

这些工具对我来说无疑是改变了生活,但是仍然缺少一些东西:测试构建系统。撰写本书时可能还不存在,因此本书中完全没有提及。这里是:

Ceedling是用于C项目的构建系统,是对Ruby Rake(make-ish)构建系统的扩展。Ceedling主要针对C语言中的测试驱动开发,旨在将CMock,Unity和CException整合在一起-如果您要使用C语言创建出色的代码,那么您将无法缺少另外三个出色的开源项目。

有了这些出色的工具,让我们继续前进。

样例项目

毫不奇怪,我们将通过测试示例项目来学习如何编写单元测试。让它成为一个相当简单的设备,我为它实际开发了固件:用于电池充电器的指示器,该指示器在具有16 kB闪存和1 kB RAM的8位PIC16 MCU上运行。如您所见,资源非常有限。

可以在以下位置找到此示例项目的存储库:https : //github.com/dimonomid/test_ceedling_example

这个项目看起来像什么

为了使项目进入初始状态,请签出至标签v0.02

<span style="color:#333333"><span style="color:#333333">  $ git checkout v0.02</span></span>

该设备应仅测量一些电压(通过ADC将其转换为整数),并正确显示它们。它也应该可以与检查器设备进行通信,从而对电路进行校准。

初始项目树如下所示:

<span style="color:#333333"><span style="color:#333333">。
└──src
    ├──应用
    │├──appl_adc_channels.h
    │└──适用
    ├──bsp
    │├──bsp_adc.c
    │└──bsp_adc.h
    └──效用
        ├──adc_handler.c
        ├──adc_handler.h
        ├──itoae.c
        ├──itoae.h
        └──util_macros.h
</span></span>

有三个目录:

  • appl:主要应用代码;
  • bsp:与主板和MCU有关的代码;
  • util:各种实用程序。

由于资源非常有限,因此我们不使用RTOS:只是一个超级循环。我们也不能使用任何printf类似函数,因为它们太昂贵了。而且我们不能使用浮点数:我们仅使用整数。

因此,main()看起来像这样:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">int</span> main <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>// TODO:特定于MCU的init</em></span>
 
    <span style="color:#808080"><em>//-初始化MCU专用ADC的东西</em></span> 
    bsp_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//- </em></span>
    <span style="color:#b1b100">为</span> <span style="color:#66cc66">(</span><span style="color:#66cc66">;; </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
        bsp_adc__proceed <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span></span></span>

对于非RTOS解决方案,这是一个非常常见的方案:在超级循环中,我们只调用每个模块的proceed函数,这可能会做一些工作。目前,我们只有一个模块:bsp_adc

bsp_adc模块是依赖于MCU的例程,用于检索原始ADC值。其API如下所示:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
 *用于ADC原始计数的类型。
 * / </em></span>
<span style="color:#993333">typedef </span> <span style="color:#993333">uint16_t</span> T_BspAdcCounts <span style="color:#66cc66">;</span>
 
 
 
<span style="color:#808080"><em>/ **
 *执行模块初始化,包括硬件初始化
 * / </em></span>
<span style="color:#993333">void</span> bsp_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
<span style="color:#808080"><em>/ **
 *返回指定通道的原始ADC计数
 * /</em></span> 
T_BspAdcCounts bsp_adc__value__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
<span style="color:#808080"><em>/ **
 *从应用程序的超级循环中反复调用:存储当前
 *在准备就绪并在不同通道之间切换时进行测量
 *适当
 * / </em></span>
<span style="color:#993333">void</span> bsp_adc__proceed <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

如您所见,这非常容易。当我们调用时bsp_adc__value__get(),它返回指定通道的原始ADC值,这并不是特别有用。我们想将其转换为更易读的内容,对吗?也就是说,以伏特为单位。

有一个adc_handler模块。基本上,它只是对所返回的原始值应用一个求和数和一个乘数bsp_adc__value__get(),我们得到一个以伏特为单位的整数值,再乘以某个比例因子(在此示例中,该因子100被使用;因此,该值1250表示12.5伏特)。

对于ADC处理程序(struct S_ADCHandler)的每个实例,我们调用其构造函数:

<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Res adc_handler__ctor <span style="color:#66cc66">(</span> 
        T_ADCHandler <span style="color:#66cc66">*</span> me <span style="color:#66cc66">,</span> <span style="color:#993333">const</span> T_ADCHandler_CtorParams <span style="color:#66cc66">*</span> p_params
         <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

传递指向参数的指针:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
 * ADC处理程序的构造函数参数
 * / </em></span>
<span style="color:#993333">typedef </span> <span style="color:#993333">struct</span> S_ADCHandler_CtorParams <span style="color:#66cc66">{</span>
 
    <span style="color:#808080"><em>/ **
     * ADC可以返回的最大值。说,对于10位ADC,应该是
     * 0x3ff。
     * /</em></span> 
    T_ADCHandler_CountsValue max_counts <span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>/ **
     *可以测量的取决于板的最大电压
     *对应于max_counts。
     *
     *仅在计算名义乘数时需要。
     * /</em></span> 
    T_ADCHandler_Voltage bsp_max_voltage <span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>/ **
     *校准数据:求和和乘数。
     *
     *仅将所有零设置为使用标称值。名义mul将通过以下方式计算
     * max_counts和bsp_max_voltage。添加将从
     * nominal_add(见下文)
     * /</em></span> 
    T_ADCHandler_Clb clb <span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>/ **
     *标称求和,以伏特为单位
     * /</em></span> 
    T_ADCHandler_Voltage标称值_add_volts <span style="color:#66cc66">;</span>
 
<span style="color:#66cc66">}</span> T_ADCHandler_CtorParams <span style="color:#66cc66">;</span></span></span>

然后,当我们想将原始ADC值转换my_raw_adc_value为伏特时,就很简单:

<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Voltage my_voltage <span style="color:#66cc66">=</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
        <span style="color:#66cc66">&</span> my_instance <span style="color:#66cc66">,</span> my_raw_adc_value
         <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

如您所见,ADC处理程序模块是完全独立的:它没有任何依赖性。这样的模块是最容易测试的模块,因此,让我们从ADC处理程序开始测试过程。

安装测试工具

在继续进行之前,我们需要安装上述Ceedling和所有随附的工具。感谢ThrowTheSwitch.org的帮助,安装过程非常简单。为此,您需要红宝石。安装完ruby后,通过在终端中输入以下内容来安装Ceedling:

<span style="color:#333333"><span style="color:#333333">$ gem安装数量</span></span>

就是这样!现在您已经准备好所有工具,除其他外,还有一个ceedling二进制文件,我们将利用它。

向项目添加测试内容

ceedling二进制允许我们通过执行命令来创建新的项目树ceedling new my_project,甚至包括主源代码目录。由于我通常以其他方式创建项目,因此在创建新项目后,我需要移动其他内容。

让我们继续:cd转到项目的目录(您在其中拥有src目录),然后执行以下操作:

<span style="color:#333333"><span style="color:#333333">$ ceedling新的test_ceedling</span></span>

这将创建test_ceedling具有以下内容的新目录:

  • test:测试C文件的目录;
  • build:我们的内置测试将位于的目录;
  • vendor:ceedling提供的二进制文件和所有随附的东西。它包括CMock,Unity和其他工具。由于我们只是Ceedling的用户,因此除文档外,我们对该目录的实际内容几乎没有兴趣:
  • vendor/ceedling/docs:吊顶和组件的文档。很有用;
  • src:Ceedling假定具有源文件的目录,但我们将不在此处存储文件,而是在我们自己的src目录中存储文件;
  • rakefile.rb:需要运行测试;您不需要了解它;
  • project.yml:实际的项目文件,我们需要根据需要进行调整。

测试构建系统的核心是项目文件:project.yml。除其他外,它包含应用程序源文件的路径。由于ceedling默认情况下未包含我们的源文件,因此我们需要稍作更改:打开文件project.yml并找到以下paths部分:

<span style="color:#333333"><span style="color:#333333">:路径:
  :测试:
    -+:测试/ **
    --:测试/支持
  :资源:
    -src / **
  :支持:
    -测试/支持</span></span>

您可能已经猜到了,我们需要更改src/**相对于项目文件位置的实际源文件的路径。好的,这很容易:让我们添加当前存在的所有源路径。我们在那里到达了三个路径:

<span style="color:#333333"><span style="color:#333333">  :资源:
    -../src/appl
    -../src/bsp
    -../src/util</span></span>

现在,为了避免混淆,让我们删除自动创建的test_ceedling/src目录,因为我们将不使用它。我们还想.gitkeep在空目录中添加一些文件,以便git将它们保存在存储库中。在存储库的根目录中,输入:

<span style="color:#333333"><span style="color:#333333">$ touch test_ceedling / build / .gitkeep
$ touch test_ceedling / test / support / .gitkeep</span></span>

将文件添加到存储库并提交:

<span style="color:#333333"><span style="color:#333333">$ git添加。
$ git commit</span></span>

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.03

实际上,我们的测试构建系统虽然是空的,但可以运行了!尝试一下:确保您在test_ceedling目录中,然后在终端中输入:

<span style="color:#333333"><span style="color:#333333">$抽水测试:全部</span></span>

您应该看到以下输出:

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------

没有执行测试。
</span></span>

它有效,并且可以预测地报告我们没有测试。因此,让我们在骨头上添加一些肉!

测试独立模块

 

为ADC处理程序编写测试

如上所述,我们将从为ADC处理程序编写测试开始,因为它是最容易测试的事情之一:它没有特定于应用程序的依赖关系。ADC处理程序的工作是将原始ADC计数转换为电压,反之亦然。我们将测试此功能。

首先,让我们创建空白测试文件。我有一个模板:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
 *包含的文件
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#808080"><em>//-unity:单元测试框架</em></span>
<span style="color:#339933">#include“ unity.h”</span>
 
<span style="color:#808080"><em>//-被测试的模块</em></span>
<span style="color:#808080"><em>// TODO</em></span>
 
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *定义
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人类型
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人数据
 ****************************************************** ******************************** /</em></span>
 
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人功能
 ****************************************************** ******************************** /</em></span>
 
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *设置,拆卸
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">无效</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">无效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#66cc66">}</span>
 
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#66cc66">}</span>
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *测试
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">无效</span> test_first <span style="color:#66cc66">(</span><span style="color:#993333">无效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>// TODO </em></span>
<span style="color:#66cc66">}</span></span></span>

每当需要编写新测试时,我们都可以使用此模板。我们的测试只是名称以开头的函数test_。在上面的示例中,我们只有空test_first()函数。

我们还有两个特殊功能:setUp()tearDown()。在setUp()每次测试之前被调用,并且tearDown()每次测试后调用。我们将尽快利用它们。现在,让我们在此处添加ADC处理程序测试。

我们首先添加要测试的模块的标题:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在测试的模块:ADC处理程序</em></span>
<span style="color:#339933">#include“ adc_handler.h”</span></span></span>

然后,添加一个我们将对其运行测试的实例,以及构造函数返回的结果代码:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人数据
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">静态</span> T_ADCHandler _adc_handler <span style="color:#66cc66">; </span>
<span style="color:#993333">静态</span> T_ADCHandler_Res _ctor_result <span style="color:#66cc66">;</span></span></span>

然后,分别在setUp()/中构造/销毁它tearDown()

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span> 
    T_ADCHandler_CtorParams params <span style="color:#66cc66">= </span> <span style="color:#66cc66">{ </span><span style="color:#66cc66">} </span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-10位ADC</em></span> 
    参数。<span style="color:#006600">max_counts </span> <span style="color:#66cc66">= </span> 0x3ff <span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-取决于电路板的最大测得电压:10 V</em></span> 
    参数。<span style="color:#006600">bsp_max_voltage </span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-偏移量为0伏</em></span> 
    参数。<span style="color:#006600">标称_添加_伏特</span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span><span style="color:#66cc66">;</span>
 
 
    <span style="color:#808080"><em>//-构造ADC处理程序,将结果保存到_ctor_result</em></span> 
    _ctor_result <span style="color:#66cc66">=</span> adc_handler__ctor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> <span style="color:#66cc66">&</span> params <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
 
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span> 
    adc_handler__dtor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

现在,我们能想到的最简单的测试是检查构造函数是否已返回成功状态,即ADC_HANDLER_RES__OK。因此,将我们的虚拟对象重命名test_firsttest_ctor_ok,并添加我们的第一个断言:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_ctor_ok <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-检查构造函数是否返回OK</em></span> 
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span> ADC_HANDLER_RES__OK <span style="color:#66cc66">,</span> _ctor_result <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

我们已经准备好进行第一个测试!

<span style="color:#333333"><span style="color:#333333">$抽水测试:全部

测试'test_adc_handler.c'
-------------------------
正在生成test_adc_handler.c的运行程序...
编译test_adc_handler_runner.c ...
编译test_adc_handler.c ...
编译unity.c ...
编译adc_handler.c ...
编译cmock.c ...
连结test_adc_handler.out ...
正在运行test_adc_handler.out ...

-----------
测试输出
-----------
[test_adc_handler.c]
  -“

--------------------
总体测试总结
--------------------
测试:1
通过:1
失败:0
忽略:0</span></span>

它有效,并且我们的测试已通过。好!

除其他事项外,您还可以看到Ceedling已经确定需要构建adc_handler.c。它是如何工作的?Ceedling检查我们包含在测试文件中的标头,并查找适当的源文件。由于adc_handler.h包含了Ceedling,因此Ceedling会adc_handler.c在我们在中指定的所有源路径中查找文件project.yml并进行编译。相当容易。

如果您以前使用过TDD做法,那么您会知道,如果代码行为错误,最好确保我们的测试失败。我们在这里不做TDD,因为在编写测试之前我们已经有一些代码,但是我们仍然可以确保测试能够失败。如果我们进行更改adc_handler__ctor()以使其返回(例如)ADC_HANDLER_RES__CLB_ERR__WRONG_PARAMS,则测试将失败,如下所示:

<span style="color:#333333"><span style="color:#333333">-----------
测试输出
-----------
[test_adc_handler.c]
  -“

-------------------
失败的测试摘要
-------------------
[test_adc_handler.c]
  测试:test_ctor_ok
  在第(72)行:“预期1是6”

--------------------
总体测试总结
--------------------
测试:1
通过:0
失败:1
忽略:0

---------------------
建立失败摘要
---------------------
单元测试失败。</span></span>

而已。让我们adc_handler回到正确的状态,然后提交。

我们绝对不想在存储库中包含构建输出,因此,添加test_ceedling/build目录以忽略列表。在仓库的根目录中,创建.gitignore具有以下内容的文件:

<span style="color:#333333"><span style="color:#333333">test_ceedling /构建</span></span>

然后,提交更改:

<span style="color:#333333"><span style="color:#333333">$ git添加。
$ git commit -m“添加了test_adc_handler”</span></span>

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.04

现在,让我们检查一下ADC处理程序实际上是否能够将ADC计数转换为电压。我们将测试该功能adc_handler__voltage__get_by_counts_value()。使用我们在setUp()函数中提供的参数,我们需要确保将0计数转换为0.0伏,将0x3ff计数转换为10.0伏,例如,将0x3ff / 3计数转换为3.33伏。

开始了:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">无效</span> test_counts_to_voltage <span style="color:#66cc66">(</span><span style="color:#993333">无效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span> 
    T_ADCHandler_Voltage电压<span style="color:#66cc66">;</span>
 
 
    <span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span> 
    电压<span style="color:#66cc66">=</span> adc_handler__ 电压__get_by_counts_value <span style="color:#66cc66">(</span>
            <span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">,</span>电压<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
 
    <span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span> 
    电压<span style="color:#66cc66">=</span> adc_handler__ 电压__get_by_counts_value <span style="color:#66cc66">(</span>
            <span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> 0x3ff 
            <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">,</span>电压<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
 
    <span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span> 
    电压<span style="color:#66cc66">=</span> adc_handler__ 电压__get_by_counts_value <span style="color:#66cc66">(</span>
            <span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> 0x3ff  <span style="color:#66cc66">/ </span> <span style="color:#cc66cc">3 </span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#66cc66">(</span>3.33 <span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>电压<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

如果我们运行此测试,它将通过:

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------
测试:2
通过:2
失败:0
忽略:0</span></span>

因此,我们现在可以确定ADC处理程序执行其基本工作。

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.05

用于整数到字符串转换的编写测试

如前所述,我们不能使用浮点数,因为它们对于这种廉价的MCU来说太昂贵了,因此,我们将电压存储为整数(单位为伏),乘以系数100。当然,我们需要将电压整数值like转换为like 1250的字符串“12.5”。这就是这个名字叫名字的模块itoae的用途。名称“ itoae”代表“整数到数组扩展”。

其API如下所示:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
 * Itoa扩展了一点:允许设置字符串的最小长度
 *(有效地使我们可以通过右对齐文本),
 *并允许将小数点放在某个固定位置。
 *
 * @参数p_buf
 *在哪里保存字符串数据
 * @参数值
 *值转换为字符串
 * @参数dpp
 *小数点位置。如果为0,则不放置小数点。
 *如果为1,则从右边开始放一位,依此类推。
 * @参数min_len
 *字符串的最小长度。如果实际字符串短于
 *指定长度,然后将最左边的字符填充为
 * fill_char。
 * @参数fill_char
 *字符以填充“额外”空间
 * / </em></span>
<span style="color:#993333">void</span> itoae <span style="color:#66cc66">(</span><span style="color:#993333">uint8_t </span> <span style="color:#66cc66">*</span> p_buf <span style="color:#66cc66">,</span> <span style="color:#993333">int</span>值<span style="color:#66cc66">,</span> <span style="color:#993333">int</span> dpp <span style="color:#66cc66">,</span> <span style="color:#993333">int</span> min_len <span style="color:#66cc66">,</span> <span style="color:#993333">uint8_t</span> fill_char <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

您可以在文件中找到源src/util/itoae.c。该实现相当愚蠢:首先,我们称“常规” itoa,它对小数点一无所知,它只是将整数转换为字符串。然后,如果需要小数点,则向右移动一些字符,然后插入.

如您所见,此功能也非常易于测试。让我们test_ceedling/test/test_itoae.c从上面的模板创建新文件,幷包含要测试的模块的标题:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在测试的模块</em></span>
<span style="color:#339933">#include“ itoae.h”</span></span></span>

我们将为生成的字符串提供一个缓冲区:

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#定义_BUF_LEN 20</span>
 
<span style="color:#808080"><em>/ **
 *缓冲区以存储生成的字符串数据
 * / </em></span>
<span style="color:#993333">静态</span> <span style="color:#993333">uint8_t</span> _buf <span style="color:#66cc66">[</span> _BUF_LEN <span style="color:#66cc66">] </span><span style="color:#66cc66">;</span></span></span>

以及将缓冲区与0xff

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#993333">int</span> i <span style="color:#66cc66">; </span>
    <span style="color:#b1b100">对于</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#993333">sizeof </span><span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{_</span> 
        buf <span style="color:#66cc66">[</span> i <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> 0xff <span style="color:#66cc66">; </span>
    <span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span></span></span>

我们将在每个断言之前调用此函数,以便每次都重新初始化缓冲区。和一些简单的测试:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">无效</span> test_basic <span style="color:#66cc66">(</span> <span style="color:#993333">无效</span> <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{_</span> 
    fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#66cc66">- </span><span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ -123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">无效</span> test_dpp <span style="color:#66cc66">(</span> <span style="color:#993333">无效</span> <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{_</span> 
    fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 12.3” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">2 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 1.23” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">3 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 0.123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">4 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“</span> 0.0123 <span style="color:#ff0000">” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

运行测试:

<span style="color:#333333"><span style="color:#333333">-------------------
失败的测试摘要
-------------------
[test_itoae.c]
  测试:test_dpp
  在第(127)行:“预期的'12 .3'是'12 .3 \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF'

--------------------
总体测试总结
--------------------
测试:4
通过:3
失败:1
忽略:0</span></span>

糟糕!有些不对劲。似乎尽管点已正确插入到字符串中,但终止符0x00并没有向右移动1个字符。

在检查了源代码之后,我看到这是令人讨厌的代码段:

<span style="color:#333333"><span style="color:#333333">        <span style="color:#993333">诠释</span>我<span style="color:#66cc66">; </span>
        <span style="color:#b1b100">对于</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#66cc66">(</span> dpp <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#808080"><em>/ * null-terminate * / </em></span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
            p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">] </span> <span style="color:#66cc66">=</span> p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">- </span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
        <span style="color:#66cc66">}</span> 
        p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> dpp <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> <span style="color:#ff0000">'。</span><span style="color:#66cc66">;</span></span></span>

尽管我提供了终止空字符的处理,但是这里还是有一个传统的“一对一”错误。正确的代码如下所示:

<span style="color:#333333"><span style="color:#333333">        <span style="color:#993333">诠释</span>我<span style="color:#66cc66">; </span>
        <span style="color:#b1b100">对于</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#66cc66">(</span> dpp <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#808080"><em>/ * null-terminate * / </em></span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
            p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">] </span> <span style="color:#66cc66">=</span> p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
        <span style="color:#66cc66">}</span> 
        p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> dpp <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> <span style="color:#ff0000">'。</span><span style="color:#66cc66">;</span></span></span>

保存,切换到终端,再次运行测试:

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------
测试:4
通过:4
失败:0
忽略:0</span></span>

凉!这些非常简单的测试已经使我摆脱了愚蠢的错误。信不信由你,这个错误itoae实际上已经发生在我身上,测试有助于揭示它。这一集很快鼓励我将更多的时间投入到测试中。

注意:本文并不包含所有测试代码,因为它是非常重复和直接的。您可以使用准备好的存储库来完成所有工作。在此处输入:git checkout v0.06

测试具有依赖性的模块

 

应用ADC模块

我们的应用程序需要一些资源丰富的方法来获取特定通道上的当前电压(以伏特为单位)。在整个应用程序中使用我们的bsp_adcadc_handler模块是不明智的。

现在,让我们添加模块appl_adc,该模块至少具有获取某些通道上当前电压(以伏特为单位)的功能。

标头src/appl/appl_adc.h应至少包含以下内容:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-对于T_ADCHandler_Voltage </em></span>
<span style="color:#339933">#include“ adc_handler.h”</span>
 
<span style="color:#808080"><em>//-对于枚举E_ApplAdcChannel </em></span>
<span style="color:#339933">#include“ appl_adc_channels.h”</span>
 
<span style="color:#808080"><em>/ **
 *初始化模块
 * / </em></span>
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
<span style="color:#808080"><em>/ **
 *获取给定通道的当前电压。
 * /</em></span> 
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

此函数应调用bsp_adc__value__get()给定channel_num,然后将返回值馈送到相应的adc_handler,并以伏特为单位返回结果值。

现在,让我们伪造实现(src/appl/appl_adc.c):

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ appl_adc.h”</span>
 
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//待办事项</em></span>
<span style="color:#66cc66">}</span>
 
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>// TODO </em></span>
    <span style="color:#b1b100">返回</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

然后进行测试:创建新文件test_appl_adc.c,然后将测试模板放入其中。与往常一样,包括要测试的模块的标题:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在测试的模块</em></span>
<span style="color:#339933">#include“ appl_adc.h”</span></span></span>

我们的setUp()tearDown()非常简单:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-在每次测试之前,重新初始化appl_adc模块</em></span> 
    appl_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
 
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-此处无操作</em></span>
<span style="color:#66cc66">}</span></span></span>

并进行以下测试appl_adc__voltage__get()

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">无效</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">无效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>// ..... </em></span>
<span style="color:#66cc66">}</span></span></span>

如前所述,appl_adc__voltage__get()首先应该调用bsp_adc__value__get()适当的通道。我们如何测试呢?

答案是-使用CMock。Ceedling也会在这里为我们提供帮助:如果我们需要“模拟”某个模块,我们要做的就是添加要模拟的模块的标头,并加上mock_前缀(实际上,该前缀可在中自定义project.yml,并且默认情况下为mock_)。

然后继续:添加以下include指令:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//- </em></span>
<span style="color:#339933">模拟的</span><span style="color:#808080"><em>模块</em></span><span style="color:#339933">#include“ mock_bsp_adc.h”</span></span></span>

现在,正在测试的模块将使用中所有功能的模拟版本bsp_adc.h,并且为我们提供了“期望”功能。让我们看看它们的作用:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-我们期望bsp_adc__value__get()被调用:</em></span> 
    bsp_adc__value__get_ExpectAndReturn <span style="color:#66cc66">(</span>
            <span style="color:#808080"><em>//-期望提供给</em></span>
            <span style="color:#808080"><em>// bsp_adc__value__get()</em></span> 
            APPL_ADC_CH__I_SETT的参数<span style="color:#66cc66">,</span>
 
            <span style="color:#808080"><em>//-以及bsp_adc__value__get()应该返回的值</em></span>
            <span style="color:#66cc66">(</span>0x3ff  <span style="color:#66cc66">/ </span> <span style="color:#cc66cc">2 </span><span style="color:#66cc66">)</span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-实际调用要测试的函数,该函数应该执行</em></span>
    <span style="color:#808080"><em>//所有未决的预期调用</em></span> 
    T_ADCHandler_Voltage voltage <span style="color:#66cc66">=</span> appl_adc__voltage__get <span style="color:#66cc66">(</span>
            APPL_ADC_CH__I_SETT
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-检查返回的电压(我们假设</em></span>
    <span style="color:#808080"><em>使用相同的参数// </em></span>
    <span style="color:#808080"><em>初始化了adc_handler </em></span><span style="color:#808080"><em>,其中0x3ff是ADC的最大值,并且</em></span><span style="color:#808080"><em>//它对应于值(10 * ADC_HANDLER__SCALE_FACTOR__U))</em></span> 
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#66cc66">(</span><span style="color:#cc66cc">5 </span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>电压<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

如果运行测试,则会得到以下结果:

<span style="color:#333333"><span style="color:#333333">-------------------
失败的测试摘要
-------------------
[test_appl_adc.c]
  测试:test_voltage_get
  在第(77)行:“预期500是0”
</span></span>

因此,它抱怨返回的值是错误的。好的,让我们appl_adc__voltage__get()进一步伪造我们的假人:使其返回,500而不是0

<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>// TODO </em></span>
    <span style="color:#b1b100">返回</span> <span style="color:#cc66cc">500 </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

并再次运行测试:

<span style="color:#333333"><span style="color:#333333"><span style="color:#66cc66">-------------------</span>
失败的测试摘要
<span style="color:#66cc66">------------------- </span>
<span style="color:#66cc66">[</span> test_appl_adc。<span style="color:#006600">c </span><span style="color:#66cc66">]</span> 
  测试<span style="color:#66cc66">:</span> test_voltage_get
  在行<span style="color:#66cc66">(</span><span style="color:#cc66cc">56 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">:</span> <span style="color:#ff0000">“功能‘bsp_adc__value__get’叫倍低于预期。”</span></span></span>

哦,太酷了!它报告该函数的bsp_adc__value__get()调用次数少于预期的次数(实际上,根本没有调用该函数)。CMock在行动!

那么,它的时间来执行appl_adc__voltage__get()或多或少完全:我们要同时使用bsp_adc,并adc_handler在那里。

appl_adc.c

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
 *包含的文件
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#339933">#include“ appl_adc.h” </span>
<span style="color:#339933">#include“ appl_adc_channels.h” </span>
<span style="color:#339933">#include“ bsp_adc.h”</span>
 
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人数据
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">静态</span> T_ADCHandler _adc_handlers <span style="color:#66cc66">[</span> APPL_ADC_CH_CNT <span style="color:#66cc66">] </span><span style="color:#66cc66">;</span>
 
 
<span style="color:#808080"><em>/ ***************************************************** ******************************
 *公开功能
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-将所有ADC通道初始化</em></span>
    <span style="color:#b1b100">为</span> <span style="color:#66cc66">(</span> channel <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> channel <span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">;</span> channel <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
        T_ADCHandler_CtorParams params <span style="color:#66cc66">= </span> <span style="color:#66cc66">{ </span><span style="color:#66cc66">} </span><span style="color:#66cc66">;</span>
 
        <span style="color:#808080"><em>//-在这里,我们使用相同的参数初始化所有通道,</em></span>
        <span style="color:#808080"><em>//但实际上,不同的ADC通道当然可能</em></span>
        <span style="color:#808080"><em>具有不同的参数。</em></span>
        参数。<span style="color:#006600">max_counts </span> <span style="color:#66cc66">= </span> 0x3ff <span style="color:#66cc66">; </span>
        参数。<span style="color:#006600">bsp_max_voltage </span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">; </span>
        参数。<span style="color:#006600">标称_添加_伏特</span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span><span style="color:#66cc66">;</span>
 
        <span style="color:#808080"><em>//-构造ADC处理程序,将结果保存到_ctor_result</em></span> 
        adc_handler__ctor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span> <span style="color:#66cc66">&</span> params <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span>
 
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#b1b100">返回</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
            <span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel_num <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span> 
            bsp_adc__value__get <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66">)</span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

好的,看来,一切都正确。尝试运行测试:

<span style="color:#333333"><span style="color:#333333">连结test_appl_adc.out ...
build / test / out / appl_adc.o:在函数'0097?ppl_adc__init'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:70:未定义对'0097?dc_handler__ctor'的引用
build / test / out / appl_adc.o:在函数'0097?ppl_adc__voltage__get'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:76:未定义对'0097?dc_handler__voltage__get_by_counts_value'的引用
collect2:错误:ld返回1退出状态

....

注意:如果链接器报告缺少符号,则可能是以下原因:
  1.测试缺少与所需源文件相对应的#include语句。
  2.项目搜索路径不包含与测试中的#include语句相对应的源文件。
  3.测试未#include所需的模拟。</span></span>

噢亲爱的。链接器抱怨未定义对ADC处理函数的引用。Ceedling通过向我们提供有用的通知而非常友好:正如它所暗示的那样,可能的原因之一是测试缺少#include与所需源文件相对应的语句。您还记得Ceedling检查我们包含在测试文件中的标头,并寻找适当的源文件吗?因此,要使其正常工作,我们应该将标头包含在adc_handler.h我们的中test_appl_adc.c

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-其他需要编译的模块</em></span>
<span style="color:#339933">#include“ adc_handler.h”</span></span></span>

现在,测试应该终于可以工作了:

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------
测试:7
通过:7
失败:0
忽略:0</span></span>

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.07

应用ADC模块的更隔离测试

您可能已经注意到,在上一节中,我们实际上最终一次测试了两个模块:appl_adc.cadc_handler.c。似乎不对:至少,我们已经针对进行了专用测试adc_handler.c,对吧?毕竟,即使是“单元测试”一词也表明我们应该一次测试一个单元。尽管有时我会让自己打破这个规则,但让我们尝试尽可能地隔离测试appl_adc.c

您可能已经猜到我们也要模拟ADC处理程序,而不是使用实际代码。因此,首先,让我们删除#include "adc_handler.h",幷包括模拟版本:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//- </em></span>
<span style="color:#339933">模拟的</span>
<span style="color:#808080"><em>模块</em></span><span style="color:#339933">#include“ mock_bsp_adc.h” </span><span style="color:#339933">#include“ mock_adc_handler.h”</span></span></span>

现在,我们实际上有几种选择。

忽略提供给ADC处理程序的参数

最简单的选择是忽略提供给adc_handler__voltage__get_by_counts_value()任何参数。这就是…_IgnoreAndReturn()功能的用途(由CMock生成):

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-我们期望bsp_adc__value__get()被调用:</em></span> 
    bsp_adc__value__get_ExpectAndReturn <span style="color:#66cc66">(</span>
            <span style="color:#808080"><em>//-期望提供给</em></span>
            <span style="color:#808080"><em>// bsp_adc__value__get()</em></span> 
            APPL_ADC_CH__I_SETT的参数<span style="color:#66cc66">,</span>
 
            <span style="color:#808080"><em>//-和bsp_adc__value__get()应该返回的值</em></span>
            <span style="color:#cc66cc">123 </span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-期望调用adc_handler__voltage__get_by_counts_value(),</em></span>
    <span style="color:#808080"><em>//忽略参数。模拟版本仅返回</em></span> 
    456。adc_handler__voltage__get_by_counts_value_IgnoreAndReturn <span style="color:#66cc66">(</span><span style="color:#cc66cc">456 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-实际调用要测试的函数,该函数应该执行</em></span>
    <span style="color:#808080"><em>//所有未决的预期调用</em></span> 
    T_ADCHandler_Voltage voltage <span style="color:#66cc66">=</span> appl_adc__voltage__get <span style="color:#66cc66">(</span>
            APPL_ADC_CH__I_SETT
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-检查返回的电压(上面的模拟应该为456)</em></span> 
    TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">456 </span><span style="color:#66cc66">,</span> voltage <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

运行测试:

<span style="color:#333333"><span style="color:#333333">-------------------
失败的测试摘要
-------------------
[test_appl_adc.c]
  测试:test_voltage_get
  在第(57)行:“函数'adc_handler__ctor'的调用次数超出预期。”</span></span>

哦,是的,我们忘记了现在我们不仅必须模拟adc_handler__voltage__get_by_counts_value(),而且还必须模拟从调用的构造函数setUp()。我们也将忽略其参数,因此,修改后的setUp()外观如下:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-将为每个通道调用ADC处理程序构造函数。</em></span>
    <span style="color:#808080"><em>//模拟的构造函数都返回ADC_HANDLER_RES__OK。</em></span>
    <span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">; </span>
    <span style="color:#b1b100">为</span> <span style="color:#66cc66">(</span>通道<span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>通道<span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">;</span>通道<span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
        adc_handler__ctor_IgnoreAndReturn <span style="color:#66cc66">(</span> ADC_HANDLER_RES__OK <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">}</span>
 
    <span style="color:#808080"><em>//-在每次测试之前,请重新初始化appl_adc模块</em></span> 
    appl_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#66cc66">}</span></span></span>

现在测试通过了。

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.08

检查提供给ADC处理程序的参数

给出的参数adc_handler__voltage__get_by_counts_value()是:

  • 指向实例的指针T_ADCHandler;
  • 计算值以转换为伏特。

因此,如果我们要检查参数,则需要访问T_ADCHandler每个专用通道,这是私有的appl_adc.c。为此,我们必须创建伴随测试的“保护”函数,appl_adc该函数将返回适当的指针:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
 *仅用于测试!
 * /</em></span> 
T_ADCHandler <span style="color:#66cc66">*</span> _appl_adc__adc_handler__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#b1b100">return </span> <span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel <span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

我们不会在头文件中包含此函数原型appl_adc.h;相反,我们会将其作为外部函数原型包含到test_appl_adc.c

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
 *外部功能原型
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#000000"><strong>extern</strong></span> T_ADCHandler <span style="color:#66cc66">*</span> _appl_adc__adc_handler__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

现在,adc_handler__voltage__get_by_counts_value()我们可以使用…_ExpectAndReturn()模拟功能来代替忽略给定的参数:

<span style="color:#333333"><span style="color:#333333">    <span style="color:#808080"><em>//-期望调用adc_handler__voltage__get_by_counts_value(),</em></span>
    <span style="color:#808080"><em>//忽略参数。嘲笑版本只是返回456</em></span> 
    adc_handler__voltage__get_by_counts_value_ExpectAndReturn <span style="color:#66cc66">(</span>
            <span style="color:#808080"><em>// -指向适当的ADC处理程序实例</em></span> 
            _appl_adc__adc_handler__get <span style="color:#66cc66">(</span> APPL_ADC_CH__I_SETT <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>
            <span style="color:#808080"><em>// -从bsp_adc__value__get返回的值()</em></span>
            <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span>
            <span style="color:#808080"><em>// -以伏特返回值</em></span>
            <span style="color:#cc66cc">456 </span>
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

请注意,从嘲笑bsp_adc__value__get()(即123)返回的值与赋予的值匹配adc_handler__voltage__get_by_counts_value()。并且测试通过。如果值不匹配,则测试将失败。

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.09

将模拟与回调一起使用

除了易于使用的帮助程序外,CMock还为我们提供了非常灵活的回调帮助程序。回调应具有与模拟函数相同的签名,但它需要一个附加参数:num_calls。第一次调用该函数时,该函数num_calls为0,并且在以后的每次调用中都会增加1。

在回调中,我们可以检查所需内容,如果出现问题,可以调用Unity宏TEST_FAIL_MESSAGE()

让我们实现这样的回调:

<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
 *私人功能
 ****************************************************** ******************************** /</em></span>
 
<span style="color:#993333">静态</span> T_ADCHandler_Voltage _get_by_counts_value_Callback <span style="color:#66cc66">(</span> 
        T_ADCHandler <span style="color:#66cc66">*</span> me <span style="color:#66cc66">,</span> 
        T_ADCHandler_CountsValue counts_value <span style="color:#66cc66">,</span>
        <span style="color:#993333">int</span> num_calls
         <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span> 
    T_ADCHandler_Voltage ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>
 
    <span style="color:#b1b100">切换</span> <span style="color:#66cc66">(</span> num_calls <span style="color:#66cc66">)</span><span style="color:#66cc66">{ </span>
        <span style="color:#b1b100">情况</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">:</span>
            <span style="color:#b1b100">if </span> <span style="color:#66cc66">(</span> counts_value <span style="color:#66cc66">!= </span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
 
                <span style="color:#808080"><em>//-我们可以在这里检查任何内容。例如,我们可以</em></span>
                <span style="color:#808080"><em>//检查“ me”所指向的数据,但是请注意,当前</em></span>
                <span style="color:#808080"><em>//只是零,因为我们也模拟了adc_handler__ctor()</em></span>
                <span style="color:#808080"><em>//,所以没有调用原始构造函数,并且</em></span>
                <span style="color:#808080"><em>//实例保持统一。</em></span>
 
                TEST_FAIL_MESSAGE <span style="color:#66cc66">(</span>
                        <span style="color:#ff0000">“ adc_handler__voltage__get_by_counts_value()被称为” </span>
                        <span style="color:#ff0000">counts_value错误“ </span>
                        <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
            <span style="color:#66cc66">}</span>
 
            ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">456 </span><span style="color:#66cc66">; </span>
            <span style="color:#000000"><strong>休息</strong></span><span style="color:#66cc66">;</span>
 
        <span style="color:#b1b100">默认值</span><span style="color:#66cc66">:</span> 
            TEST_FAIL_MESSAGE <span style="color:#66cc66">(</span>
                    <span style="color:#ff0000">“ adc_handler__voltage__get_by_counts_value()被称为“ </span>
                    <span style="color:#ff0000">“太多次” </span>
                    <span style="color:#66cc66">))</span><span style="color:#66cc66">;</span>
            <span style="color:#000000"><strong>休息</strong></span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">}</span>
 
    <span style="color:#b1b100">返回</span> ret <span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

在我们的中test_voltage_get(),我们按以下方式使用它:

<span style="color:#333333"><span style="color:#333333">    <span style="color:#808080"><em>//-预期调用adc_handler__voltage__get_by_counts_value()</em></span> 
    adc_handler__voltage__get_by_counts_value_StubWithCallback <span style="color:#66cc66">(</span>
            _get_by_counts_value_Callback
            <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

尽管这样的回调看起来不太优雅,并且对于这个特定示例来说,这是不必要的过大杀伤力,但是它们却非常灵活。因此,请将其保留在工具箱中,并在适当时使用。

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.10

处理特定于编译器的东西

编译器通常具有一些有用的非标准内置内容。例如,XC8 Microchip编译器具有函数__builtin_software_breakpoint(),顾名思义,该函数放置软件断点。如果MCU附带调试器在其中运行,则调试器将停止执行。如果包含"xc.h"标头,则此功能可用。

我经常将其用于某些永远不会发生的情况。例如,appl_adc__voltage__get()永远不要以错误的方式来调用我们channel_num。让我们为此添加检查:

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ xc.h”</span>
 
<span style="color:#808080"><em>// .....</em></span>
 
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span> 
    T_ADCHandler_Voltage ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>
 
    <span style="color:#b1b100">如果</span> <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">)</span><span style="color:#66cc66">{</span> 
        ret <span style="color:#66cc66">=</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
                <span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel_num <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span> 
                bsp_adc__value__get <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66">)</span>
                <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">} </span> <span style="color:#b1b100">else </span> <span style="color:#66cc66">{ </span>
        <span style="color:#808080"><em>//-给出了非法的channel_num:永远不要在这里</em></span> 
        __builtin_software_breakpoint <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
    <span style="color:#66cc66">}</span>
 
    <span style="color:#b1b100">返回</span> ret <span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

在任何应用程序中都必须进行这样的检查,但是如果尝试运行测试,最终将出现以下错误:

<span style="color:#333333"><span style="color:#333333">编译appl_adc.c ...
../src/appl/appl_adc.c:15:16:致命错误:xc.h:没有此类文件或目录
 #include“ xc.h”</span></span>

显然,GCC(默认用于测试)既没有这种内置函数,也没有“xc.h”头文件。

我们可以通过使用Ceedling“支持”目录来解决此问题,该目录默认位于test/support。让我们在其中创建“xc.h”文件,并在其中放置以下内容:

c

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#ifndef _MY_XC_DUMMY#定义</span>
<span style="color:#339933">_MY_XC_DUMMY</span>
 
<span style="color:#993333">void</span> __builtin_software_breakpoint <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
<span style="color:#339933">#endif // _MY_XC_DUMMY</span></span></span>

如果现在运行测试,将会有不同的错误:

<span style="color:#333333"><span style="color:#333333">连结test_appl_adc.out ...
build / test / out / appl_adc.o:在函数'0097?ppl_adc__voltage__get'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:101:未定义对“ 0095”的引用?_builtin_software_breakpoint'
collect2:错误:ld返回1退出状态</span></span>

不错:至少,“xc.h”文件清楚地使用了appl_adc.c我们的文件,现在我们需要提供的实际实现__builtin_software_breakpoint()。最简单的方法就是模拟它。因此,将以下行添加到我们的test_appl_adc.c文件中:

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ mock_xc.h”</span></span></span>

现在,运行测试,它们就通过了!

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------
测试:7
通过:7
失败:0
忽略:0</span></span>

我们可以再编写一个测试:让我们检查__builtin_software_breakpoint()是否通过了非法的通道号,即被调用:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get_wrong_channel_number <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
    <span style="color:#808080"><em>//-我们期望__builtin_software_breakpoint()被称为...</em></span> 
    __builtin_software_breakpoint_Expect <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
 
    <span style="color:#808080"><em>//-...当我们使用非法的</em></span>
    <span style="color:#808080"><em>通道号</em></span><span style="color:#808080"><em>调用appl_adc__voltage__get()时</em></span><span style="color:#808080"><em>。</em></span>
    appl_adc__voltage__get <span style="color:#66cc66">(</span>
            APPL_ADC_CH_CNT
            <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>

测试再次通过:

<span style="color:#333333"><span style="color:#333333">--------------------
总体测试总结
--------------------
测试:8
通过:8
失败:0
忽略:0</span></span>

我们鼓励您确认如果__builtin_software_breakpoint()在非法频道号的情况下我们删除呼叫,测试将失败。

注意:您可以使用准备好的存储库完成所有操作。在此处输入:git checkout v0.12

有关在主机上进行测试的更多注意事项

在主机上进行测试非常方便:只需按几次键即可运行测试,测试运行很快,我们几乎可以立即获得结果。但是,由于架构不同,因此我们应格外小心。

如上所述,不同的编译器具有一些内置函数。除此之外,内存对齐方式通常是不同的:在8位MCU上,对齐方式为1字节,但是在您的主机上,通常为4或8字节(取决于您的体系结构)。因此,我们必须将应用程序多目标化

我经常发现自己创建了一个文件,例如my_crosscompiler.h,根据所使用的编译器在其中声明了一些内容。例如,如果需要某种结构packed,则必须使用特定于编译器的属性。所以它可能看起来像这样:

<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#if defined(__ XC8__)</span>
<span style="color:#808080"><em>//-不需要8位MCU上的“打包”属性</em></span>
<span style="color:#339933">#define __MY_CROSS_ATTR_PACKED </span>
<span style="color:#339933">#elif defined(__ GNUC__)</span>
<span style="color:#339933">#define __MY_CROSS_ATTR_PACKED __attribute __((packed))</span>
<span style="color:#339933">#endif</span></span></span>

在应用程序代码中,我使用了macro __MY_CROSS_ATTR_PACKED

这样,我们可以编写在目标MCU和主机上都可以使用的代码。当然,这会花费额外的精力和时间,但是一般来说测试也是如此。这些天,我花了大量时间编写测试。它的回报很好。

其他测试思路

编写测试代码通常被认为是一个乏味的过程,我不能完全不同意。但是,我总是鼓励自己找到一些测试事物的新方法,而不是反复地对此进行测试。

例如,考虑EEPROM模块(电可擦可编程只读存储器)。我们很可能最终将使用特定于MCU或电路板的模块bsp_eeprom,该模块仅能从指定地址读取普通数据或从指定地址读取普通数据。由于它很大程度上取决于硬件,因此我们无法在主机上对其进行测试。

除之外bsp_eeprom,使用像这样的与应用程序有关的模块也很方便appl_eeprom,该模块应具有向EEPROM写入或从EEPROM读取某些应用程序实体的功能。当然,这些依赖于应用程序的bsp_eeprom__...函数在下面调用函数。例如,我们可能具有以下功能来读取/写入每个特定ADC通道的乘法器:

<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">int16_t</span> appl_eeprom__adc_clb_mul__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel channel <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#993333">void</span> appl_eeprom__adc_clb_mul__set <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚举</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">,</span> <span style="color:#993333">int16_t</span> mul <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>

如果应用程序很大,则可能会有很多类似的功能。这是非常乏味的所有单独测试。

相反,我们可能会想到最容易犯的错误。对于appl_eeprom模块之类的东西,这是复制粘贴错误:当我们有很多相似的函数(实际上,它们都调用相同的bsp_eeprom函数,但是针对不同的地址)时,很容易将一个函数复制并粘贴到另一个函数中,也很容易忘记正确调整粘贴的代码。

因此,我经常使用这样的技术:为bsp_eeprom函数定义存根回调,即仅检查给定区域是否“干净”,如果是,则使用一些预定义的数据填充该区域(使其“脏”)。如果该区域已经“脏”,则会生成错误。

然后,执行“写入”测试:我appl_eeprom使用所有允许的参数调用模块中的每个“写入”函数,然后,EEPROM的整个工作区域应该是“脏的”,没有孔。而且,正如我之前说的,每个回调还检查将要写入的区域是否干净。这样,我们可以轻松消除这些“复制粘贴”问题:如果某个函数写入错误的区域,我们最终将获得覆盖的数据和“空洞”,这将被我们的测试捕获。

并且,当然,应该对“读取”功能进行完全相同的测试。

这比分别测试每个功能要有趣(快速)得多,最后我们将获得足够可靠的测试。

结论

ThrowTheSwitch.org 的开发人员提供的工具使我们几乎可以轻松地测试C代码。感谢大伙们!

我希望本文能帮助您大致了解Ceedling及其配套产品,并鼓励您仔细阅读文档。

将所有组件的文档集中在一处的最简单方法是通过执行以下操作创建新的立项项目:

<span style="color:#333333"><span style="color:#333333">$终止新的my_project</span></span>

并导航到my_project/vendor/ceedling/docs包含几个pdf文件的目录。

再说一次,如果您真的想投入大量时间来测试嵌入式设计(这可能是个好主意),请考虑阅读James W. Grenning 撰写的《嵌入式C测试驱动开发》一书,其中介绍了各种测试方法,方法和工具非常彻底。

让我们编写不烂的C代码!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章