单元测试设备驱动程序

时间:2009-12-24 07:28:17

标签: unit-testing embedded drivers

我有一种情况需要为嵌入式硬件的某些设备驱动程序编写一些单元测试。代码很老很大,很遗憾没有很多测试。现在,唯一可能的测试是完全编译操作系统,将其加载到设备上,在现实场景中使用它并说“它有效”。没有办法测试单个组件。

我遇到了一个nice thread here which discusses unit testing for embedded devices,我从中获得了很多信息。我想更具体一点,并询问是否有人在这种情况下测试设备驱动程序是否有任何“最佳实践”。我不希望能够模拟有问题的电路板正在与之交谈的任何设备,因此可能必须在实际的硬件上测试它们。

通过这样做,我希望能够获得驱动程序的单元测试覆盖率数据,并诱使开发人员编写测试以增加其驱动程序的覆盖范围。

我遇到的一件事是编写在操作系统上运行的嵌入式应用程序并运行驱动程序代码,然后将结果传回测试工具。该设备有几个接口,我可以用来从我的测试PC驱动应用程序,以便我可以运用代码。

非常感谢任何其他建议或见解。


更新:虽然它可能不是确切的术语,但当我说单元测试时,我的意思是能够测试/练习代码而无需编译整个OS +驱动程序并将其加载到设备上。如果我必须这样做,我会称之为集成/系统测试。

问题在于我们所拥有的硬件是有限的,开发人员经常在修复错误等时使用它们。要保持一个专用并连接到CI服务器和自动化测试的机器可能是一个在这个阶段不,不。这就是为什么我正在寻找测试驱动程序的方法,而无需实际构建整个程序并将其上传到设备上。


摘要

基于下面的优秀答案,我认为解决问题的合理方法是使用IOCTL公开驱动程序功能,然后在嵌入式设备的应用程序空间中编写测试以实际运行驱动程序代码。

在设备的应用程序空间中放置一个小程序也是有意义的,它暴露了一个可以通过串行或USB运行驱动程序的API,这样单元测试的内容就可以写在PC上了。与硬件通信并运行测试。

如果项目刚刚启动,我认为我们可以更好地控制组件的隔离方式,以便主要在PC级别进行测试。鉴于编码已经完成并且我们正在尝试将测试工具和案例改装到系统上,我认为上述方法更实用。

感谢大家的回答。

6 个答案:

答案 0 :(得分:5)

在过去,这就是我们测试和调试设备驱动程序的方式。调试这样一个系统的最好方法是让工程师将嵌入式系统用作开发系统,一旦达到足够的系统成熟度,就可以取消原有的交叉开发系统!

根据您的情况,我想到了几种方法:

  • 添加ioctl处理程序:每个代码执行特定的单元测试
  • 使用条件编译,将main()添加到驱动程序中,该驱动程序在驱动程序中执行功能单元测试,并将结果输出到stdout
  • 为了最初的调试方便,也许这可以使多平台可操作,这样您就不必在目标硬件上进行调试。
  • 也许条件代码也可以模拟环回式设备。

答案 1 :(得分:4)

真正依赖于硬件的代码(分层架构中驱动程序堆栈的最低级别)无法在硬件或高质量模拟之外的任何地方进行测试硬件。

如果您的驱动程序具有更高级别功能的某些组件,这些组件不直接依赖于硬件(例如,用于以特定格式向硬件发送消息的协议处理程序)以及该部分在代码中很好地自包含,然后你可以在基于PC的单元测试框架中单独测试它。

回到最低级别 - 如果它依赖于硬件,那么测试夹具需要包括硬件。您可以制作包含硬件,驱动程序和一些测试软件的测试夹具。我认为,主要的是将正常产品的应用程序代码排除在测试之外,而是输入一些测试代码。测试代码可以系统地测试所有驱动程序的功能和角落情况(应用程序代码可能没有),并且还可以在很短的时间内(应用程序可能没有)密集地驱动驱动程序。因此,与运行应用程序相比,它更有效地使用有限的硬件,并为您提供更好的结果。

如果你可以让PC进入循环,那么PC可能有助于测试。例如。如果您正在为嵌入式设备编写串行端口驱动程序,那么您可以:

  • 为发送各种已知数据流的嵌入式设备编写测试代码。
  • 将其连接到PC的串行端口,运行验证传输数据流的测试代码。
  • 在另一个方向相同 - PC发送数据;嵌入式设备接收并验证它,并通知PC任何错误。
  • 测试可以全速流式传输数据,并播放一系列不同的字节时序(我曾经发现一个微控制器UART芯片错误,只有在字节间发送的字节数为5毫秒时才会出现。)

您可以使用以太网驱动程序(Wi-Fi驱动程序)执行类似的操作。

如果您正在测试存储设备驱动程序,例如EEPROM或闪存芯片,则PC无法以相同的方式参与。在这种情况下,您的测试工具可以测试各种写入条件(单字节,块...),并使用各种读取条件验证数据完整性。

答案 2 :(得分:2)

两个月前我完成了这个确切的任务。

让我猜一下: 您可能有“代码片段”,它们会向设备说明低级别的详细信息。您知道这些片段有效,但您无法覆盖它们,因为它们依赖于设备驱动程序。

同样,单独测试每一行也没有意义。它们永远不会孤立地运行,并且您的单元测试最终看起来像生产代码的镜像反射。 例如,如果要启动设备,则需要创建连接,传递特定的低级重置命令,然后是初始化参数struct等。 如果您需要添加一个配置,这可能需要您将其脱机,添加配置然后将其联机。 这样的东西。

你不想测试低级别的东西。然后,您的单元测试只能反映您假设设备如何工作而不确认任何内容。

这里的关键是创建三个项目:控制器,抽象和该抽象的适配器实现。在Cpp,Java或C#中,您将创建基类或接口来表示此抽象。我假设您创建了一个界面。 您将片段分解为原子操作。例如,您在界面中创建一个名为“start”和“add(parameter)”的方法。您将您的代码段放在设备适配器中。 控制器通过接口作用于适配器。

确定您在适配器中放置的代码段中的逻辑片段。然后你需要决定这个逻辑是低级别(协议处理细节等)还是这个应该属于控制器的逻辑。

然后您可以分两个阶段进行测试: *有一个简单的测试面板应用程序,作用于混凝土适配器。这用于确认适配器实际工作。它按“开始”时开始。例如,如果按顺序按“离线”,“传输(192)”和“上线”,设备将按预期响应。这是您的集成测试。

您没有对适配器中的详细信息进行单元测试。您可以手动测试它,因为唯一的成功标准是设备的响应方式。

然而,控制器是完全经过单元测试的。它只对抽象有依赖性,抽象是在测试代码中模拟出来的。因此,您的代码不依赖于您的设备驱动程序,因为不涉及具体的适配器。

然后你编写单元测试来确认,例如,方法“Add(1)”实际调用“Go offline”然后“Transmit(1)”然后在模拟的抽象上“Go online”。

这里的挑战是区分适配器和控制器。怎么回事?对我有用的是首先创建上述测试面板,然后通过它操作设备。

如果设备发生变化,适配器应隐藏您只需要更改的详细信息。

  1. 如果操作控制面板很麻烦,需要一次又一次地重复执行大量序列,或者操作面板需要非常特定于设备的知识,那么那么你的粒度太高了< / em>并且应该将它们中的一些放在一起。测试小组应该有意义。

  2. 如果最终用户需求的变化会对适配器代码产生影响,那么您的粒度可能太小并且应该将操作分开,以便通过测试驱动来满足需求变化在控制器类中进行开发。

答案 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)

词汇

  

我不希望能够模拟有问题的电路板正在与之通信的任何设备,因此可能需要在实际的硬件上测试它们。

然后,您正在退出单元测试。也许您可以使用其中一个表达式?

  • 自动化测试:测试在没有用户输入的情况下进行(与手动测试相反)。
  • 集成测试:一起测试多个组件(与单元测试相反)。
    在更大的范围内,如果您测试整个系统而不仅仅测试几个组件,则称为系统测试。
  

在问题中的评论和更新后添加:

  • 组件测试:与集成测试或系统测试相似,但规模更小 注意:所有三个组件集成系统测试在不同的规模上共享相同的问题集。相反,单元测试没有(见下图)。

“真实”单元测试的优点

通过集成 - (或系统或组件 - )测试,获得一些反馈(如测试覆盖率)当然很有趣。这当然很有用。

但是,在某些方面取得进展是非常困难的(阅读“非常昂贵”),所以 我建议您使用补充方法,例如添加一些真正的单元测试。为什么? :

  • 非常难以模拟边缘或错误条件。 (示例:计算机时钟在交易期间跨越一天或一年;网络电缆已拔下;某些组件或整个系统的电源电量下降然后上升;磁盘已满。使用单元测试,因为您模拟这些条件而不是尝试重现它们,所以更容易。单元测试是获得非常好的代码覆盖率的唯一机会。
  • 集成测试需要时间(因为访问外部资源)。您可以在执行一个集成测试期间执行数千个单元测试。因此,只有使用单元测试才能测试多种组合......
  • 要求访问特定资源(硬件,许可证等),集成测试通常会受到时间或规模的限制。如果资源由其他项目共享,则每个项目可能仅在每天的几个小时内使用它们。即使有独占访问权限,也许只有一台机器可以使用它,因此您无法并行运行测试。或者,贵公司可能会为生产购买资源(许可证或硬件),但没有(或足够早)进行开发......