

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

# 配置 IDT 状态机
<a name="idt-state-machine"></a>

**重要**  
从 IDT v4.5.2 开始，此状态机已弃用。我们强烈建议您使用新的测试编排工具。有关更多信息，请参阅 [配置 IDT 测试编排工具](idt-test-orchestrator.md)。

状态机是一种控制测试套件执行流程的构造。它决定测试套件的起始状态，根据用户定义的规则管理状态转换，并继续在这些状态之间进行转换，直到达到结束状态。

如果您的测试套件不包含用户定义的状态机，IDT 将为您生成状态机。默认状态机执行以下功能：
+ 使测试运行器能够选择和运行特定的测试组，而不是整个测试套件。
+ 如果未选择特定的测试组，则按随机顺序运行测试套件中的每个测试组。
+ 生成报告并打印控制台摘要，其中显示每个测试组和测试用例的测试结果。

IDT 测试套件的状态机必须满足以下条件：
+ 每个状态都对应于 IDT 要采取的操作，例如运行测试组或产品报告文件。
+ 过渡到状态会执行与该状态关联的操作。
+ 每个状态都定义了下一个状态的过渡规则。
+ 结束状态必须为 `Succeed` 或 `Fail`。

## 状态机格式
<a name="state-machine-format"></a>

您可以使用以下模板来配置自己的`<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*。  
`States` 对象必须包含 `Succeed` 和 `Fail` 状态。有关有效状态的信息，请参阅[有效状态和状态定义](#valid-states)。

## 有效状态和状态定义
<a name="valid-states"></a>

本节介绍可在 IDT 状态机中使用的所有有效状态的状态定义。以下某些状态支持测试用例级别的配置。但是，除非绝对必要，否则我们建议您在测试组级别而不是测试用例级别配置状态转换规则。

**Topics**
+ [RunTask](#state-runtask)
+ [Choice](#state-choice)
+ [Parallel](#state-parallel)
+ [AddProductFeatures](#state-addproductfeatures)
+ [报告](#state-report)
+ [LogMessage](#state-logmessage)
+ [SelectGroup](#state-selectgroup)
+ [Fail](#state-fail)
+ [Succeed](#state-succeed)

### RunTask
<a name="state-runtask"></a>

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

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

包含值的所有字段都为必填字段，如下所述：

**`Next`**  
在当前状态下执行操作后要过渡到的状态的名称。

**`TestGroup`**  
可选。要运行的测试组的 ID。如果未指定此值，则 IDT 将运行测试运行器选择的测试组。

**`TestCases`**  
可选。 IDs 来自中指定组的测试用例数组`TestGroup`。IDT 根据 `TestGroup` 和 `TestCases` 的值确定测试执行行为，如下所示：  
+ 同时指定 `TestGroup` 和 `TestCases` 时，IDT 会运行测试组中的指定测试用例。
+ 如果 `TestCases` 已指定但 `TestGroup` 未指定，IDT 将运行指定的测试用例。
+ 如果 `TestGroup` 已指定但 `TestCases` 未指定，则 IDT 将运行指定测试组中的所有测试用例。
+ 如果未指定 `TestGroup` 或 `TestCases`，IDT 将运行测试运行器从 IDT CLI 中选择的测试组中的所有测试用例。要为测试运行器启用组选择，必须在 `statemachine.json` 文件中同时包含 `RunTask` 和 `Choice` 状态。有关其工作原理的示例，请参阅[状态机示例：运行用户选择的测试组](#allow-specific-groups)。

  有关为测试运行器启用 IDT CLI 命令的更多信息，请参阅 [启用 IDT CLI 命令](test-executables.md#idt-cli-coop)。

**`ResultVar`**  
要与测试运行结果一起设置的上下文变量的名称。如果您没有为指定值，请不要指定此值 `TestGroup`。基于以下内容，IDT 将您在 `ResultVar` 中定义的变量的值设置为 `true` 或 `false`：  
+ 如果变量名称的形式为 `text_text_passed`，则该值设置为第一个测试组中的所有测试均已通过或跳过。
+ 在所有其他情况下，该值设置为所有测试组中的所有测试已通过或跳过。

通常，您将使用`RunTask`状态来指定测试组 ID，而不指定单个测试用例 IDs，这样 IDT 将运行指定测试组中的所有测试用例。在此状态下运行的所有测试用例均按随机顺序并行运行。但是，如果所有测试用例都需要一台设备运行，并且只有一台设备可用，则测试用例将按顺序运行。

**错误处理**

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

### Choice
<a name="state-choice"></a>

`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` 中定义的状态。表达式字符串从状态机上下文中检索值，然后对它们执行操作以得出布尔值。有关访问状态机上下文的信息，请参阅[状态机上下文](#state-machine-context)。  
**`Choices.Next`**  
如果 `Choices.Expression` 中定义的表达式的评估结果为 `true`，则设置要过渡到的状态的名称。

**错误处理**

在以下情况下，`Choice` 状态可能需要错误处理：
+ 选择表达式中的某些变量在状态机上下文中不存在。
+ 表达式的结果不是布尔值。
+ JSON 查询的结果不是字符串、数字或布尔值。

在这种状态下，不能使用 `Catch` 数据块来处理错误。如果要在状态机遇到错误时停止执行它，则必须将 `FallthroughOnError` 设置为 `false`。但是，建议您将 `FallthroughOnError` 设置为 `true`，并根据您的用例，执行以下操作之一：
+ 如果您正在访问的变量预计在某些情况下不存在，则使用的值 `Default` 和其他 `Choices` 数据块来指定下一个状态。
+ 如果您正在访问的变量应始终存在，则将 `Default` 状态设置为 `Fail`。

### Parallel
<a name="state-parallel"></a>

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

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

包含值的所有字段都为必填字段，如下所述：

**`Next`**  
在当前状态下执行操作后要过渡到的状态的名称。

**`Branches`**  
要运行的状态机定义阵列。每个状态机定义都必须包含自己的`StartAt`、`Succeed` 和 `Fail` 状态。此阵列中的状态机定义不能引用其自身定义之外的状态。  
由于每个分支状态机共享相同的状态机上下文，因此在一个分支中设置变量然后从另一个分支读取这些变量可能会导致意外行为。

只有在 `Parallel` 运行所有分支状态机之后，该状态才会移动到下一个状态。每种需要设备的状态都将等到设备可用后才运行。如果有多个设备可用，则此状态会并行运行来自多个组的测试用例。如果没有足够的设备可用，则测试用例将按顺序运行。由于测试用例在并行运行时按随机顺序运行，因此可能会使用不同的设备来运行来自同一个测试组的测试。

**错误处理**

确保分支状态机和父状态机都过渡到 `Fail` 状态以处理执行错误。

由于分支状态机不会将执行错误传输到父状态机，因此您不能使用 `Catch` 数据块来处理分支状态机中的执行错误。相反，在共享状态机上下文中使用 `hasExecutionErrors` 值。有关如何执行此操作的示例，请参阅 [状态机示例：并行运行两个测试组](#run-in-parallel)。

### AddProductFeatures
<a name="state-addproductfeatures"></a>

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

产品功能是关于设备可能符合的特定标准的用户定义信息。例如，`MQTT` 产品功能可以指定设备正确发布 MQTT 消息。在报告中，根据指定的测试是否通过，将产品功能设置为 `supported`、`not-supported` 或自定义值。



**注意**  
`AddProductFeatures` 状态本身不生成报告。此状态必须过渡到[`Report`状态](#state-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`。如果未指定此值，则根据测试结果，将特征值设置为 `supported` 或 `not-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`**  
可选。与 `device.json` 文件中指定的 `protocol` 值相匹配的执行方法阵列。如果指定了此值，则测试运行器必须指定与该阵列中的一个 `protocol` 值相匹配的值，才能将该功能包含在报告中。如果未指定此值，则该要素将始终包含在报告中。

要使用 `AddProductFeatures` 状态，必须将 `RunTask` 状态 `ResultVar` 中的值设置为以下值之一：
+ 如果您指定了单个测试用例 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`。

### 报告
<a name="state-report"></a>

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

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

包含值的所有字段都为必填字段，如下所述：

**`Next`**  
在当前状态下执行操作后要过渡到的状态的名称。

您应始终过渡到测试执行流程即将结束时的 `Report` 状态，以便测试运行器可以查看测试结果。通常，此状态之后的下一个状态是 `Succeed`。

**错误处理**

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

### LogMessage
<a name="state-logmessage"></a>

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

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

包含值的所有字段都为必填字段，如下所述：

**`Next`**  
在当前状态下执行操作后要过渡到的状态的名称。

**`Level`**  
创建日志消息的错误级别。如果您指定的级别无效，则此状态会生成一条错误消息并将其丢弃。

**`Message`**  
要记入日志的消息。

### SelectGroup
<a name="state-selectgroup"></a>

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

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

包含值的所有字段都为必填字段，如下所述：

**`Next`**  
在当前状态下执行操作后要过渡到的状态的名称。

**`TestGroups`**  
一组将被标记为选中的测试组。对于此阵列中的每个测试组 ID，`group-id_selected` 变量在上下文中都设置为 `true`。请确保提供有效的测试组， IDs 因为 IDT 不会验证指定的组是否存在。

### Fail
<a name="state-fail"></a>

`Fail` 状态表示状态机未正确执行。这是状态机的结束状态，每个状态机定义都必须包含此状态。

```
{
    "Type": "Fail"
}
```

### Succeed
<a name="state-succeed"></a>

`Succeed` 状态表示状态机已正确执行。这是状态机的结束状态，每个状态机定义都必须包含此状态。

```
{
    "Type": "Succeed"
}
```

## 状态机上下文
<a name="state-machine-context"></a>

状态机上下文是一个只读 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 查询的值。您可以对占位符使用以下值：
+ `RunTask`状态的 `TestCases` 值。
+ `Expression` 值 `Choice` 状态。

当您访问状态机上下文中的数据时，请确保满足以下条件：
+ JSON 路径必须以 `$.` 开头
+ 每个值的计算结果必须为字符串、数字或布尔值。

有关使用 JSONPath 符号从上下文访问数据的更多信息，请参阅[使用 IDT 上下文](idt-context.md)。

## 执行错误
<a name="execution-errors"></a>

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

您可以使用以下方法来处理执行错误：
+ 在状态定义中添加一个[`Catch` 数据块](#catch)。
+ 在状态机上下文中检查 [`hasExecutionErrors`值](#context)。

### 捕获
<a name="catch"></a>

要使用 `Catch`，请将以下内容添加到您的状态定义中：

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

包含值的所有字段都为必填字段，如下所述：

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

**`Catch.Next`**  
当前状态遇到与 `Catch.ErrorEquals` 中指定值之一相匹配的执行错误时，设置要过渡到的下一个状态。

捕获数据块按顺序处理，直到匹配。如果没有错误与捕获数据块中列出的错误相匹配，则状态机将继续执行。由于执行错误是由状态定义不正确造成的，因此我们建议您在状态遇到执行错误时过渡到失败状态。

### hasExecutionError
<a name="context"></a>

当某些状态遇到执行错误时，除了发出错误外，它们还会在状态机上下文中将 `hasExecutionError` 值设置为 `true`。您可以使用此值来检测何时发生错误，然后使用 `Choice` 状态将状态机过渡到 `Fail` 状态。

该方式具有以下特征。
+ 状态机不以分配给 `hasExecutionError` 的任何值启动，并且在特定状态设置该值之前，该值不可用。这意味着必须将访问此值的 `Choice` 状态的 `FallthroughOnError` 明确设置为 `false`，以防止在没有发生执行错误时状态机停止。
+ 一旦将其设置为 `true`，`hasExecutionError` 就永远不会设置为 false 或将其从上下文中删除。这意味着该值仅在首次设置为 `true` 时才有用，并且对于所有后续状态，它不会提供有意义的值。
+ `hasExecutionError` 值与处于 `Parallel` 状态的所有分支状态机共享，这可能会导致意想不到的结果，具体取决于访问该值的顺序。

由于这些特性，如果您可以改用捕获数据块，我们不建议您使用此方法。

## 示例状态机
<a name="state-machine-examples"></a>

本部分提供了一些状态机配置示例。

**Topics**
+ [状态机示例：运行单个测试组](#single-test-group)
+ [状态机示例：运行用户选择的测试组](#allow-specific-groups)
+ [状态机示例：使用产品功能运行单个测试组](#run-with-product-features)
+ [状态机示例：并行运行两个测试组](#run-in-parallel)

### 状态机示例：运行单个测试组
<a name="single-test-group"></a>

该状态机：
+ 运行带有 id `GroupA` 的测试组，该组必须以 `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"
        }
    }
}
```

### 状态机示例：运行用户选择的测试组
<a name="allow-specific-groups"></a>

该状态机：
+ 检查测试运行器是否选择了特定的测试组。状态机不检查特定的测试用例，因为如果不选择测试组，测试运行器就无法选择测试用例。
+ 如果选择了测试组：
  + 在选定的测试组中运行测试用例。为此，状态机不会明确指定处于 `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"
        }
    }
}
```

### 状态机示例：使用产品功能运行单个测试组
<a name="run-with-product-features"></a>

该状态机：
+ 运行测试组 `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"
        }
    }
}
```

### 状态机示例：并行运行两个测试组
<a name="run-in-parallel"></a>

该状态机：
+ 并行运行 `GroupA` 和 `GroupB` 测试组。通过分支状态机中的 `RunTask` 状态存储在上下文中的 `ResultVar` 变量可供 `AddProductFeatures` 状态使用。
+ 检查是否存在执行错误，如果发现任何错误，则过渡到 `Fail`。此状态机不使用 `Catch` 数据块，因为该方法不会检测分支状态机中的执行错误。
+ 根据通过的群组向 `awsiotdevicetester_report.xml` 文件添加功能
  + 如果 `GroupA` 通过，则该功能将设置为 `supported`。
  + 报告中未将该功能标记为可选。
+ 生成报告，如果没有错误，则过渡到 `Succeed`，否则过渡到 `Fail`

如果在设备池中配置了两个设备，则 `GroupA` 和 `GroupB` 可以同时运行。但是，如果其中一个 `GroupA` 或 `GroupB` 有多个测试，则两个设备都可能分配给这些测试。如果只配置了一台设备，则测试组将按顺序运行。

```
{
    "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"
        }
    }
}
```