白皮书

敏捷行为驱动开发和测试驱动开发与基于模型的设计相得益彰

作者:Hugo de Kock 和 Jim Ross

基于模型的设计以及行为驱动开发和测试驱动开发的敏捷实践,在软件密集型的现代大型开发项目中发挥着重要作用。本白皮书阐述了一种基于与工程组织多年合作所确立的最佳实践的组合方法。

部分

简介

您多久才会遇到一次从项目一开始便获得正确完整的需求的情况?您是否曾成功地验证了模型和代码符合既定需求,甚至结构化覆盖率也达到了 100%,但最终的系统却没有按照预期交付?本白皮书利用了规模化敏捷框架 (SAFe) [1] 中的多项“效益假设”。简而言之,效益假设明确指出了建议的可衡量效益。在本文中,我们会运用特定语言来组织断言或声明,指出某项实践将根据规定的标准交付结果。下文将通过基于模型的设计、行为驱动开发 (BDD) 和测试驱动开发 (TDD) 的相关效益假设,就组合方法为何非常有用提供一些见解。

基于模型的设计

Simulink® 和 Stateflow® 等建模产品以基于模型的设计而闻名,并在 ISO® 26262 等安全关键型系统开发标准中得到了引用。凭借基于模型的设计,工程师能够在比代码更高的抽象级别工作,从而开发复杂的系统。模型和状态机具有可视性,这使得表达和理解复杂关系和数据流变得更加容易。您可以使用模型作为可执行规范,以通过系统仿真对需求进行早期验证,然后从这些模型自动生成文档和产品级代码。随着当今系统的日益复杂,早期虚拟开发(包括系统仿真)也愈加重要。

效益假设: 我认为,具有系统仿真和产品级代码生成功能的基于模型的设计,将有助于提高大型软件项目的生产率和效率,这可以通过需求的正确性和所生成的产品级代码的质量来衡量

行为驱动开发 (BDD)

按照我们开发人员和产品负责人以往的经验,需求通常从项目伊始就不完整、不正确,或者是在没有明确的验收标准的情况下采集的。如果需求存在这些问题,则将很难实现,也很难验证。我们认为,需求挖掘需要团队协作,涉及多个角色及其职责;通过需求挖掘,可以清楚地了解需要做什么为什么要这样做,以及什么时候已经做得足够好。这是一个迭代的过程。最佳做法是考虑最终用户的使用场景,让客户参与其中,并在较短的时间间隔内演示工作原型(例如使用系统仿真)。在看到一些应用实例之前,通常很难知道需要采取哪种行为。我们认为,BDD 是一种用于获得具有验收标准的需求基线的有效方法。

除了对需求进行早期迭代和验证外,BDD 还为后续的持续验证打下了基础。为 BDD 创建的系统测试用例可以自动执行,并用于产品的整个生命周期,旨在确保更改或新需求不会导致现有功能出现问题。

规模化敏捷框架 (SAFe) 是一种广泛应用于各行各业的领先框架,无论是小公司还是大企业都在使用。根据 SAFe,“行为驱动开发 (BDD) 是一种测试优先的敏捷测试实践,它通过在指定系统行为之前或之中定义(并可能自动执行)测试来实现内建质量。BDD 是一个企业和敏捷团队就需求达成共识的协作过程。BDD 测试并不关注内部实现,它们是面向企业的场景。这些场景尝试从用户的角度描述故事、特征或功能的行为。”[1]

在 BDD 中,重点在于获取正确的需求,并确保设计符合这些需求,这是一个迭代的过程。通过将 BDD 与基于模型的设计的系统仿真功能相结合,可以在早期阶段创建虚拟原型,并从最终用户的角度了解系统行为,以及验收标准的履行情况。

BDD 需要对与控制器(软件)和被控对象(非软件)相关的系统的功能建模,并将它们结合在一起进行仿真,以了解系统的完整行为。

效益假设:我认为,行为驱动开发 (BDD) 与基于模型的设计的系统仿真功能相结合,将有助于实现内建质量,这可以通过能够高效挖掘正确的系统需求和尽早验证系统的正确行为来衡量

测试驱动开发 (TDD)

在敏捷开发背景下,TDD 理念和实践就是在实现系统的一部分之前先构建和执行测试 [2][3]。尽管 TDD 实践可能以手写代码而广为人知,即遵循极限编程 (XP) 实践,但它也非常适用于基于模型的设计,例如遵循极限建模 (XM) 实践 [3]

根据 SAFe,“TDD 实践主要设计用于开发人员编写的单元测试环境。单元测试是“白盒测试”的一种形式,因为它们用于测试系统的内部,并且侧重于结构化覆盖率。通过一组丰富的单元测试,可以确保重构工作不会引入新的错误,从而让开发人员能够专注于不断改进他们的设计。重构有助于打造质量,因为它让设计可随着时间的推移进行调整,并满足解决方案不断变化的要求。” [2]

将 TDD 与基于模型的设计结合使用不同于将 TDD 应用于手写代码,主要是因为模型可以用作可执行规范。因此,模型支持通过生成测试向量进行早期验证,并且具有公认的好处,即生成代码和文档。将 TDD 与基于模型的设计相结合,是一种公认的有效方法,可用来实现可靠的软件密集型系统 [4]。而 Simulink 提供了一种环境,可简化用来执行这项测试的模拟环境的创建和维护。

效益假设:我认为,测试驱动开发 (TDD) 与基于模型的设计的可执行规范(仿真)功能、模型级验证和代码生成功能相结合,将有助于实现内建质量,这可以通过单元级别的已验证模型正确行为和生成的代码来衡量

部分

需求的作用

本白皮书重点介绍 BDD 和 TDD 对基于模型的设计的价值。但是,如果没有好的需求,则无法成功运用这两种实践。

好的需求的重要性

BDD 和 TDD 都以带有验收标准的需求为起点。对于 BDD,这意味着要从了解所需的功能或系统行为入手。而对于 TDD,这通常意味着要在解决方案选定的情况下,从系统行为需求中派生更详细的规范。

无论是哪种情况,好的需求都必须是可测试的,即可以定义输入和相应的预期结果。若要执行测试,必须能够衡量响应能力,并确定该响应是否符合预期。

如何编写好的需求?

下表总结了一些编写需求的敏捷最佳实践。您可以创建用例图 [5] 作为可视化概述,然后根据下面的公式详细说明每个用例,这样做可能也很有用。拥有每个需求的验收标准非常重要,因为这些标准是定义测试的依据。如果没有明确的验收标准,则无法编写任何测试,因此也就不可能执行 BDD 和 TDD。

效益假设 我认为 <功能>
将会产生 <结果>,这可以通过
<指标> 来衡量。
传达业务和客户相关性信息 为什么?
客户案例 作为 <角色>,
我想 <需求>,
以便能够 <目标>。
提供上下文 什么?
验收标准 如果 <前提条件>,
当 <触发操作> 时,
则将 <预期结果>。
传达质量保证标准信息 什么时候才能做得足够好?

表 1. 敏捷 Gherkin/需求模板。

部分

BDD 和 TDD 确实值得使用吗?

根据 SAFe,“TDD 会创建大量开发人员级别的测试,这使得质量保证和测试人员可以专注于解决其他测试难题。他们不用花时间寻找和报告代码级别的 Bug,可以将重点放在更复杂的行为和组件之间的交互上。TDD 与 BDD 都属于实现内建质量的“测试优先”方法。优先编写测试可以创建一个更加平衡的测试组合,其中既有许多快速、自动化的开发测试,也不乏少量缓慢的手动端到端测试。” [1]

从工具的角度来看,当开发人员使用相同的环境和语言来编写测试并进行基于模型的设计时,这个过程变得更容易了,而且执行 BDD 和 TDD 的心理障碍也小了。开发人员不需要仅仅为了编写测试用例而学习另一种语言或在不同的环境之间切换了。如果负责其他测试类型和级别的测试人员或系统工程师也在使用相同的工具和环境,则不同角色之间的沟通效率可能也会得到大大提高。如果测试人员检测到错误,则开发人员能够轻松地理解该测试用例,并且可能会直接开始调试。

效益假设: 我认为,使用单一开发环境和语言进行基于模型的设计(包括测试),将有助于实现不同角色(例如系统工程师、开发人员和测试人员)之间的高效沟通,加速消除错误需求和软件 Bug,以及改善开发流程,这可以通过系统和单元测试各阶段(例如桌面仿真、软件在环、处理器在环、硬件在环测试)的重用测试用例和评估数来衡量

部分

BDD 和 TDD 是如何与基于模型的设计结合使用的?

BDD 和 TDD 均采用“测试优先”的方法。首先,在进行任何测试之前,要有明确的需求,更具体地说是明确的验收标准,这一点至关重要。其次,需要有最基本的架构或设计,例如具有输入、输出、可能的函数调用以及一些系统参数的子系统。只有这样,才能开始构建带有评估的测试框架和测试用例。

下表通过一个简单的例子阐明了从需求(包括明确的验收标准)开始的必要性。

需求 问题
软件要检测电池电压较低的情况。 这项需求模糊且不完整,也没有明确的验收标准。
软件要检测低于 4 伏的电池电压并通知用户。 该需求现在有明确的验收标准,但仍然不完整。
作为电子设备用户,
我想知道我的设备的电池电压何时较低,
以便我可以用充电器为其充电。

设备已开机,
电池电压降到 4 伏以下超过 3 秒时,
软件应通知用户。

这项需求似乎很完整,且有明确的验收标准。


我们承认,经过进一步的分析,可能会确定需要进行额外的说明,也可能会确定该需求不正确,但我们认为,这足以用于演示目的。

图 1 是 BDD 和 TDD 生命周期的图形表示,其中包含一些调整,将基于模型的设计和最佳实践的好处纳入在内。

本白皮书中所述 TDD 和 BDD 生命周期步骤的流程图。这些步骤会在针对系统测试的 BDD 和针对组件测试的 TDD 之间切换。

图 1. 使用 BDD 的 TDD 和 MBD 的生命周期流程图。

本白皮书的后续部分将详细介绍图 1 所示流程图中的活动和目的。其中,每个部分都将确定涉及的典型角色,以 (R) 指明“负责任的”角色。除了每个阶段的详细活动,还将列出可用于执行这些任务的适用 MATLAB® 和 Simulink 产品。

阶段 A:分析系统需求

第一个阶段是定义系统需求。通常,最好先确定利益相关方的需求和以客户为中心的用例。在这一探索阶段,可以采集形式化的系统需求,包括验收标准。最后,与利益相关方一起审查系统需求和用例图。

涉及的典型角色 详细信息/备注 适用的产品
  • 产品负责人 (R)
  • 客户和其他利益相关方
  • 敏捷团队(系统工程师、软件架构师、开发人员、测试人员)
  • 使用以客户为中心的用例图。
  • 使用表 1 所示的敏捷 Gherkin。
  • Requirements Toolbox
  • System Composer
  • Simulink
  • Stateflow

阶段 B:定义系统架构

在需求和验收标准明确后,便可进行系统架构分析。系统应分为明确定义的组件和接口。请注意,对于大型或复杂系统,这可能是一个迭代的过程:高级组件会被用来表示整个产品的子系统,而这些子系统本身也会使用其自身的内部组件和接口进行架构定义。在每一层,为每个组件分配需求。

基于架构创建系统模型。在系统架构的每一层,创建骨架模型(或重用现有模型),并根据该架构将它们连接起来。生成的系统模型和一组组件模型将用于后续阶段的 BDD 和 TDD。

涉及的典型角色 详细信息/备注 适用的产品
  • 软件架构师 (R)
  • 敏捷团队(系统工程师、开发人员、测试人员)
  • 骨架模型仅适用于新功能。
  • 需要更新现有架构,以防需要新接口。
  • Requirements Toolbox
  • System Composer
  • Simulink
  • Stateflow

阶段 C:编写系统测试用例

当需要的最基本架构准备就绪后,便可开始编写系统级测试用例。其目标是创建系统级的测试用例和评估。如果组件中缺乏功能,则会导致这些用例和评估失败。根据我们的经验,在这个阶段编写系统测试用例可以实现双重目的。第一个目的显而易见,那就是有了这些测试用例,便可支持 BDD。第二个目的是,编写系统测试用例时,通常会发现需求中存在的其他问题或缺漏。这其实是对系统需求的第二次审查,往往也是更加详细的审查。从 BDD 的角度来看,这一点在涉及到验收标准时尤为如此。

此阶段所需执行的步骤之一是,创建仿真系统功能所需的被控对象和环境模型。其他步骤包括为系统模型创建测试框架,定义输入序列以执行在“阶段 A:分析系统需求”中获得的用例。使用验收标准定义可重复的可执行评估。此时,所有非平凡评估都应该会失败。

涉及的典型角色 详细信息/备注 适用的产品
  • 系统工程师 (R)
  • 敏捷团队(系统工程师、软件架构师、开发人员、测试人员)
  • 在此阶段,通常需要详细了解物理系统并创建被控对象模型。
  • 最好在此阶段自动执行测试用例和评估,因为它们可在 BDD 的各个阶段重复使用。
  • 确认测试用例失败表明,测试用例捕获了错误行为或者功能缺失情况。
  • Requirements Toolbox
  • Simulink Test
  • Simulink Design Verifier
  • Simscape
  • Simulink
  • Stateflow

阶段 D:编写组件测试用例

一旦完成系统级测试用例,就应启动实际的测试驱动开发阶段。与系统测试用例一样,编写组件测试用例也有两个目的:支持 TDD 和审查为组件分配的需求。编写测试用例同建模或编写代码一样,都需要对需求进行更深入的研究,而在这个过程中,往往会发现遗漏的缺口和问题。自动执行测试用例可确保验收标准的完整性和可测试性。

涉及的典型角色 详细信息/备注 适用的产品
  • 开发人员 (R)
  • 测试人员
  • 将验收标准转化为自动评估可能会很耗时,并且这些评估需要随着需求的变化而发生更改。然而,自动评估必不可少。作为 TDD 理念的一部分,它有助于阐明需求和验收标准(如果它们仍不明确)。
  • 带有自动评估的测试用例将在 TDD 的各个阶段重复使用。
  • 通过将测试用例和评估与需求关联起来,可进行变更影响分析并确保一致性。
  • 确认测试用例失败表明,测试用例捕获了错误行为或者功能缺失情况。
  • Requirements Toolbox
  • Simulink Test
  • Simulink Design Verifier
  • Simscape
  • Simulink
  • Stateflow

提示:对于逻辑驱动的软件需求,使用 Requirements Toolbox™ 中的 Requirements Table 模块 [6],因为该模块在测试框架中可作为可执行评估重复使用。而对于基于时间或价值的需求,使用 Model Verification 和 Simulink Design Verifier™ 库中的模块,在测试框架中创建可重用的自动评估。

提示:使用 Simscape™ 和 System Identification Toolbox™ 的丰富功能,可以创建能够在测试框架中用作测试替身的被控对象模型。

阶段 E:设计详细组件

如果所有组件测试用例都失败,则进行详细组件设计,以添加每个组件所需的功能。在此阶段,多名开发人员可以处理多个组件。“结对编程”是一种可能会采用的敏捷实践;换句话说,编程人员以快速同行评审的方式协同工作,以提高编程质量。

涉及的典型角色 详细信息/备注 适用的产品
  • 开发人员 (R)
  • 测试人员
  • 一次只为一项需求建模,使建模刚好足以通过该需求的测试。在测试用例成功之前,执行所需的最小实现。不要过度设计解决方案。解决方案仍扮演着原型角色。
  • 激活并使用编辑时检查 (Simulink Check™) 以避免以后返工。
  • 主要想法是拥有刚好够用的功能,并且让人自然而然地觉得该功能已经过验证,支持系统仿真和 BDD。
  • 虽然在此阶段测量范围限定于需求的结构化覆盖率 [7] 是可选的,但这样做可以让您对缺失的需求或测试用例有所了解。
  • Simulink
  • Stateflow
  • AUTOSAR Blockset
  • Simulink Check
  • Requirements Toolbox
  • Simulink Test
  • Simulink Design Verifier
  • Simulink Coverage

阶段 F:仿真和测试系统

如果所有组件的功能都刚好足以通过验收标准,则进入下一个阶段,即系统仿真。由于系统模型是通过集成阶段 B 中的骨架模型创建的,因此,完成详细组件设计阶段就意味着,系统模型现在应该能够提供系统行为的有用仿真。如果系统评估失败,则很容易得出这样的结论:实现所需功能的模型存在问题。尽管情况可能如此,但 BDD 测试用例失败也可能表明测试用例本身存在问题,甚至可以说需求存在问题。事实上,如前所述,BDD 的价值完全体现于能够在开发早期发现需求问题。

涉及的典型角色 详细信息/备注 适用的产品
  • 系统工程师 (R)
  • 敏捷团队(系统工程师、软件架构师、开发人员、测试人员)
  • 此步骤还有助于对需求和系统行为进行早期验证。
  • 先进行此级别的原型设计,然后检查是否值得在既定方法上花费更多时间,可以避免大量返工。
  • Simulink Test
  • Requirements Toolbox
  • System Composer

阶段 G:使用真实硬件进行快速原型设计

在阶段 F 中,MIL 系统仿真可以树立利益相关方对预期系统功能的信心,并及早为他们提供相关反馈。这是执行完整性检查的机会,也是有可能纠正不完整或不正确的需求或不正确的验收标准的机会。使用被控对象模型进行 MIL 系统仿真的优势包括一致的可用性,以及通过对变体的支持实现整个产品线的测试,只不过后者可能需要提前做些规划。然而,被控对象模型通常是现实应用的简化版,而且捕获影响解决方案的相关系统行为有时很难或耗费时间。使用快速原型设计可以最大程度减少这些限制。换句话说,使用旁通技术(无论使不使用外部实时硬件)来实时测试真实系统中的预期组件 [8]

涉及的典型角色 详细信息/备注 适用的产品
开发人员 (R)
  • 此步骤还有助于将预期系统中实时运行的原型功能的早期验证用作概念验证。
  • 手动检查包含数百个信号的大型测量文件可能非常耗时且容易出错。在对测量结果进行后处理的过程中重用现有的自动评估可以加速评估并提高评估质量。
  • Simulink Real-Time
  • Speedgoat Rapid Prototyping Hardware
  • Vehicle Network Toolbox
  • Simulink Test
  • MATLAB(编写脚本)

阶段 H:实现组件

在阶段 E、F 和 G 中,您已经对组件原型和集成系统行为有了信心,现在便可通过代码生成来详细描述要实现的模型了。如果拥有一组自动化测试,则可自由地重构模型来实现长期的可维护性。为了生成适于量产微处理器的代码,可以对实现数据类型和存储类加以定义。在阶段 F 和 G 中验证了需求后,应确保正确关联这些需求与模型,以满足可追溯性需要。最后,加强测试以实现覆盖率目标。

涉及的典型角色 详细信息/备注 适用的产品
  • 开发人员 (R)
  • 测试人员 (R)
  • 重构模型,即不改变行为(除了优化实现数据类型)。
  • 重构模型(例如改进命名,添加模型注释)。
  • 重构模型,检查重复部分(克隆)。
  • 运行建模指南检查并修复错误。
  • 运行设计错误检测并修复错误(例如防止除以零错误)。
  • 生成代码并对代码运行静态分析。
  • 将刚经过验证的需求关联到模型,以实现可追溯性。
  • 执行等效性测试(MIL、SIL、PIL),无论是单元级测试,还是系统级测试。
  • Simulink
  • Requirements Toolbox
  • Stateflow
  • Embedded Coder
  • AUTOSAR Blockset
  • Simulink Test
  • Simulink Check
  • Simulink Design Verifier
  • Polyspace Bug Finder
  • Polyspace Code Prover

提示:当项目处于更成熟的阶段时,将一些手动活动从阶段 H 转移到阶段 E,并使用持续集成平台 [9] 自动执行阶段 E 和 F 中的所有活动:模型检查、单元级和系统级测试以及代码生成。

阶段 I:运行系统 SIL 测试

当所有组件模型都针对代码生成进行了优化,根据组件验收标准进行了验证,并且接受了等效性(MIL 与 SIL 与 PIL)验证后,便可以进入下一个阶段。在此阶段,可根据系统级验收标准在 SIL 模式下测试系统级组件集成。除了根据需求进行测试外,还可以将 SIL 测试的结果与阶段 F 的 MIL 测试结果进行比较。

涉及的典型角色 详细信息/备注 适用的产品
测试人员 (R)
  • 此步骤还有助于对系统(包括预期的产品级代码)进行早期验证。
  • Simulink Test
  • Requirements Toolbox
  • System Composer

阶段 J:运行系统 HIL 测试

在交付了为每个组件(或软件组合)生成的代码之后,便可针对目标硬件集成并编译软件。目标硬件(例如电子控制单元)可在环境中实时运行,而系统的其余部分可以进行实时仿真 [10]。转换现有的 MIL 测试以供在 HIL 中使用,可以提供等效性测试功能。请务必测试在 MIL 或 SIL 中无法测试的其他软件方面。例如,可以在 HIL 测试中评估故障代码、通信消息和资源利用率。这可能需要额外的测试用例或评估。

涉及的典型角色 详细信息/备注 适用的产品
测试人员 (R)
  • 此步骤还有助于在仿真环境 (HIL) 中对目标硬件上实时运行的已编译软件进行早期验证。
  • 使用 Simulink Test™ 将现有测试用例转换为实时测试用例。
  • 通过使用生产线束和控制器,可以全面测试在该过程的早期阶段无法测试的软件和集成。
  • 重用之前在阶段 C 中开发的现有被控对象模型和评估标准。
  • Simulink Real-Time
  • Simulink Test
  • 用于运行环境模型的 Speedgoat 实时硬件
部分

结束语

我们认为,行为驱动开发、测试驱动开发和基于模型的设计相得益彰。通过将它们结合使用,您可以高效地开发并交付高质量的产品。MathWorks 创建了一个完整的工具链,使得在一个工具环境中将 BDD、TDD 和 MBD 结合使用成为了可能。MathWorks 和 Speedgoat 强强联合,旨在为快速原型设计和 HIL 测试提供完整的解决方案。

效益假设: 我认为,在 MATLAB 和 Simulink 的 MBD 环境中执行 BDD 和 TDD,将有助于在组件和系统级别创建大量的测试用例,这使得项目能够定期升级到新版本的工具链(小幅增加),从而便于您使用最先进的开发环境,这可以通过升级工具链所需的时间来衡量

部分

参考文献

[1] “Behavior-Driven Development.”Scaled Agile Framework,
https://scaledagileframework.com/behavior-driven-development/.Accessed 08/22.
[2] “Test-Driven Development,” Scaled Agile Framework,
https://scaledagileframework.com/test-driven-development/.Accessed 08/22.
[3] Shekoufeh, Kolahdouz-Rahimi, et al. “eXtreme Modeling: an approach to agile model-based development.” Journal of Computing and Security 6, no. 2 (July 2019):42–52.
[4] Fu, Yujian et al.“Model-Based Test-Driven Cyber-Physical System Design.”SoutheastCon 2018, IEEE, 2018, pp. 1–6.DOI.org (Crossref),
https://doi.org/10.1109/SECON.2018.8479080.
[5] Jacobson, Dr. Ivar, et al. Use-Case 2.0:The Guide to Succeeding with Use Cases.Ivar Jacobson International, 2011.
[6] “Use a Requirements Table Block to Create Formal Requirements,” MathWorks, accessed 2022.
https://nl.mathworks.com/help/slrequirements/ug/use-requirements-table-block.html.
[7] “Assess Coverage Results from Requirements-Based Tests,” MathWorks, accessed 2022.
https://nl.mathworks.com/help/slcoverage/ug/assess-coverage-results-from-requirements-based-tests.html.
[8] Pablo Romero Cumbreras and Tjorben Gross, “Model-based calibration testing and ECU bypassing with XCP using Simulink Real-Time and Speedgoat target hardware,” MathWorks, filmed September 26, 2018, video, 26:26.
[9] “Continuous Integration (CI),” MathWorks, accessed 2022.
https://nl.mathworks.com/help/matlab/continuous-integration.html.
[10] “Hardware-in-the-Loop,” Speedgoat, accessed 2022.
https://www.speedgoat.com/solutions/testing-workflows/hardware-in-the-loop-testing.

2023 年发布