【问题标题】:Unit testing device drivers单元测试设备驱动程序
【发布时间】:2010-12-29 18:09:49
【问题描述】:

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

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

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

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

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


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

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


总结

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

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

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

感谢大家的回答。

【问题讨论】:

    标签: unit-testing embedded drivers


    【解决方案1】:

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

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

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

    【讨论】:

    • 如果您的嵌入式系统足够大,可以在其上安装编译器,那么您在过去就无法工作。
    【解决方案2】:

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

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

    回到最底层——如果它依赖于硬件,那么测试夹具需要包含硬件。您可以制作一个包含硬件、驱动程序和一些测试软件的测试夹具。我认为最主要的是将正常产品的应用程序代码从测试中取出,然后放入一些测试代码。测试代码可以系统地测试驱动程序的所有功能和极端情况(应用程序代码可能不会),并且还可以在很短的时间内真正密集地测试驱动程序(应用程序可能不会)。因此,它比仅运行应用程序更有效地利用有限的硬件,并为您提供更好的结果。

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

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

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

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

    【讨论】:

      【解决方案3】:

      两个月前我有这个确切的任务。

      让我猜猜: 您可能有“sn-ps”的代码向设备讲述低级细节。您知道这些 sn-ps 可以工作,但您无法了解它们,因为它们依赖于设备驱动程序。

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

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

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

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

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

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

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

      然后你编写单元测试来确认,例如,方法“Add(1)”实际上调用了“脱机”,然后是“Transmit(1)”,然后是模拟抽象的“联机”。

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

      适配器应隐藏您仅在设备更改时才需要更改的详细信息。

        1234563 /em> 并且应该将其中一些放在一起。测试面板应该有意义。
      1. 如果最终用户需求变化对适配器代码有影响,那么您的粒度可能太低并且应该拆分操作,以便需求变化可以适应测试驱动在控制器类中开发。

      【讨论】:

        【解决方案4】:

        两三年前我也遇到过类似的问题。我已将设备驱动程序从 VxWorks 移植到 Integrity。我们只更改了驱动程序的操作系统相关部分,但这是一个安全关键项目,因此所有单元测试、集成测试都重做。我们在单元测试中使用了一个名为 LDRA 测试平台的自动化测试工具。我们 99% 的单元测试都是在使用 Microsoft 编译器的 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 function) 编写存根,并检查它是否调用具有正确参数的函数。如果您不直接从驱动程序访问内存,就是这种情况。有了这种心态,你可以在没有任何操作系统或设备的情况下测试任何类型的驱动程序代码。

        如果您已将设备内存直接映射到您的内存空间,并且设备驱动程序读取和写入设备内存,因为它是它自己的内存,它会变得有点复杂。使用自动化测试工具,现在您必须观察指针的值并根据这些指针的值定义通过/失败标准。如果您正在从内存中读取值,则必须定义预期值。在某些情况下,这可能很难。

        还有一个问题,开发者在驱动单元测试时总是很困惑,比如:

         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 内准备好,您无需担心这一点。如果您的代码在这里失败,您必须将其报告给设备制造商,找到解决此问题的解决方法不是您的工作。你明白我的意思了吗?

        我希望这会有所帮助……

        【讨论】:

        • 感谢您的 cmets。我本来希望这样做,但硬件相关和独立的部分现在耦合太紧密了,无法在板上进行测试。
        【解决方案5】:

        我建议进行基于应用程序的测试。即使脚手架建造起来既困难又昂贵,这里也有很多收获:

        • 只崩溃一次进程而不是一个系统
        • 能够使用标准工具集(调试器、内存检查器...)
        • 克服硬件可用性限制
        • 更快的反馈:设备中无需安装,只需编译和测试
        • ...

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

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

        【讨论】:

        • 如果我错了,请纠正我,但这些都是优点,而不是“方法”。我看到了它的优势,我自己也在努力,但我需要一种方法来让它发挥作用,而且不会过于耗时或昂贵。
        【解决方案6】:

        词汇

        我不希望能够模拟相关板正在与之通信的任何设备,因此可能必须在实际硬件本身上对其进行测试。

        然后,您将退出单元测试。也许您可以改用其中一种表达方式?

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

        在问题中的 cmets 和更新之后添加:

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

        “真实”单元测试的优势

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

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

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

        【讨论】:

        • 如果他单独测试单个驱动程序,即使他必须在硬件上进行测试,那么也许可以称为单元测试。
        • 我想从广义上讲。我已经用我最初的意思更新了这个问题。
        • @Noufal 感谢您的更新。我现在知道了。我的答案的第二部分有什么有用的吗?否则,我可以删除我的答案,以消除一些噪音;-)
        • 与确切的问题并不特别相关,但由于我正处于构建我的工具并说服开发团队改变他们的工作方式的初始阶段,这样的 cmets 非常有价值。
        猜你喜欢
        • 2010-09-06
        • 1970-01-01
        • 1970-01-01
        • 2018-03-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-08
        • 2018-05-28
        相关资源
        最近更新 更多