配置 IDT 状态机 - FreeRTOS
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

配置 IDT 状态机

状态机是控制测试套件执行流的构造。它确定测试套件的开始状态,根据用户定义的规则管理状态转换,并继续转换经过这些状态,直到它达到结束状态。

如果您的测试套件不包括用户定义的状态机,IDT 将为您生成一个状态机。默认状态机执行以下功能:

  • 提供测试运行程序以选择和运行特定测试组,而不是整个测试套件。

  • 如果未选择特定测试组,则 会以随机顺序运行测试套件中的每个测试组。

  • 生成报告并打印控制台摘要,其中显示每个测试组和测试用例的测试结果。

IDT 测试套件的状态机必须满足以下条件:

  • 每个状态对应于 IDT 要采取的操作,如运行测试组或产品报告文件。

  • 转换到某个状态将执行与状态关联的操作。

  • 每个状态定义下一个状态的转换规则。

  • 结束状态必须为 SucceedFail

状态机格式

您可以使用以下模板来配置您自己的 <custom-test-suite-folder>/suite/state_machine.json 文件:

{ "Comment": "<description>", "StartAt": "<state-name>", "States": { "<state-name>": { "Type": "<state-type>", // Additional state configuration } // Required states "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }

包含值的所有字段都为必填字段,如下所述:

Comment

状态机的描述。

StartAt

IDT 开始运行测试套件的状态的名称。的值必须设置为 StartAt 对象中列出的状态之一。States

States

将用户定义的状态名称映射到有效 IDT 状态的对象。每个州/省。state-name 对象包含映射到 的有效状态的定义 state-name.

对象必须包含 StatesSucceed 状态。Fail有关有效状态的信息,请参阅有效的状态和状态定义

有效的状态和状态定义

此部分描述了可在 IDT 状态机中使用的所有有效状态的状态定义。以下某些状态支持测试用例级别的配置。但是,我们建议您在测试组级别而不是测试用例级别配置状态转换规则,除非绝对必要。

RunTask

状态从测试套件中定义的测试组运行测试用例。RunTask

{ "Type": "RunTask", "Next": "<state-name>", "TestGroup": "<group-id>", "TestCases": [ "<test-id>" ], "ResultVar": "<result-name>" }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

TestGroup

可选。要运行的测试组的 ID。如果未指定该值,IDT 将运行测试运行程序选择的测试组。

TestCases

可选。在 IDs 中指定的组中的测试用例 TestGroup 的数组。 根据 TestGroupTestCases 的值,IDT 确定测试执行行为,如下所示:

  • 如果同时指定 TestGroupTestCases,IDT 将从测试组中运行指定的测试用例。

  • 如果指定了 TestCases 但未指定 TestGroup,IDT 将运行指定的测试用例。

  • 如果指定了 TestGroup,但未指定 TestCases,IDT 将运行指定测试组中的所有测试用例。

  • 如果未指定 TestGroupTestCases,IDT 将从测试运行程序从 IDT CLI 中选择的测试组运行所有测试用例。要为测试运行程序启用组选择,您必须在 RunTask 文件中包含 Choicestatemachine.json 状态。有关其工作方式的示例,请参阅状态机示例:运行用户选择的测试组

    有关为测试运行程序启用 IDT CLI 命令的更多信息,请参阅启用 IDT CLI 命令

ResultVar

要设置的上下文变量的名称以及测试运行的结果。如果未指定 TestGroup 的值,请不要指定此值。 IDT 基于以下内容将您在 ResultVar 中定义的变量的值设置为 truefalse

  • 如果变量名称的格式为 text_text_passed,则将值设置为是否通过或跳过了第一个测试组中的所有测试。

  • 在所有其他情况下,该值设置为是否通过或跳过所有测试组中的所有测试。

通常,您将使用 RunTask 状态指定测试组 ID,而无需指定单个测试用例 IDs,以便 IDT 运行指定测试组中的所有测试用例。通过此状态运行的所有测试用例都以随机顺序并行运行。但是,如果所有测试用例都需要运行一个设备,并且只有单个设备可用,则测试用例将按顺序运行。

错误处理

如果任何指定的测试组或测试用例 IDs 无效,此状态将发出 RunTaskError 执行错误。如果状态遇到执行错误,则它还会将状态机上下文中的 hasExecutionError 变量设置为 true

Choice

状态允许您根据用户定义的条件动态将下一个状态设置为转换为 。Choice

{ "Type": "Choice", "Default": "<state-name>", "FallthroughOnError": true | false, "Choices": [ { "Expression": "<expression>", "Next": "<state-name>" } ] }

包含值的所有字段都为必填字段,如下所述:

Default

Choices 中定义的任何表达式都无法计算为 true 时,要转换为的默认状态。

FallthroughOnError

可选。指定当状态在评估表达式中遇到错误时的行为。如果您希望在评估导致错误时跳过表达式,请设置为 true。如果没有表达式匹配,则状态机转换为 Default 状态。如果未指定 FallthroughOnError 值,则它默认为 false

Choices

一组表达式和状态,用于确定在执行处于当前状态的操作后转换为哪个状态。

Choices.Expression

计算结果为布尔值的表达式字符串。如果表达式的计算结果为 true,则状态机转换为在 Choices.Next 中定义的状态。 表达式字符串从状态机上下文中检索值,然后对它们执行操作以到达布尔值。有关访问状态机上下文的信息,请参阅状态机上下文

Choices.Next

如果在 Choices.Expression 中定义的表达式的计算结果为 true,则为要转换为的状态的名称。

错误处理

状态可能在以下情况下需要错误处理:Choice

  • 选择表达式中的某些变量在状态机上下文中不存在。

  • 表达式的结果不是布尔值。

  • JSON 查找的结果不是字符串、数字或布尔值。

您不能使用 Catch 块来处理处于此状态的错误。如果要在状态机遇到错误时停止执行状态机,则必须将 FallthroughOnError 设置为 false。 但是,我们建议您将 FallthroughOnError 设置为 true,并根据您的使用案例,执行以下操作之一:

  • 如果您访问的变量在某些情况下不存在,请使用 Default 的值和额外的 Choices 块指定下一个状态。

  • 如果您访问的变量应始终存在,然后将 Default 状态设置为 Fail

Parallel

状态允许您并行定义和运行新的状态机。Parallel

{ "Type": "Parallel", "Next": "<state-name>", "Branches": [ <state-machine-definition> ] }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

Branches

要运行的状态机定义的数组。每个状态机定义必须包含自己的 StartAtSucceedFail 状态。此数组中的状态机定义无法引用自身定义之外的状态。

注意

由于每个分支状态机共享相同的状态机上下文,因此在一个分支中设置变量,然后从另一个分支中读取这些变量可能会导致意外行为。

状态仅在运行所有分支状态机后进入下一个状态。Parallel需要设备的每个状态都将等待运行,直到设备可用。如果有多个设备可用,此状态将从多个组中并行运行测试用例。如果没有足够的设备可用,则测试用例将按顺序运行。由于测试用例在并行运行时采用随机顺序运行,因此,不同的设备可用于从同一测试组运行测试。

错误处理

确保分支状态机和父状态机均转换为 Fail 状态以处理执行错误。

由于分支状态机不会将执行错误传输到父状态机,因此,您无法使用 Catch 块来处理分支状态机中的执行错误。请改为在共享状态机上下文中使用 hasExecutionErrors 值。有关其工作方式的示例,请参阅示例状态机:并行运行两个测试组

AddProductFeatures

状态允许您将产品功能添加到 IDT 生成的 AddProductFeatures 文件中。awsiotdevicetester_report.xml

产品功能是有关设备可能满足的特定标准的用户定义的信息。例如,MQTT 产品功能可以指定设备正确发布 MQTT 消息。在报告中,产品功能设置为 supportednot-supported 或自定义值,具体取决于指定的测试是否通过。

注意

状态本身不生成报告。AddProductFeatures此状态必须转换为 Report 状态才能生成报告。

{ "Type": "Parallel", "Next": "<state-name>", "Features": [ { "Feature": "<feature-name>", "Groups": [ "<group-id>" ], "OneOfGroups": [ "<group-id>" ], "TestCases": [ "<test-id>" ], "IsRequired": true | false, "ExecutionMethods": [ "<execution-method>" ] } ] }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

Features

要显示在 awsiotdevicetester_report.xml 文件中的产品功能数组。

Feature

功能的名称

FeatureValue

可选。要在报告中使用的自定义值,而不是 supported。 如果未指定该值,则根据测试结果,将特征值设置为 supportednot-supported

如果您为 FeatureValue 使用自定义值,则可以使用不同的条件测试相同的功能,并且 IDT 会连接支持条件的功能值。例如,以下摘录显示了具有两个单独的特征值的 MyFeature 功能:

... { "Feature": "MyFeature", "FeatureValue": "first-feature-supported", "Groups": ["first-feature-group"] }, { "Feature": "MyFeature", "FeatureValue": "second-feature-supported", "Groups": ["second-feature-group"] }, ...

如果两个测试组都通过,则特征值设置为 first-feature-supported, second-feature-supported

Groups

可选。测试组 IDs 的数组。 每个指定测试组中的所有测试都必须通过,才能支持该功能。

OneOfGroups

可选。测试组 IDs 的数组。 至少一个指定测试组中的所有测试都必须通过,才能支持该功能。

TestCases

可选。测试用例 IDs 的数组。 如果您指定此值,则以下情况适用:

  • 必须传递所有指定的测试用例才能支持该功能。

  • Groups 必须仅包含一个测试组 ID。

  • 不得指定 OneOfGroups

IsRequired

可选。设置为 false 可将此功能标记为报告中的可选功能。默认值为 true

ExecutionMethods

可选。与 protocol 文件中指定的 device.json 值匹配的一组执行方法。如果指定了此值,则测试运行程序必须指定与此数组中的值之一匹配的 protocol 值,以便在报告中包含该功能。如果未指定该值,则此功能将始终包含在报告中。

要使用 AddProductFeatures 状态,您必须将 ResultVar 状态的值设置为以下值之一:RunTask

  • 如果您指定了单个测试用例 IDs,则将 ResultVar 设置为 group-id_test-id_passed

  • 如果未指定单个测试用例 IDs,请将 ResultVar 设置为 group-id_passed

状态通过以下方式检查测试结果:AddProductFeatures

  • 如果您未指定任何测试用例 IDs,则每个测试组的结果均根据状态机上下文中的 group-id_passed 变量的值确定。

  • 如果您指定了测试用例 IDs,则每个测试的结果均根据状态机上下文中的 group-id_test-id_passed 变量的值确定。

错误处理

如果在此状态下提供的组 ID 不是有效的组 ID,则该状态将导致 AddProductFeaturesError 执行错误。如果状态遇到执行错误,则它还会将状态机上下文中的 hasExecutionErrors 变量设置为 true

Report

状态生成 Reportsuite-name_Report.xml 文件。awsiotdevicetester_report.xml此状态还会将报告流式传输到控制台。

{ "Type": "Report", "Next": "<state-name>" }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

您应该始终在测试执行流的末尾变为 Report 状态,以便测试运行程序可以查看测试结果。通常,此状态之后的下一个状态是 Succeed

错误处理

如果此状态在生成报告时遇到问题,则会发出 ReportError 执行错误。

LogMessage

状态生成 LogMessage 文件并将日志消息流式传输到控制台。test_manager.log

{ "Type": "LogMessage", "Next": "<state-name>" "Level": "info | warn | error" "Message": "<message>" }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

Level

要在其中创建日志消息的错误级别。如果您指定的关卡无效,此状态会生成一条错误消息并将其丢弃。

Message

要记录的消息。

SelectGroup

状态更新状态机上下文,以指示选择了哪些组。SelectGroup此状态设置的值将由任何后续 Choice 状态使用。

{ "Type": "SelectGroup", "Next": "<state-name>" "TestGroups": [ <group-id>" ] }

包含值的所有字段都为必填字段,如下所述:

Next

在执行处于当前状态的操作后要转换为的状态的名称。

TestGroups

将标记为选中状态的测试组的数组。对于此数组中的每个测试组 ID,上下文中的 group-id_selected 变量设置为 true。请确保您提供了有效的测试组 IDs,因为 IDT 不验证指定的组是否存在。

Fail

状态表示状态机未正确执行。Fail这是状态机的最终状态,并且每个状态机定义必须包含此状态。

{ "Type": "Fail" }

Succeed

状态表示状态机已正确执行。Succeed这是状态机的最终状态,并且每个状态机定义必须包含此状态。

{ "Type": "Succeed" }

状态机上下文

状态机上下文是一个只读 JSON 文档,其中包含在执行期间可供状态机使用的数据。状态机上下文只能从状态机访问,并且包含用于确定测试流的信息。例如,您可以使用测试运行程序在 userdata.json 文件中配置的信息来确定是否需要运行特定测试。

状态机上下文使用以下格式:

{ "pool": { <device-json-pool-element> }, "userData": { <userdata-json-content> }, "config": { <config-json-content> }, "suiteFailed": true | false, "specificTestGroups": [ "<group-id>" ], "specificTestCases": [ "<test-id>" ], "hasExecutionErrors": true }
pool

有关为测试运行选择的设备池的信息。对于所选设备池,从 device.json 文件中定义的相应顶级设备池数组元素检索此信息。

userData

文件中的信息。userdata.json

config

固定 config.json 文件的信息。

suiteFailed

在状态机启动时,该值设置为 false。如果测试组在 RunTask 状态失败,则在状态机执行的剩余持续时间内,此值将设置为 true

specificTestGroups

如果测试运行程序选择要运行的特定测试组而不是整个测试套件,则创建此键并包含特定测试组 IDs 的列表。

specificTestCases

如果测试运行程序选择要运行的特定测试用例而不是整个测试套件,则创建此键并包含特定测试用例 IDs 的列表。

hasExecutionErrors

在状态机启动时不退出。如果任何状态遇到执行错误,则会在状态机执行的剩余持续时间内创建此变量并将其设置为 true

您可以使用 JSONPath 表示法查询上下文。状态定义中的 JSONPath 查询的语法为 {{$.query}}。 您可以使用 JSONPath 查询作为某些状态中的占位符字符串。IDT 将占位符字符串替换为上下文中计算得到的 JSONPath 查询的值。您可以对以下值使用占位符:

  • 处于 TestCases 状态的 RunTask 值。

  • Expression 状态。Choice

当您从状态机上下文访问数据时,请确保满足以下条件:

  • 您的 JSON 路径必须以 $. 开头

  • 每个值必须计算为字符串、数字或布尔值。

有关使用 JSONPath 表示法访问上下文中的数据的更多信息,请参阅使用 IDT 上下文

执行错误

执行错误是状态机定义中在执行状态时遇到的错误。IDT 记录有关 test_manager.log 文件中每个错误的信息,并将日志消息流式传输到控制台。

您可以使用以下方法来处理执行错误:

Catch

要使用 Catch,请将以下内容添加到状态定义中:

"Catch": [ { "ErrorEquals": [ "<error-type>" ] "Next": "<state-name>" } ]

包含值的所有字段都为必填字段,如下所述:

Catch.ErrorEquals

要捕获的错误类型的数组。如果执行错误与指定值之一匹配,则状态机转换为 Catch.Next 中指定的状态。 有关其生成的错误类型的信息,请参阅每个状态定义。

Catch.Next

如果当前状态遇到与 Catch.ErrorEquals 中指定的值之一匹配的执行错误,则将转换为下一个状态。

将按顺序处理捕获数据块,直到一个匹配。如果没有错误与 Catch 数据块中列出的错误匹配,则状态机将继续执行。由于执行错误是由不正确的状态定义导致的,因此,我们建议您在状态遇到执行错误时转换为 Fail 状态。

hasExecutionError

当某些状态遇到执行错误时,除了发出错误之外,它们还会在状态机上下文中将 hasExecutionError 值设置为 true。您可以使用此值来检测出现错误的时间,然后使用 Choice 状态将状态机转换为 Fail 状态。

此方法具有以下特征。

  • 状态机不会从分配给 hasExecutionError 的任何值开始,并且在特定状态设置该值之前,此值不可用。这意味着,您必须为访问此值的 FallthroughOnError 状态将 false 显式设置为 Choice,以防止在没有执行错误时状态机停止。

  • 一旦它设置为 true,则 hasExecutionError 永远不会设置为 false 或者从上下文中删除。这意味着该值仅在首次设置为 true 时有用,并且对于所有后续状态,它不会提供有意义的值。

  • 值将与处于 hasExecutionError 状态的所有分支状态机共享,这可能会导致意外结果,具体取决于其访问顺序。Parallel

由于这些特征,如果您可以使用 Catch 数据块,则不建议使用此方法。

示例状态机

本节提供一些示例状态机配置。

示例状态机:运行单个测试组

此状态机:

  • 使用 ID GroupA 运行测试组,该 ID 必须存在于 group.json 文件的套件中。

  • 检查执行错误,如果找到,则转换为 Fail

  • 生成一个报告,并在没有错误时转移到 Succeed,否则为 Fail

{ "Comment": "Runs a single group and then generates a report.", "StartAt": "RunGroupA", "States": { "RunGroupA": { "Type": "RunTask", "Next": "Report", "TestGroup": "GroupA", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "Report": { "Type": "Report", "Next": "Succeed", "Catch": [ { "ErrorEquals": [ "ReportError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }

状态机示例:运行用户选择的测试组

此状态机:

  • 检查测试运行程序是否选择了特定的测试组。状态机不会检查特定测试用例,因为测试运行程序无法在不同时选择测试组的情况下选择测试用例。

  • 如果选择了测试组:

    • 在选定的测试组中运行测试用例。为此,状态机未明确指定处于 RunTask 状态的任何测试组或测试用例。

    • 在运行所有测试并退出后生成报告。

  • 如果未选择测试组:

    • 在测试组 GroupA 中运行测试。

    • 生成报告和退出。

{ "Comment": "Runs specific groups if the test runner chose to do that, otherwise runs GroupA.", "StartAt": "SpecificGroupsCheck", "States": { "SpecificGroupsCheck": { "Type": "Choice", "Default": "RunGroupA", "FallthroughOnError": true, "Choices": [ { "Expression": "{{$.specificTestGroups[0]}} != ''", "Next": "RunSpecificGroups" } ] }, "RunSpecificGroups": { "Type": "RunTask", "Next": "Report", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "RunGroupA": { "Type": "RunTask", "Next": "Report", "TestGroup": "GroupA", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "Report": { "Type": "Report", "Next": "Succeed", "Catch": [ { "ErrorEquals": [ "ReportError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }

示例状态机:运行具有产品功能的单个测试组

此状态机:

  • 运行测试组 GroupA

  • 检查执行错误,如果找到,则转换为 Fail

  • FeatureThatDependsOnGroupA 功能添加到 awsiotdevicetester_report.xml 文件中:

    • 如果 GroupA 通过,此功能将设置为 supported

    • 该功能在报告中不会标记为可选。

  • 生成报告,并在没有错误时转移到 Succeed,否则为Fail

{ "Comment": "Runs GroupA and adds product features based on GroupA", "StartAt": "RunGroupA", "States": { "RunGroupA": { "Type": "RunTask", "Next": "AddProductFeatures", "TestGroup": "GroupA", "ResultVar": "GroupA_passed", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "AddProductFeatures": { "Type": "AddProductFeatures", "Next": "Report", "Features": [ { "Feature": "FeatureThatDependsOnGroupA", "Groups": [ "GroupA" ], "IsRequired": true } ] }, "Report": { "Type": "Report", "Next": "Succeed", "Catch": [ { "ErrorEquals": [ "ReportError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }

示例状态机:并行运行两个测试组

此状态机:

  • 并行运行 GroupAGroupB 测试组。按 ResultVar 在分支状态机中的 RunTask 状态存储在上下文中的 AddProductFeatures 变量可用于 状态。

  • 检查执行错误,如果找到,则转换为 Fail。此状态机不使用 Catch 块,因为该方法不会检测分支状态机中的执行错误。

  • 根据通过的组向 awsiotdevicetester_report.xml 文件添加功能

    • 如果 GroupA 通过,此功能将设置为 supported

    • 该功能在报告中不会标记为可选。

  • 生成报告,并在没有错误时转移到 Succeed,否则为Fail

如果在设备池中配置了两个设备,则 GroupAGroupB 都可以同时运行。但是,如果 GroupAGroupB 在其中具有多个测试,则可以将这两个设备分配给这些测试。如果只配置了一个设备,则测试组将按顺序运行。

{ "Comment": "Runs GroupA and GroupB in parallel", "StartAt": "RunGroupAAndB", "States": { "RunGroupAAndB": { "Type": "Parallel", "Next": "CheckForErrors", "Branches": [ { "Comment": "Run GroupA state machine", "StartAt": "RunGroupA", "States": { "RunGroupA": { "Type": "RunTask", "Next": "Succeed", "TestGroup": "GroupA", "ResultVar": "GroupA_passed", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }, { "Comment": "Run GroupB state machine", "StartAt": "RunGroupB", "States": { "RunGroupA": { "Type": "RunTask", "Next": "Succeed", "TestGroup": "GroupB", "ResultVar": "GroupB_passed", "Catch": [ { "ErrorEquals": [ "RunTaskError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } } ] }, "CheckForErrors": { "Type": "Choice", "Default": "AddProductFeatures", "FallthroughOnError": true, "Choices": [ { "Expression": "{{$.hasExecutionErrors}} == true", "Next": "Fail" } ] }, "AddProductFeatures": { "Type": "AddProductFeatures", "Next": "Report", "Features": [ { "Feature": "FeatureThatDependsOnGroupA", "Groups": [ "GroupA" ], "IsRequired": true }, { "Feature": "FeatureThatDependsOnGroupB", "Groups": [ "GroupB" ], "IsRequired": true } ] }, "Report": { "Type": "Report", "Next": "Succeed", "Catch": [ { "ErrorEquals": [ "ReportError" ], "Next": "Fail" } ] }, "Succeed": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }