前言
这个系列来一期嵌入式TDD实战,讲一下目前我嵌入式TDD开发的整个过程(不包括版本管理部分,虽然实际上我是用VCS来回顾之前的一步一步做了什么的),肯定不是最佳做法,还在不断改进中,算是给想学习类似开发方法的人提供一个实践的参考,入个门。
整个课程(姑且叫做课程吧)的主线就是记录了我通过TDD的方法开发 Flash芯片AT26DF的驱动模块 的整个过程,这个模块的当前版本已经发在之前的博文中:https://blog.csdn.net/lin_strong/article/details/90274561。
我们会从整个项目的基本配置讲起,搭建好开发环境,然后一起翻开芯片的手册,对驱动模块进行构思,构造mock对象;在非嵌入式的环境下进行大部分的开发;偶尔到嵌入式编译器下仿真一遍看看能否通过测试;一点点添加测试,不断重构模块并通过新的测试;最后实现一个简单的硬件测试,可以到实际硬件上进行自动化测试,验证模块的正确性。
配置及基础要求
开发环境配置
要跟着我一起动手做,电脑上最好先装好VS2012,这个,微软官网上搜一个下了装好就行,免费的。
然后我们还需要Unity以及其配套的Cmock,详细的可以移步http://www.throwtheswitch.org/。主要是Cmock的问题,为了使用Cmock自动生成mock代码,我们需要先装好Ruby。而这两个需要的最基础源码,我直接放在了开发环境中,所以主要是配置Cmock+Ruby并调通它的问题。
然后既然是嵌入式TDD,肯定得有嵌入式开发的环境,我们这里用到的是CodeWarrior v5.x,熟悉我的朋友肯定知道我主要用的芯片是MC9S12XEP100,所以给出的环境也是按照它来配的,所以要装CodeWarrior v5.x。
编程能力
编程能力要求不高,但起码得有C语言的基础,最好K&R整本都完整学习过,这样在看整个课程的过程中应该不会对我使用的一些关键字什么的产生疑问;主要我也没法事无巨细的每个小细节都解释过去,如果有不懂的地方可以下面留言。
另外,VS2012和CodeWarrior这两个开发环境最好都能稍微熟悉下,虽然我会手把手的教,但也没法保证所有细节都讲到。只能,尽可能详细吧。
另外,在开始课程之前可以先把Unity+Cmock的相关手册学一遍,应该不会花太多时间,我之前已经做好了翻译,这三个学完就好:
https://blog.csdn.net/lin_strong/article/details/84989534
https://blog.csdn.net/lin_strong/article/details/85053703
https://blog.csdn.net/lin_strong/article/details/84974842
起码要到能够使用给出的头文件生成出对应的mock文件。
如果上面这些搞不定。可能你还不适合开始这个课程。
开发环境介绍
我已经把我当前的工程开发测试框架上传提供下载https://download.csdn.net/download/lin_strong/11217430
目录结构
个人习惯是把所有 项目间共享的 模块都放在根目录下,单独放一个文件夹,这样就不需要每个工程保留一个拷贝。当然这也需要将模块写的足够的灵活,能够在不动源码的情况下满足各个工程的独立配置。另外,模块自身的测试文件我会在模块文件夹下再新建一个test文件夹来放。
Unity和Cmock里顾名思义就是放着Unity和Cmock的源码,但是只有.c和.h文件,没有你自己从其官网上下载后的一堆乱七八糟的东西。
除了ProTempletWithUnity文件夹外的其他文件夹都是各个模块,Console和SCI是为了让CodeWarrior工程能够打印信息而添加进行去模块,RuntimeError模块则在这边最主要是作为一个测试文件模版而放在这里的。
ProTempletWithUnity是我们的TDD开发工程模版。为了开始开发一个新模块或新项目,先将整个文件夹复制一份,然后改个名字。
进入文件夹,看到这些文件。
其中inlcude我主要放产品代码(或被开发的模块)的头文件,Sources则是产品代码的源文件,
SpecialInclude则顾名思义是放特殊的要include的文件,里头放着一个stdint.h,主要是因为CW中没有提供这个头文件,而很多模块会用到,所以我自己实现了一个放在这里。
sln则是配置好的VS2012测试工程框架(VS2012里头的解决方案,其包括0-多个工程),后面再说。
ProductionCodeLib则是被包含到解决方案里的其中一个工程,用来管理产品代码。不用动它。
进入UnityTestPro,里头就很纯的都是测试相关的文件了,因为混杂了CW工程的各种文件,所以看起来比较杂,include中放只在测试时用到的头文件,src中放只在测试时用到的源文件,由于有时不同平台的文件不一样,两个文件夹下还有代表各个平台的子文件夹。
UnityLib下是VS2012的工程文件,管理Unity和Cmock的代码。理论上好像应该把这个直接放到根目录下,这样可以不同工程间通用,但后来考虑到不同工程对Unity和Cmock的配置可能会不一样,于是就直接放在各个工程下分别编译了。
.mcp是CW的工程文件,里头是配置好了的Unity测试框架,后面详细说明。
UnityTestPro.vc……这几个文件都是UnityTestPro工程文件,其也被包含在前面那个解决方案里。
剩下几个文件夹不用太管,主要是工程用于存放各种临时文件等。
VS2012相关
我们回到上一个文件夹,双击sln文件进入配置好的解决方案。
差不多长成这个样子。
在右边的解决方案资源管理器里我们看到三个项目:ProductionCodeLib、UnityLib和UnityTest。分别对应之前的几个文件夹和其下的工程文件。
这里参考了CppUTest的组织方法,把不同的代码分开来管理。
在UnityTest项上右键->生成
应该会看到类似这样的输出:
从输出的最后可以看到Unity执行了两个测试,两个都成功了。这也是我们后面启动一遍测试的方法。
再展开各个工程。
RuntimeError模块是作为示例测试的,所以这个模块就相当于产品代码,所以其头文件和源文件放在ProductionCodeLib这个工程中,后面添加新的模块时也应该丢在这个工程里。将产品代码和测试代码放在不同的工程里主要是为了保证测试代码与产品代码的解耦。
在这个框架中,产品代码与测试代码并不混在一起编译,而是会生成一个静态库lib文件。所以右键->属性 打开属性页面后,如上图,能在配置属性项看到配置类型为静态库,而不是常见的应用程序exe。
这里有一个小trick,unity fixture其实提供了内存泄露的检测工具(我绕了好大一圈才发现这个事实),但是要使用它需要#include unity_fixture_malloc_overrides.h这个头文件;这个头文件实际上使用宏替换重定向了那几个内存动态分配函数。
为了保持产品代码的纯净,我使用了编译选项来强制include这个头文件,如上图。否则,你会发现,产品代码中动态分配的内存的泄露会无法检测到,但是测试代码中的泄露却能检测到,这种现象。
UnityLib里头则管理Unity以及Cmock的源码,同样是编译成一个Lib文件。里头可以看到一个叫unity_config.h的文件,你可以在里头设置win下、工程特定的unity配置,具体可配置选项可以参考之前的翻译,当然,目前它是个空文件,因为大部分情况下默认选项就够用了。
UnityTest工程里则放着所有的测试文件,对于Unity来说就是XXXXTest和XXXXTestRunner,以及其他的测试替身文件,以及一个总的AllTest文件和main文件。这些在几面几篇博文详谈。
另外,观测一开始那个输出可以看到,生成成功之后好像神奇地自动做了些别的工作;两个Lib工程是复制了Lib文件,Test工程是自动运行了exe文件,这个是通过后期生成事件实现的,即build后自动执行的命令行指令。从而简化了我们的工作。
有的时候我们会被警告找不到某某头文件,特别是被include的头文件与源文件没放在一个文件夹下时,要解决这个问题,需要到项目的属性页->配置属性->C/C++常规->附加包含目录中添加找不到的头文件所在的路径。当然,要是你是按照要求放在对应的include文件夹下的话就不会有这个问题了,因为我已经把那路径包含进去了。
最后一个小问题:怎么做到生成UnityTest工程的代码时会自动检测另外两个项目的变更并自动重新生成的?
答案是,项目上右键->项目依赖项,把依赖的工程勾上就好了。
有的时候,可能因为某些原因需要再建一个测试工程(比如对产品代码中两个相互依赖的模块分别使用链接时替身来分别测试),那一个简单的办法就是原地复制UnityTest工程的那几个工程文件,改个名字,然后添加进解决方案。然后有些上面讲到的设置可能需要重新设置下,以及有些不能复用的文件也得重新建一个,就不详谈了。
CodeWarrior相关
接下来我们对照着看看CodeWarrior的模板。双击.mcp文件进入工程模板。(不要太在意父路径的问题,我在不同的电脑上写的博客。。)
单击箭头指向的那个按钮以编译项目并打开调试器,如果问要不要build就选build。当前的配置为 “Full Chip Simulation”(全芯片仿真),所以不需要接上真正的MCU就能仿真地进行测试。弹出如下窗体:
为了看到仿真的输出,我们点击菜单栏的Component->Open->双击Terminal 以打开终端。
然后窗口差不多就变成这样了:
之后,为了看到和VS2012中同样的测试输出。点击上面的那个播放键或按F5:
还可以吧。基本与VS2012中的体验一致。
BTW:
由于CW中没有解决方案这种概念,所以我在CW这个模板中是把所有文件都放到了一个工程中,如果有建立多个测试项目的需求的话,复制一份mcp文件,然后对于不能复用的文件可能要替换一个新文件。
熟悉CW的朋友肯定知道,在CW中是不能直接使用标准输入输出相关函数的,但是在我这个模板中,你是可以像VS2012中一样很舒服地使用printf、vprintf、getchar、putchar、sprintf、snprintf函数的。虽然我之前的博文 中讲解了要怎么在CW中辅助IDE完成printf功能,但这里的实现其实不太一样。实际上我强制include了Console.h这个头文件,然后用了宏替换重定向了这些标准输入输出函数。Console又基于SCI模块使得标准输入输出到SCI0端口。这个模块以及其基于的新写的SCI模块暂时我还没有发到博客上(就是懒而已,实现比较复杂,写起来很烦),有兴趣的可以自己去研究研究,应该会有些收获。但是用起来很方便呀,反正你就按标准库的用法来用这些标准输入输出就是了,初始化什么的我在main.c里头都帮你弄好了。
自然,为了实现内存泄露检查,我还强制include了Inclunity_fixture_malloc_overrides.h这个头文件。所以在任一测试用例中出现内存泄露的话都可以得到报告。
文件结构应该是一目了然的,和VS2012中的可以对照,同样有一个unity_config.h文件。其中我排除了Float类型。
注意:这个工程本身也是不支持浮点数类型的。如果需要浮点数支持,可以参照之前的博文自己改一改项目配置,然后再修改下Unity的配置使之支持浮点型。
接下来我们来聊一些细节。主要是编译器选项。
菜单栏->Edit->Standard Settings->Target->Compiler for HC12
看到当前项目的编译器选项,其中一些是默认的,大部分是我加的。
-AddIncl开头的编译器选项即强制include。
-D开头的意思是宏定义
特别注意下其中我定义了-DCMOCK_MEM_SIZE=3000 -DCMOCK_MEM_STATIC
这里是为了把CMOCK的内存分配定义为静态分配且设定其大小。如果不这么设置,其默认方式会导致内存占用过大无法编译或者运行不正常。
CW中一般不会出现找不到.h文件这种问题,因为在你添加进新文件时,编译器自动会帮你加进去那个文件的路径。
到了最后需要上真板子来测试的那一步,只需要把目标从全芯片仿真改成TBDML,然后烧写进板子里,在SCI0上查看输出就行。
结语
这章介绍了我为各位朋友配置好的嵌入式TDD开发环境,可能讲的太细节了点,如果暂时不想深入研究的人只需要两个模板都能顺利编译,看到输出即可。
另外,这个模板是使用的Unity Fixture的,所以和Unity最原始的测试模板有点差异。如果你有《测试驱动的嵌入式TDD开发》一书,可以直接在附录中找到Fixture的说明。没有也没关系,跟着我的步骤一步步来完成这次实战。
上一篇:MC9S12XE 启动过程
下一篇:MC9S12XEP 的bootloader解析
推荐阅读最新更新时间:2024-11-12 02:13
设计资源 培训 开发板 精华推荐
- TB2140FTG 立体声耳机放大器的典型应用
- LTC2656CFE-L12 八通道、12 位数模转换器的典型应用
- EVAL-AD7896CB,用于 AD7896、12 位、100KSPS ADC 的便携式评估板
- 基于TS472的2 V偏置低噪声麦克风前置放大器的演示板
- Windows Hello
- 基于LNBH25LS的LNB电源评估板
- 使用 Richtek Technology Corporation 的 RT2657 的参考设计
- 使用 Infineon Technologies AG 的 OM7604ST 的参考设计
- TCR2EN285、200mA、2.85V 输出电压 CMOS 低压降稳压器的典型应用
- LTM4630IV 4.5V 至 15Vin、1.5V 和 1.2V、18A 输出 DC/DC 稳压器的典型应用电路