作者:Steve Leibson, 赛灵思战略营销与业务规划总监
Neil Johnson – 首席顾问 – XtremeEDA
Soheil Salehian – 助理研究员 – 卡尔加里大学
模拟(Mocking)是嵌入式系统开中用于单元测试和测试驱动的一种技术。模拟的好处在于通过删除不相关的从属代码,它允许开发者很容易地隔离和单元测试其中一部分代码。在嵌入式系统中,这些从属代码可能被当作由设备驱动和硬件运行的功能集合而存在。在这篇博客中,我们将讨论关于模拟设备驱动和硬件以及使用GoogleTest和GoogleMock来测试应用程序代码。
在我们的例子中,目标硬件是Zynq-7000 SoC的ZedBoard。之所以挑选Zedboard是因为它有大量拥趸和很多简便可用的参考设计。不过其不足是,一板难求!--由于需求量大,这块开发板的运送时间超过2个月。不过目标硬件送达延迟对嵌入式开发者来说是个普遍问题。没有目标硬件,嵌入式开发者通常只有两种选择:
a) 等待,或…
b) 在没有硬件的情况下开发并测试高质量应用
我们的选择是不等待 :-)
我们的应用是一个视频演示平台。在这个平台上,Zynq SoC中ARM Cortex-A9 MPCore CPU上运行的软件创建一系列视频帧,将它们通过由Xilinx IP核组建的硬件流水线传输到HDMI视频输出。我们的目的就是通过模拟设备驱动来写入和测试整个应用程序。然后,当收到ZedBoard后,我们就可以用真正的设备驱动来重新部署我们的应用了。
模拟设备驱动功能
我们选择一个对我们单元测试很好的粒度(granularity)水平,因此我们能够从一系列很小的步骤开始建立和测试我们的应用程序, 每一步测试完毕后再进行下一步。例如,软件功能是用xiic_l.中的Xiic_DynInit()函数对目标硬件I2C 控制器基地址进行适当的初始化。
为了模拟像Xiic_DynInit()这样的设备驱动程序,我们的应用包含赛灵思设备驱动头文件xiic_l.h,正如你通常做的一样。然后我们新建我们自己的该函数实现的存根来代替从libxil.a中链接真正的设备驱动代码。这存根始于空的实现体,由此我们可以流水清除Makefile然后build,确保编译器和链接器都没问题(大多数有经验的开发者也许不会有这个习惯,但是这是个开始阶段,我们觉得有帮助)。
下一步是在存根下插入真正的功能,我可以使用这些功能来模拟设备驱动的功能。这就是要用到GoogleMock。GoogleMock 是一个C++框架,我们用它来模拟C函数(实际上GoogleMock只是个C++框架,对C++不熟悉的开发者不必为此担心。所有的事都会由单内衬(one-liner)的宏和函数完成,所以你会感到在没C++知识下创建和使用模拟也很容易)。
我们选择模拟GoogleMock中的一些设备驱动,叫做xdriverMock。在xdriverMock中,有我们感兴趣的每个函数的声明。例如 Xiic_DynInit(),我们使用MOCK_METHOD1,用来定义一个输入变量的函数。(类似的,也有MOCK_METHOD2,MOCK_METHOD3之类的来声明有任意个输入变量的函数。)MOCK_METHOD宏定义了很多一般特性使得编写单元测试和测试相互作用变得非常容易。更多好处还在后面。
作为我们 xdriverMock的一部分,我们也有叫做getXdriverMock()和destroyXdriverMock()的全局函数分别来实例化和析构这个mock对象。如果你熟悉C++,getXdriverMock()和destroyXdriverMock()函数很像是构造函数和析构函数;它们构造一个xdriverMock的实例用于测试,然后析构作为碎片收集。在具体实现的文件里这些方法看上去如下:
这个难题的最后一块就是调用GoolgeMock来填充Xiic_DynInit()函数的存根应用 。如下所示:
有了这个,我们就成功地用GoogleMock代替了真正设备驱动的功能。对单元测试来说,我们现在有了对设备驱动功能完整的控制,也摆脱了对硬件的依赖。记住我们的首要任务是验证分配给 I2C控制器的基地址,现在我们可以开始编写验证应用程序正常工作的单元测试了。
测试你的第一个功能
为了用我们新的设备驱动模拟器开始编写测试,我们首先需要建立GoogleTest线束,这相对简单,涉及新建测试单元实例,叫做licCtrl的类和xdriverMock的一个副本。如果你的测试单元是纯粹的C函数而不是像我们这样的C++类,当然你就只要直接调用C函数,不必管该类实例了。
对单元测试来说,大多数有关GoogleMock的相互作用是通过EXPECT_CALL宏发生的。有了这个宏,我们能够验证我们的应用程序满足所有期望的与设备驱动的相互作用。通常的做法例如,我们的应用程序通过叫做init()的licCtrl的一个成员函数来调用Xiic_DynInit()。确认运行的测试如下:
测试程序的第一行中,我们使用EXPECT_CALL来说明,当测试期间输入值等于HDMI I2C基地址时,XIic_DynInit()函数应当被调用一次。第二行是调用我们iicCtrl应用代码里的init()方法。如果init()方法无法用我们期望的地址调用Xiic_DynInit()函数,一个断言将在该模拟中发生,并且测试以失败告终。否则,测试通过。
这个测试的模式很通用,这也是我们选择在这儿展示的原因。不过,你可以设置更为复杂的预期来测试,例如返回值,指针的值和参考变量等等。对于可用功能的全面参考,我们推荐 GoogleMock Cookbook。
这些是使用GoogleMock来模拟设备驱动和模仿与真实硬件间的相互作用,运行于Zynq-7000 SoC 的ARM Cortex-A9 MPCore CPU上的测试应用程序的基本原理。然而这个例子只展示了GoogleMock模拟的一个设备驱动函数,我们有超过75个函数以相同的方式被模拟,以便让我们在应用程序中运行超过100种测试;全都不用目标硬件。
更多的参考资料,我们所有的应用程序和测试代码都是开源的,可在github的网址 https://github.com/nosnhojn/agile-codev-platform 上找到。更多的资讯和持续的开发报道,你可以关注 http://www.agilesoc.com/open-source-projects/agile_codev_platform/ 。
原文链接:
http://forums.xilinx.com/t5/Xcell-Daily-Blog/Mocking-Device-Drivers-to-D...
© Copyright 2014 Xilinx Inc.
如需转载,请注明出处