代码之家  ›  专栏  ›  技术社区  ›  Noufal Ibrahim

单元测试设备驱动程序

  •  27
  • Noufal Ibrahim  · 技术社区  · 16 年前

    一些

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

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

    我想到的一件事是编写在操作系统上运行的嵌入式应用程序,并练习驱动程序代码,然后将结果传回测试线束。该设备有两个接口,我可以使用这些接口从我的测试PC驱动应用程序,以便我可以练习代码。

    如有任何其他建议或见解,将不胜感激。


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

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


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

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

    如果这个项目刚刚开始,我想我们会对隔离组件的方式有更多的控制,这样测试就可以在PC级别完成。考虑到编码已经完成,并且我们正在尝试在系统上改装测试线束和案例,我认为上述方法更实用。

    谢谢大家的回答。

    6 回复  |  直到 8 年前
        1
  •  9
  •   wallyk    16 年前

    在过去,我们就是这样测试和调试设备驱动程序的。调试这样一个系统的最好方法是工程师使用嵌入式系统作为开发系统,一旦达到足够的系统成熟度,就把原来的交叉开发系统拿走!

    针对您的情况,我想到了几种方法:

    • 添加ioctl处理程序:每个代码执行一个特定的单元测试
    • 通过条件编译,将main()添加到驱动程序中,该驱动程序在驱动程序中执行功能单元测试并将结果输出到 stdout .
    • 也许条件代码也可以模拟环回类型的设备。
        2
  •  7
  •   Craig McQueen Dr. Watson    16 年前

    真正依赖于硬件的代码( 除了在硬件上或硬件的高质量模拟之外,在任何地方都无法真正进行测试。

    如果您的驱动程序具有以下组件: 高级 不直接依赖于硬件的功能(例如,以特定格式向硬件发送消息的协议处理程序),如果该部分在代码中完全独立,则可以在基于PC的单元测试框架中单独进行单元测试。

    回到最低级别如果它依赖于硬件,那么测试夹具需要包括硬件。您可以制作一个包含硬件、驱动程序和一些测试软件的测试夹具。我认为,主要的事情是让普通产品的应用程序代码脱离测试,并加入一些测试代码。测试代码可以系统地测试所有驱动程序的功能和转角情况(应用程序代码可能没有),也可以在短时间内对驱动程序进行密集的测试(应用程序可能没有)。因此,它比仅仅运行应用程序更有效地使用有限的硬件,并为您提供更好的结果。

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

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

    你可以用以太网驱动程序,Wi-Fi驱动程序做类似的事情。

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

        3
  •  3
  •   Tormod    16 年前

    就在两个月前,我刚刚完成了这项任务。

    让我猜猜: 您可能有向设备讲述低级细节的“代码片段”。您知道这些代码段是有效的,但是您无法覆盖它们,因为它们依赖于设备驱动程序。

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

    你不想测试低级的东西。然后,您的单元测试将只反映您 设备在没有任何确认的情况下工作。

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

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

    *有一个作用于混凝土适配器的简单测试面板应用程序。这用于确认适配器是否实际工作。当你按下“开始”时,它开始。例如,如果您依次按“脱机”、“传输(192)”和“联机”,则设备会按预期响应。这是您的集成测试。

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

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

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

    这里的挑战是区分适配器和控制器。什么去哪里?对我来说,首先创建前面提到的测试面板,然后通过它操纵设备。

    1. 如果控制面板操作起来很麻烦,需要一次又一次地重复大量序列,或者操作面板需要特定于设备的知识, 那么您的粒度太高了 并将其中的一些混合在一起。测试面板应该有意义。

    2. 如果最终用户需求的更改对适配器代码有影响, 那么您的粒度可能太低了 并且应该将操作分开,以便在控制器类中的测试驱动开发能够适应需求变化。

        4
  •  3
  •   BenMorel Manish Pradhan    11 年前

    首先,当您进行单元测试时,您正在测试一个软件。当您在测试中包括真实设备时,您也在测试该设备。有时,硬件或硬件文档可能存在问题。当你们在设计软件时,若你们已经清楚地描述了每个功能的行为,那个么进行单元测试是非常容易的,例如,考虑功能;

    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是否计算正确的结果,或者读取正确的地址。这是其他一些单元测试的责任。。因此,您需要做的是为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在设备上做一些工作,使其准备好阅读。开发人员总是问自己“如果设备无法永远准备好,我的代码在这里出现死锁怎么办”,他们倾向于在设备上测试这种东西。

    嗯,你必须相信设备制造商和技术作者。如果他们说设备将在1-2秒内准备好,您不必担心这一点。如果您的代码在这里失败,您必须向设备制造商报告,您的工作不是找到解决此问题的方法。你明白我的意思了吗?

        5
  •  2
  •   philant    16 年前

    我建议进行基于应用程序的测试。即使脚手架很难建造,成本也很高,但这里有很多好处:

    • 能够使用标准工具集(调试器、内存检查器…)
    • 克服硬件可用性限制
    • 更快的反馈:无需在设备中安装,只需编译和测试
    • ...

    就命名而言,这可以称为组件测试。

    应用程序可以像目标操作系统一样初始化设备驱动程序,也可以直接使用驱动程序的实习生。前者成本更高,但覆盖范围更广。然后,链接器将告诉哪些函数丢失,存根它们,可能使用 exploding stubs .

        6
  •  1
  •   Community Mohan Dere    5 年前

    词汇

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

    然后,您将退出单元测试。也许你可以用其中一个来代替?

    • 自动化 手册 测试)。
    • 测试:一起测试多个组件(与 测试)。
      系统

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

    “真实”单元测试的优势

    但要取得超越某一点的进展是非常困难的(读作“非常昂贵”),所以
    . 为什么?

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