多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从两起坠机事故看嵌入式软件测试:需求追溯、故障注入与鲁棒性设计

从两起坠机事故看嵌入式软件测试:需求追溯、故障注入与鲁棒性设计 1. 从两起坠机事故说起一个被反复误读的软件测试命题2018年10月和2019年3月两起涉及同一机型的大型客机坠毁事故让全球航空业经历了一次罕见的全机型停飞。事后调查的公开信息指向了一个共同的关键词机动特性增强系统MCAS。这套系统的设计初衷是在特定飞行状态下自动调整飞机姿态弥补机体气动特性带来的操纵差异。但最终它接收到了错误的迎角传感器数据并在飞行员不知情的情况下反复下压机头酿成惨剧。很多做软件测试的朋友第一次看到这个案例时第一反应是这不就是个传感器数据校验没做好吗。这个判断对但远远不够。如果只把问题归结为某个输入没做边界检查那就浪费了这个案例对嵌入式软件测试领域最深刻的启示。我在带团队做嵌入式测试的这些年里反复拿这个案例给新人做培训因为它几乎把嵌入式软件测试里所有容易踩的坑都集中演示了一遍需求追溯的断裂、传感器融合的信任假设、状态机的异常路径覆盖、人机交互的反馈缺失、以及最要命的——测试用例设计时对正常工况的过度依赖。这篇内容我想聊的不是航空事故本身而是把它当作一面镜子照出嵌入式软件测试在方法论、流程设计和工具选型上的系统性短板。适合谁看如果你正在做车载、工业控制、医疗设备、航空航天这类安全相关的嵌入式软件测试或者你正在准备软件测试项目实战、梳理软件测试流程、准备软件测试面试题这个案例拆解出来的东西比任何教科书上的理论都更值得反复咀嚼。我会从需求追溯、传感器数据处理、状态机测试、工具链选型几个维度把从这起事故里到底该学到什么讲透并且给出可以直接落地到项目里的测试策略和实操方法。2. MCAS的逻辑漏洞为什么能一路穿过测试关卡2.1 需求文档里那句假设迎角数据可靠埋下的雷MCAS的核心逻辑并不复杂当系统判断飞机接近失速状态时自动调整水平尾翼角度让机头下压帮助飞机恢复气动效率。问题出在它的触发条件上——它只依赖单个迎角传感器的数据而且在设计时做了一个致命假设传感器数据是可靠的。这个假设在需求文档里可能只是一句轻描淡写的系统接收迎角传感器信号当迎角超过阈值时触发修正。但做嵌入式测试的人都知道传感器数据可靠性从来不是默认成立的而是需要被验证、被监控、被冗余保护的。需求文档里没有明确写出当传感器数据异常时系统应如何降级测试用例自然也就不会覆盖这个场景。我在实际项目里见过太多类似的情况。需求写温度传感器采集环境温度超过阈值触发告警测试就只测了温度正常时不告警温度超标时告警两个用例。但传感器断线呢传感器输出恒定值呢传感器数据跳变呢这些异常路径在需求阶段就被忽略了测试阶段自然也不会有人去补。这里的关键教训是嵌入式软件测试的第一道防线不在测试阶段而在需求评审阶段。测试人员必须参与需求评审并且专门盯住那些隐含假设——凡是需求里出现假设XX正常默认XX可靠这类表述都要追问一句如果不正常系统行为是什么这个追问如果没人提后面所有测试都是在验证一个错误的前提。2.2 单传感器信任模型冗余设计在软件层被架空了从公开信息看MCAS在设计时只读取一侧迎角传感器的数据而另一侧传感器的数据被忽略了。这在硬件层面可能是有冗余的——飞机上装了两个迎角传感器——但在软件层面冗余被架空了。这是一个非常典型的嵌入式软件测试盲区。硬件冗余不等于软件冗余。两个传感器都在但如果软件只读其中一个另一个就是摆设。更糟糕的是当两个传感器数据不一致时系统没有任何机制去判断哪个是对的也没有向飞行员发出明确的冲突告警。做嵌入式测试时我们经常要验证传感器融合逻辑。一个合格的融合算法应该包含数据有效性检查范围、变化率、连续性、多源交叉验证如果有多路传感器、异常降级策略当数据不可信时切换到安全模式或交还人工控制。这些在MCAS的早期版本里要么缺失要么没有被充分测试。我自己的经验是测试传感器融合逻辑时必须专门设计一组对抗性用例故意让一路传感器输出漂移值、让两路传感器输出矛盾值、让传感器数据在阈值附近反复跳变。这些用例不是为了验证正常功能而是为了验证异常时系统不会做出危险决策。很多团队的测试用例库里这类对抗性用例占比不到5%这是远远不够的。2.3 状态机测试只覆盖了正常迁移异常路径全漏MCAS的行为可以用一个状态机来描述待机状态、监控状态、触发修正状态、退出修正状态。正常的测试思路是验证每个状态之间的迁移条件是否正确验证触发后的输出是否符合预期。但问题在于MCAS的触发条件是迎角超过阈值而迎角数据来自一个可能出错的传感器。当传感器输出错误的高迎角值时状态机会从待机直接跳到触发修正而且会反复触发。测试用例如果只用了模拟正常迎角变化的数据就永远发现不了这个问题。嵌入式软件测试里有一个基本原则状态机的测试用例必须包含非法迁移和异常保持。什么叫非法迁移比如系统在待机状态下收到了一个不应该出现的触发信号它应该忽略还是响应什么叫异常保持比如触发条件持续满足时系统是持续输出还是应该有超时退出机制MCAS的案例告诉我们当状态机的输入来自不可靠的传感器时测试用例必须包含传感器数据异常这一整类场景。具体来说至少要覆盖传感器数据超范围、传感器数据恒定不变、传感器数据高频跳变、传感器数据缓慢漂移、传感器断线后恢复。这些场景在正常的飞行中很少出现但一旦出现系统必须有确定的、安全的响应。3. 迎角传感器数据链路嵌入式测试最该盯紧的输入可信度3.1 从物理量到数字量数据链路每一环都可能引入错误迎角传感器的数据要经过一条完整的链路才能被飞控计算机使用物理感应元件→信号调理电路→模数转换→总线传输→软件解析→数据校验→融合算法→控制逻辑。这条链路上任何一环出问题最终到达控制逻辑的数据都可能是错的。做嵌入式软件测试时我们通常只能从软件层面介入但必须理解整条链路。比如模数转换的量化误差、总线传输的位翻转、软件解析时的字节序错误、数据校验的遗漏这些都是软件测试可以覆盖的。但很多团队的测试只停留在给一个模拟的传感器值看软件输出对不对这个层面完全没有覆盖数据链路上的异常。我一般会建议团队在测试传感器数据处理模块时专门做一组数据链路注入测试在软件解析层注入带有位翻转的数据包、注入超出量程的原始值、注入校验和错误的数据帧、注入延迟到达的数据。这些测试不需要真实的硬件故障只需要在软件层面模拟异常输入即可。工具上可以用ETest这类嵌入式测试工具来做数据注入和协议仿真也可以在单元测试层面用桩函数模拟异常数据。3.2 数据有效性检查范围、变化率、连续性一个都不能少一个合格的传感器数据处理模块至少应该做三层检查范围检查数据是否在物理可能的范围内。迎角传感器的量程是有限的超出量程的值应该被标记为无效。变化率检查数据的变化速率是否合理。物理世界的迎角不会在毫秒级内从-10度跳到30度如果出现这种跳变大概率是传感器故障或传输错误。连续性检查数据是否持续更新。如果传感器断线软件应该能检测到数据停止更新而不是继续使用最后一个有效值。MCAS的问题在于它似乎没有对这些检查做充分的处理。当迎角传感器输出错误的高值时系统直接采信并触发修正。如果软件层有变化率检查一个突然跳变到高值的传感器数据应该被标记为可疑而不是直接用于控制决策。在测试这类逻辑时我通常会设计一组边界异常的用例矩阵测试场景输入数据特征预期系统行为正常范围迎角在-5到15度之间缓慢变化正常监控不触发修正超范围高值迎角突然跳到50度标记数据无效不触发修正超范围低值迎角突然跳到-50度标记数据无效不触发修正高频跳变迎角在5和25度之间每秒跳变10次标记数据可疑降级处理数据恒定迎角保持20度不变超过10秒检测到数据停滞告警数据断线迎角数据停止更新检测到断线切换到安全模式这张表里的每一行都应该对应至少一个测试用例。如果测试用例库里没有这些那就是在赌传感器永远不会坏。3.3 多传感器冲突时的仲裁逻辑测试用例必须覆盖矛盾输入如果系统有两个迎角传感器当它们的数据不一致时系统应该怎么办这是一个典型的仲裁问题。常见的策略有取平均值、取中值、投票表决、或者直接告警并交还人工控制。MCAS的案例里两个传感器的数据不一致时系统似乎没有做出正确的仲裁而是继续依赖其中一个。这暴露了一个测试盲区多传感器系统的测试用例必须包含传感器数据矛盾的场景。我在测试这类逻辑时会专门设计一组矛盾注入用例让传感器A输出正常值传感器B输出偏大值让两个传感器都输出正常值但相位相反让两个传感器交替输出正常和异常值。然后观察系统的仲裁逻辑是否正确是否会在数据矛盾时做出危险决策。这里有一个实操心得仲裁逻辑的测试不能只看最终输出还要看中间状态。比如系统是否记录了传感器冲突事件是否向飞行员或操作员发出了告警是否在冲突持续一段时间后自动降级这些中间状态往往比最终输出更能反映系统的安全性。4. 人机交互与告警设计测试视角下最容易被低估的环节4.1 系统在自作主张时操作员是否知情MCAS最受诟病的一点是它在触发修正时飞行员并不知情。系统在后台自动调整飞机姿态而驾驶舱里没有任何明确的指示告诉飞行员现在MCAS正在工作。当飞行员感觉到飞机异常下压时他们不知道这是系统行为还是飞机本身的问题导致排查方向错误。从软件测试的角度看这是一个典型的人机交互反馈缺失问题。嵌入式系统在自动运行时必须让操作员知道系统当前的状态和正在执行的动作。测试这类逻辑时不能只验证系统功能是否正确还要验证系统状态是否可感知。我通常会把这类测试分成三个层次状态可见性测试系统在自动模式下运行时界面上是否有明确的指示指示是否准确反映了系统状态告警及时性测试当系统检测到异常或进入降级模式时告警是否在合理时间内发出告警是否足够醒目操作可干预性测试操作员能否在系统自动运行时接管控制接管后系统是否正确退出自动模式MCAS的案例里这三个层次的测试可能都没有充分覆盖。飞行员不知道系统在工作不知道系统为什么工作也不知道如何可靠地关闭系统。这在安全关键的嵌入式系统里是致命的。4.2 告警抑制与告警泛滥测试用例要覆盖多告警并发嵌入式系统里经常出现的一个问题是告警泛滥。当多个异常同时发生时系统可能同时触发大量告警操作员根本来不及处理。MCAS的案例里当迎角传感器故障时可能同时触发了失速告警、MCAS修正、配平告警等多个信号飞行员在短时间内面对大量信息很难快速判断核心问题。测试告警系统时必须专门设计多告警并发的用例模拟多个传感器同时故障、模拟多个子系统同时进入异常状态、模拟告警在短时间内密集触发。然后验证告警的优先级排序是否正确是否有告警抑制机制避免重复告警操作员能否快速识别最关键的告警我见过一些团队的告警测试只验证了单个告警触发时是否正确显示完全没有覆盖多告警场景。这在安全关键的嵌入式系统里是一个巨大的风险。4.3 操作员响应时间测试不能只验证功能正确还要验证人能不能反应过来嵌入式软件测试里有一个容易被忽略的维度时间。不是软件执行的时间而是人机交互的时间。系统发出告警后操作员需要多长时间才能理解并做出正确响应这个时间是否在安全允许的范围内MCAS的案例里从系统触发修正到飞行员意识到问题并采取行动中间的时间窗口非常短。如果测试阶段能够模拟这个时间压力验证飞行员在合理时间内能否正确响应也许能更早发现人机交互设计的问题。在实际项目中我建议对关键告警做响应时间测试邀请实际操作员参与模拟告警触发记录他们从看到告警到做出正确操作的时间。如果这个时间超过了安全阈值就需要重新设计告警的呈现方式或简化操作流程。5. 用ETest这类工具把异常注入变成常规测试手段5.1 为什么嵌入式测试必须依赖工具做故障注入嵌入式软件的测试和普通应用软件有一个根本区别普通软件可以很方便地模拟各种输入但嵌入式软件的输入来自物理世界很多异常场景在真实环境中很难复现。你不可能为了测试传感器故障真的去把飞机上的传感器弄坏。这就是故障注入工具的价值所在。ETest这类嵌入式测试工具的核心能力是可以在软件层面模拟各种物理输入和总线数据包括正常数据、边界数据、异常数据、故障数据。测试人员可以通过工具向被测系统注入各种在真实环境中很难出现但必须被处理的场景。我在项目里用ETest做的最多的事情就是构造异常数据包。比如模拟一个迎角传感器在正常飞行中突然输出满量程值或者模拟两个传感器输出矛盾值或者模拟总线上的数据帧丢失。这些场景用真实硬件很难复现但用工具可以精确控制、反复执行。5.2 故障注入测试用例的设计方法故障注入不是随便乱注需要有系统的方法。我通常按照以下步骤来设计识别关键输入列出系统所有来自外部的输入包括传感器数据、总线消息、操作员指令等。分析每个输入的失效模式对每个输入分析它可能出现的异常类型比如超范围、跳变、断线、延迟、错误校验等。设计注入用例针对每种失效模式设计具体的注入用例明确注入的数据特征和预期的系统响应。定义通过标准明确系统在异常输入下的正确行为是什么比如标记数据无效切换到降级模式发出告警等。自动化执行用工具把这些用例脚本化实现自动化执行和结果比对。这套方法的核心思想是不要等故障发生了才去想怎么处理而是在测试阶段主动把故障注入进去验证系统的容错能力。5.3 从功能测试到鲁棒性测试的思维转变很多嵌入式测试团队的测试用例库80%以上都是功能测试验证正常输入下系统输出是否正确。这当然重要但远远不够。MCAS的案例告诉我们真正致命的问题往往出现在异常场景下。我建议团队在测试用例设计时强制要求每个功能模块至少有30%的用例是异常场景和边界场景。具体来说输入数据的边界值最大值、最小值、刚好超出范围的值输入数据的异常模式跳变、恒定、断线、噪声输入数据的时序异常延迟、乱序、重复系统状态的异常迁移非法状态跳转、状态保持超时资源异常内存不足、CPU过载、总线拥塞这些用例不一定每次回归都跑但在关键版本发布前必须覆盖。工具的价值就在于让这些异常用例可以自动化执行而不是依赖测试人员手工构造。6. 需求追溯矩阵让每一个安全假设都有对应的测试用例6.1 从需求到测试用例的追溯断链是怎么发生的MCAS的案例里需求文档中假设迎角数据可靠这句话在测试阶段没有被转化为具体的测试用例。这就是需求追溯断链的典型表现需求里写了一个假设但没有人去验证这个假设在异常情况下是否成立。需求追溯矩阵RTM是解决这个问题的标准工具。它的核心逻辑是每一条需求都必须对应至少一个测试用例每个测试用例都必须追溯到至少一条需求。但在实际项目中RTM经常流于形式——测试人员为了填表而填表没有真正去检查这条需求是否被充分验证了。我的经验是RTM要真正发挥作用必须在需求评审阶段就介入。测试人员要逐条阅读需求对每一条需求问三个问题这条需求的正常场景是什么对应的测试用例是什么这条需求的异常场景是什么对应的测试用例是什么这条需求里有没有隐含假设如果有假设不成立时系统应该怎样第三个问题是最关键的。MCAS的需求里假设迎角数据可靠就是一个隐含假设如果测试人员在评审时追问了这个问题后面的测试用例就会覆盖传感器故障场景。6.2 安全关键需求的测试覆盖标准对于安全关键的嵌入式软件测试覆盖不能只满足于语句覆盖或分支覆盖。MCAS的案例告诉我们即使代码覆盖率达到了100%如果测试用例没有覆盖异常输入场景系统仍然可能在真实环境中失效。我通常建议对安全关键模块采用以下覆盖标准覆盖类型最低要求说明语句覆盖100%每行代码至少执行一次分支覆盖100%每个判断的每个分支至少执行一次条件组合覆盖关键模块100%多个条件组合的每种情况都覆盖异常路径覆盖100%每个异常处理路径至少有一个用例边界值覆盖100%每个输入的边界值都有用例故障注入覆盖关键输入100%每个关键输入的典型故障模式都有用例这张表里的后三项是很多团队容易忽略的。特别是故障注入覆盖需要工具支持才能高效执行。6.3 把假设变成可测试的断言需求里的隐含假设之所以危险是因为它们没有被显式地表达出来也就无法被测试。解决方法是在需求分析阶段把所有隐含假设都转化为显式的、可测试的断言。比如假设迎角数据可靠可以转化为断言1当迎角数据超出物理量程时系统应标记数据无效并在100ms内切换到降级模式。断言2当迎角数据变化率超过物理可能的最大值时系统应标记数据可疑并发出告警。断言3当两路迎角传感器数据偏差超过阈值时系统应发出传感器冲突告警并交还人工控制。这些断言一旦写出来测试用例就自然而然地产生了。测试人员不需要自己去想异常场景只需要验证这些断言是否成立。7. 从事故案例反推嵌入式测试流程的改进点7.1 测试左移在需求阶段就介入异常场景分析MCAS的教训之一是异常场景的分析做得太晚。如果等到测试阶段才去想传感器坏了怎么办往往已经来不及修改需求或设计了。测试左移的核心思想就是让测试人员在需求阶段就介入专门负责异常场景的分析和用例设计。具体怎么做我通常会在需求评审会上带一张异常场景检查清单逐条对照这个功能的输入来自哪里输入可能以什么方式失效输入失效时系统应该怎样需求里写了吗系统有没有冗余设计冗余在软件层是否被正确使用系统自动运行时操作员是否知情能否干预异常发生时告警是否明确操作员能否在合理时间内响应这张清单上的每一个问题都应该在需求文档里有明确的答案。如果没有就是需求缺陷必须在开发开始前补上。7.2 独立验证测试团队不能只依赖开发团队提供的正常路径MCAS的案例还暴露了一个问题如果测试团队只按照开发团队提供的接口文档和正常流程来设计用例就会遗漏大量异常场景。独立验证意味着测试团队要有自己的能力去分析系统的失效模式而不是被动地接受开发团队的假设。我建议测试团队在项目早期就做一份失效模式与影响分析FMEA列出系统所有可能的失效模式、每个失效模式的影响、以及对应的检测和缓解措施。这份分析不需要很复杂但必须由测试团队独立完成不能直接复制开发团队的设计文档。7.3 回归测试策略每次修改后必须重跑异常用例MCAS的案例里系统在事故后进行了修改但修改本身也引入了新的问题。这提醒我们嵌入式软件的回归测试不能只跑功能用例必须把异常用例和故障注入用例纳入常规回归集。我的做法是把测试用例分成三个等级核心回归集每次代码提交都必须跑包括关键功能用例和关键异常用例。完整回归集每个版本发布前跑包括所有功能用例、边界用例、异常用例。专项测试集针对特定修改或特定风险场景按需执行。核心回归集里必须包含故障注入用例。比如传感器数据异常、总线通信中断、状态机异常迁移这些场景每次修改后都要重新验证。8. 给正在做嵌入式测试项目的人几条实在建议8.1 面试和实战中怎么讲清楚这个案例的价值如果你正在准备软件测试面试题或者想在软件测试项目实战中展示自己的深度MCAS这个案例是一个非常好的素材。但讲的时候不要停留在传感器数据没校验这个层面要往深里挖需求追溯断链需求里的隐含假设没有被转化为测试用例。冗余设计失效硬件冗余在软件层没有被正确使用。状态机测试盲区异常迁移和异常保持没有被覆盖。人机交互缺失系统自动运行时操作员不知情、无法干预。测试工具缺位没有用故障注入工具系统性地验证异常场景。这五个点每一个都可以展开成一个独立的测试改进方向。面试时如果能把这五个点讲清楚并且给出具体的测试策略和工具方案比背一百道面试题都有用。8.2 日常测试工作中最容易忽略的三个检查点根据我自己的经验嵌入式测试日常工作中最容易忽略的三个检查点是第一传感器数据的有效性检查。很多团队只测了数据正常时功能正确没有测数据异常时系统是否安全。建议每个传感器输入都至少设计5个异常用例超范围、跳变、恒定、断线、噪声。第二状态机的异常路径。很多团队只测了正常的状态迁移没有测非法迁移和异常保持。建议每个状态机都画一张完整的状态迁移图标出所有合法迁移和非法迁移然后为每条非法迁移设计一个测试用例。第三告警的并发场景。很多团队只测了单个告警没有测多告警并发。建议设计一组告警风暴用例模拟多个异常同时发生验证系统的告警优先级和抑制机制。8.3 工具选型的务实建议嵌入式测试工具的选择不要追求大而全要根据项目实际需求来。ETest这类工具的优势在于协议仿真和故障注入能力强适合做总线通信、传感器数据模拟、异常场景注入。但如果你的项目主要是单元测试可能更适合用普通的单元测试框架加桩函数。我的建议是至少要有一种工具能支持故障注入。因为异常场景的测试如果全靠手工构造效率极低且容易遗漏。工具的价值不在于替代测试人员的思考而在于让测试人员设计的异常用例能够被高效、可重复地执行。另外工具的选择要考虑团队的学习成本。我见过一些团队买了很贵的测试工具但因为学习曲线太陡最后只有一两个人会用工具的价值完全没有发挥出来。选工具时优先选那些文档齐全、社区活跃、上手快的让团队里大多数人都能用起来比选一个功能强大但只有专家会用的工具更有价值。8.4 从测试执行者到质量设计者的思维升级最后想说的是MCAS的案例给测试人员最大的启示不是某个具体的技术点而是一种思维方式的转变。测试人员不能只做需求说什么我就测什么的执行者而要做系统可能在什么地方失效的设计者。这意味着测试人员要主动去理解系统的物理背景、理解传感器的特性、理解操作员的使用场景、理解异常情况下的安全要求。这些知识不在需求文档里但它们是设计高质量测试用例的基础。我在带新人的时候经常让他们做一件事拿到一个功能需求后先不要看开发写的代码自己先想一遍如果我是这个系统的设计者我会担心什么。然后带着这些担心去设计测试用例。这样设计出来的用例往往比单纯按照需求文档写的用例更能发现深层问题。嵌入式软件测试的门槛不在于工具用得多熟练而在于对系统失效模式的理解有多深。MCAS的案例值得每一个做嵌入式测试的人反复琢磨因为它提醒我们测试的终极目标不是证明系统能正常工作而是确保系统在异常情况下不会造成伤害。这个目标听起来简单但真正做到需要方法论、工具和思维方式的全面升级。
返回列表