我有一种情况需要为嵌入式硬件的某些设备驱动程序编写一些单元测试。代码很老很大,很遗憾没有很多测试。现在,唯一可能的测试是完全编译操作系统,将其加载到设备上,在现实场景中使用它并说“它有效”。没有办法测试单个组件。
我遇到了一个nice thread here which discusses unit testing for embedded devices,我从中获得了很多信息。我想更具体一点,并询问是否有人在这种情况下测试设备驱动程序是否有任何“最佳实践”。我不希望能够模拟有问题的电路板正在与之交谈的任何设备,因此可能必须在实际的硬件上测试它们。
通过这样做,我希望能够获得驱动程序的单元测试覆盖率数据,并诱使开发人员编写测试以增加其驱动程序的覆盖范围。
我遇到的一件事是编写在操作系统上运行的嵌入式应用程序并运行驱动程序代码,然后将结果传回测试工具。该设备有几个接口,我可以用来从我的测试PC驱动应用程序,以便我可以运用代码。
非常感谢任何其他建议或见解。
更新:虽然它可能不是确切的术语,但当我说单元测试时,我的意思是能够测试/练习代码而无需编译整个OS +驱动程序并将其加载到设备上。如果我必须这样做,我会称之为集成/系统测试。
问题在于我们所拥有的硬件是有限的,开发人员经常在修复错误等时使用它们。要保持一个专用并连接到CI服务器和自动化测试的机器可能是一个在这个阶段不,不。这就是为什么我正在寻找测试驱动程序的方法,而无需实际构建整个程序并将其上传到设备上。
基于下面的优秀答案,我认为解决问题的合理方法是使用IOCTL公开驱动程序功能,然后在嵌入式设备的应用程序空间中编写测试以实际运行驱动程序代码。
在设备的应用程序空间中放置一个小程序也是有意义的,它暴露了一个可以通过串行或USB运行驱动程序的API,这样单元测试的内容就可以写在PC上了。与硬件通信并运行测试。
如果项目刚刚启动,我认为我们可以更好地控制组件的隔离方式,以便主要在PC级别进行测试。鉴于编码已经完成并且我们正在尝试将测试工具和案例改装到系统上,我认为上述方法更实用。
感谢大家的回答。
答案 0 :(得分:5)
在过去,这就是我们测试和调试设备驱动程序的方式。调试这样一个系统的最好方法是让工程师将嵌入式系统用作开发系统,一旦达到足够的系统成熟度,就可以取消原有的交叉开发系统!
根据您的情况,我想到了几种方法:
stdout
。答案 1 :(得分:4)
真正依赖于硬件的代码(分层架构中驱动程序堆栈的最低级别)无法在硬件或高质量模拟之外的任何地方进行测试硬件。
如果您的驱动程序具有更高级别功能的某些组件,这些组件不直接依赖于硬件(例如,用于以特定格式向硬件发送消息的协议处理程序)以及该部分在代码中很好地自包含,然后你可以在基于PC的单元测试框架中单独测试它。
回到最低级别 - 如果它依赖于硬件,那么测试夹具需要包括硬件。您可以制作包含硬件,驱动程序和一些测试软件的测试夹具。我认为,主要的是将正常产品的应用程序代码排除在测试之外,而是输入一些测试代码。测试代码可以系统地测试所有驱动程序的功能和角落情况(应用程序代码可能没有),并且还可以在很短的时间内(应用程序可能没有)密集地驱动驱动程序。因此,与运行应用程序相比,它更有效地使用有限的硬件,并为您提供更好的结果。
如果你可以让PC进入循环,那么PC可能有助于测试。例如。如果您正在为嵌入式设备编写串行端口驱动程序,那么您可以:
您可以使用以太网驱动程序(Wi-Fi驱动程序)执行类似的操作。
如果您正在测试存储设备驱动程序,例如EEPROM或闪存芯片,则PC无法以相同的方式参与。在这种情况下,您的测试工具可以测试各种写入条件(单字节,块...),并使用各种读取条件验证数据完整性。
答案 2 :(得分:2)
两个月前我完成了这个确切的任务。
让我猜一下: 您可能有“代码片段”,它们会向设备说明低级别的详细信息。您知道这些片段有效,但您无法覆盖它们,因为它们依赖于设备驱动程序。
同样,单独测试每一行也没有意义。它们永远不会孤立地运行,并且您的单元测试最终看起来像生产代码的镜像反射。 例如,如果要启动设备,则需要创建连接,传递特定的低级重置命令,然后是初始化参数struct等。 如果您需要添加一个配置,这可能需要您将其脱机,添加配置然后将其联机。 这样的东西。
你不想测试低级别的东西。然后,您的单元测试只能反映您假设设备如何工作而不确认任何内容。
这里的关键是创建三个项目:控制器,抽象和该抽象的适配器实现。在Cpp,Java或C#中,您将创建基类或接口来表示此抽象。我假设您创建了一个界面。 您将片段分解为原子操作。例如,您在界面中创建一个名为“start”和“add(parameter)”的方法。您将您的代码段放在设备适配器中。 控制器通过接口作用于适配器。
确定您在适配器中放置的代码段中的逻辑片段。然后你需要决定这个逻辑是低级别(协议处理细节等)还是这个应该属于控制器的逻辑。
然后您可以分两个阶段进行测试: *有一个简单的测试面板应用程序,作用于混凝土适配器。这用于确认适配器实际工作。它按“开始”时开始。例如,如果按顺序按“离线”,“传输(192)”和“上线”,设备将按预期响应。这是您的集成测试。
您没有对适配器中的详细信息进行单元测试。您可以手动测试它,因为唯一的成功标准是设备的响应方式。
然而,控制器是完全经过单元测试的。它只对抽象有依赖性,抽象是在测试代码中模拟出来的。因此,您的代码不依赖于您的设备驱动程序,因为不涉及具体的适配器。
然后你编写单元测试来确认,例如,方法“Add(1)”实际调用“Go offline”然后“Transmit(1)”然后在模拟的抽象上“Go online”。
这里的挑战是区分适配器和控制器。怎么回事?对我有用的是首先创建上述测试面板,然后通过它操作设备。
如果设备发生变化,适配器应隐藏您只需要更改的详细信息。
如果操作控制面板很麻烦,需要一次又一次地重复执行大量序列,或者操作面板需要非常特定于设备的知识,那么那么你的粒度太高了< / em>并且应该将它们中的一些放在一起。测试小组应该有意义。
如果最终用户需求的变化会对适配器代码产生影响,那么您的粒度可能太小并且应该将操作分开,以便通过测试驱动来满足需求变化在控制器类中进行开发。
答案 3 :(得分:1)
我建议进行基于应用程序的测试。即使脚手架很难建造也很昂贵,但这里有很多好处:
就命名而言,这可称为组件测试。
应用程序可以像目标操作系统一样初始化设备驱动程序,也可以直接使用驱动程序的实习生。前者更贵,但会带来更多的报道。然后链接器将告诉哪些函数丢失,将它们存根,可能使用exploding stubs。
答案 4 :(得分:1)
两三年前我遇到过类似的问题。我已经将设备驱动程序从VxWorks移植到Integrity。我们只更改了驱动程序的操作系统相关部分,但它是一个安全关键项目,所以所有单元测试,集成测试都重做。我们使用了一种名为LDRA testbed的自动化测试工具进行单元测试。 99%的单元测试都是在使用Microsoft Compilers的Windows机器上完成的。现在我将解释如何做到这一点
首先,当您进行单元测试时,您正在测试软件。当您在测试中包含真实设备时,您也在测试该设备。有时硬件或硬件文档可能存在问题。在设计软件时,如果您已经清楚地描述了每个功能的行为,那么很容易进行单元测试,例如,考虑功能;
readMessageTime(int messageNo, int* time);
//This function calculates the message location, if the location is valid,
//it reads the time information
address=calculateMessageAddr(messageNo);
if(address!=NULL) {
read(address+TIME_OFFSET,time);
return success;
}
else {
return failure;
}
好吧,在这里你只是测试readMessageTime是否正在做它应该做的事情。您不必测试calculateMessageAddr是否正在计算正确的结果,或者read读取正确的地址。这是其他一些单元测试的责任。所以你需要做的是为calculateMessageAddr和read(OS函数)编写存根,并检查它是否使用正确的参数调用函数。这种情况如果您没有直接从驱动程序访问内存。您可以在没有任何具有这种心态的操作系统或设备的情况下测试任何类型的驱动程序代码。
如果您已将设备内存直接映射到内存空间,并且设备驱动程序读取和写入设备内存,因为它是自己的内存,则会有点复杂。使用自动化测试工具,现在您必须根据这些指针的值来观察指针的值并定义通过/降低标准。如果要从内存中读取值,则必须定义预期值。在某些情况下,这可能很难。
还有一个问题,开发人员总是对驱动程序的单元测试感到困惑,例如:
readMessageTime(int messageNo, int* time);
//This function calculates the message location, if the location is valid,
//it does some jobs to make the device ready to read then
//it reads the time information
address=calculateMessageAddr(messageNo);
if(address!=NULL) {
do_smoething(); // Get the device ready to read!
do_something_else() // do some other stuff so you can read the result in 3us.
status=NOT_READY;
while(status==NOT_READY) // mustn't be longer than 3us.
status=read(address+TIME_OFFSET,time);
return success;
} else
{
return failure;
}
这里do_something和do_something_else在设备上完成一些工作,使其可以随时阅读。开发人员总是问自己“如果设备没有永远准备好并且我的代码在这里出现死锁怎么办”,他们倾向于在设备上测试这种东西。
嗯,您必须信任设备制造商和技术作者。如果他们说设备将在1-2us内准备就绪,您无需担心这一点。如果您的代码在这里失败,您必须向设备制造商报告,找不到解决此问题的解决方法不是您的工作。你看到了我的意思吗?
我希望这会有所帮助......
答案 5 :(得分:0)
我不希望能够模拟有问题的电路板正在与之通信的任何设备,因此可能需要在实际的硬件上测试它们。
然后,您正在退出单元测试。也许您可以使用其中一个表达式?
在问题中的评论和更新后添加:
通过集成 - (或系统或组件 - )测试,获得一些反馈(如测试覆盖率)当然很有趣。这当然很有用。
但是,在某些方面取得进展是非常困难的(阅读“非常昂贵”),所以 我建议您使用补充方法,例如添加一些真正的单元测试。为什么? :