在 Step Functions Local 中使用模拟服务集成进行测试 - AWS Step Functions

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

在 Step Functions Local 中使用模拟服务集成进行测试

不支持 Step Functions Local

Step Functions Local 功能并不完备且不受支持

出于测试目的,您可以考虑使用能够模拟 Step Functions 的第三方解决方案。

在 Step Functions Local 中,您可以使用模拟服务集成来测试状态机的执行路径,而无需实际调用集成服务。要将状态机配置为使用模拟服务集成,请创建一个模拟配置文件。在此文件中,您可以将服务集成的所需输出定义为模拟响应,并将使用模拟响应模拟执行路径的执行定义为测试用例。

通过向 Step Functions Local 提供模拟配置文件,您可以通过运行使用测试用例中指定模拟响应的状态机来测试服务集成调用,而无需进行实际的服务集成调用。

注意

如果您未在模拟配置文件中指定模拟服务集成响应,Step Functions Local 将使用您在设置 Step Functions Local 时配置的端点调用AWS服务集成。有关为 Step Functions Local 配置端点的信息,请参阅为 Step Functions Local 设置配置选项

本主题使用以下列表中定义的几个概念:

  • 模拟服务集成 - 指配置为使用模拟响应而非执行实际服务调用的 Task 状态。

  • 模拟响应 - 指 Task 状态可以配置为使用的模拟数据。

  • 测试用例 - 指配置为使用模拟服务集成的状态机执行。

  • 模拟配置文件 - 指包含 JSON 的模拟配置文件,该文件定义了模拟服务集成、模拟响应和测试用例。

配置模拟服务集成

您可以使用 Step Functions Local 来模拟任何服务集成。但是,Step Functions Local 并不强制模拟与真实 APIs模拟相同。模拟 Task 永远不会调用服务端点。如果您未指定模拟响应,则 Task 将尝试调用服务端点。此外,使用 .waitForTaskToken 模拟 Task 时,Step Functions Local 将自动生成任务令牌。

第 1 步:在模拟配置文件中指定模拟服务集成

你可以使用 Step Functions Local 测试 Step Functions AWS SDK 和优化的服务集成。下图展示了在状态机定义选项卡中定义的状态机:

模拟服务集成示例。

为此,您必须创建一个模拟配置文件,其中包含模拟配置文件结构中定义的部分。

  1. 创建一个名为 MockConfigFile.json 的文件,以便使用模拟服务集成配置测试。

    以下示例展示了一个引用状态机的模拟配置文件,该状态机有两个已定义的状态,分别名为 LambdaStateSQSState

    Mock configuration file example

    以下是一个模拟配置文件示例,演示了如何模拟调用 Lambda 函数向 Amazon SQS 发送消息所产生的响应。在此示例中,LambdaSQSIntegration 状态机包含三个测试用例,分别名为 HappyPathRetryPathHybridPath,它们模拟名为 LambdaStateSQSStateTask 状态。这些状态使用 MockedLambdaSuccessMockedSQSSuccessMockedLambdaRetry 模拟服务响应。这些模拟服务响应在文件 MockedResponses 部分中定义。

    { "StateMachines":{ "LambdaSQSIntegration":{ "TestCases":{ "HappyPath":{ "LambdaState":"MockedLambdaSuccess", "SQSState":"MockedSQSSuccess" }, "RetryPath":{ "LambdaState":"MockedLambdaRetry", "SQSState":"MockedSQSSuccess" }, "HybridPath":{ "LambdaState":"MockedLambdaSuccess" } } } }, "MockedResponses":{ "MockedLambdaSuccess":{ "0":{ "Return":{ "StatusCode":200, "Payload":{ "StatusCode":200, "body":"Hello from Lambda!" } } } }, "LambdaMockedResourceNotReady":{ "0":{ "Throw":{ "Error":"Lambda.ResourceNotReadyException", "Cause":"Lambda resource is not ready." } } }, "MockedSQSSuccess":{ "0":{ "Return":{ "MD5OfMessageBody":"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51", "MessageId":"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51" } } }, "MockedLambdaRetry":{ "0":{ "Throw":{ "Error":"Lambda.ResourceNotReadyException", "Cause":"Lambda resource is not ready." } }, "1-2":{ "Throw":{ "Error":"Lambda.TimeoutException", "Cause":"Lambda timed out." } }, "3":{ "Return":{ "StatusCode":200, "Payload":{ "StatusCode":200, "body":"Hello from Lambda!" } } } } } }
    State machine definition

    下面是一个名为 LambdaSQSIntegration 的状态机定义示例,它定义了两个名为 LambdaStateSQSState 的服务集成任务状态。LambdaState 包含基于 States.ALL 的重试策略。

    { "Comment":"This state machine is called: LambdaSQSIntegration", "StartAt":"LambdaState", "States":{ "LambdaState":{ "Type":"Task", "Resource":"arn:aws:states:::lambda:invoke", "Parameters":{ "Payload.$":"$", "FunctionName":"HelloWorldFunction" }, "Retry":[ { "ErrorEquals":[ "States.ALL" ], "IntervalSeconds":2, "MaxAttempts":3, "BackoffRate":2 } ], "Next":"SQSState" }, "SQSState":{ "Type":"Task", "Resource":"arn:aws:states:::sqs:sendMessage", "Parameters":{ "QueueUrl":"https://sqs.us-east-1.amazonaws.com/account-id/myQueue", "MessageBody.$":"$" }, "End": true } } }

    您可以使用以下测试用例之一运行模拟配置文件中引用的 LambdaSQSIntegration 状态机定义:

    • HappyPath - 该测试分别使用 MockedLambdaSuccessMockedSQSSuccess 模拟 LambdaStateSQSState 的输出。

      • LambdaState 将返回以下值:

        "0":{ "Return":{ "StatusCode":200, "Payload":{ "StatusCode":200, "body":"Hello from Lambda!" } } }
      • SQSState 将返回以下值:

        "0":{ "Return":{ "MD5OfMessageBody":"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51", "MessageId":"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51" } }
    • RetryPath - 该测试分别使用 MockedLambdaRetryMockedSQSSuccess 模拟 LambdaStateSQSState 的输出。此外,LambdaState 被配置为执行四次重试尝试。这些尝试的模拟响应在 MockedLambdaRetry 状态中进行了定义和索引。

      • 原始尝试以任务失败结束,其中包含原因和错误消息,如以下示例所示:

        "0":{ "Throw": { "Error": "Lambda.ResourceNotReadyException", "Cause": "Lambda resource is not ready." } }
      • 第一次和第二次重试都以任务失败告终,其中包含原因和错误消息,如以下示例所示:

        "1-2":{ "Throw": { "Error": "Lambda.TimeoutException", "Cause": "Lambda timed out." } }
      • 第三次重试以任务成功结束,其中包含来自模拟 Lambda 响应中的“有效负载”部分的状态结果。

        "3":{ "Return": { "StatusCode": 200, "Payload": { "StatusCode": 200, "body": "Hello from Lambda!" } } }
        注意
        • 对于采用重试策略的状态,Step Functions Local 将用尽策略中设置的重试次数,直到收到成功响应。这意味着必须用连续的尝试次数来表示重试模拟,并且应该在返回成功响应之前涵盖所有的重试尝试。

        • 如果您没有为具体的重试尝试次数指定模拟响应,例如重试 “3”,则状态机执行将失败。

    • HybridPath - 此测试模拟 LambdaState 的输出。LambdaState 成功运行并收到模拟数据作为响应后,SQSState 对生产中指定的资源执行实际服务调用。

    有关如何使用模拟服务集成开始测试执行的信息,请参阅第 3 步:运行模拟服务集成测试

  2. 确保模拟响应的结构符合您在调用集成服务时收到的实际服务响应的结构。有关模拟响应的结构要求信息,请参阅配置模拟服务集成

    在前面的示例模拟配置文件中,MockedLambdaSuccessMockedLambdaRetry 中定义的模拟响应与调用 HelloFromLambda 时返回的实际响应结构一致。

    重要

    AWS 服务不同,响应结构可能会不同。Step Functions Local 不会验证模拟响应结构是否符合实际的服务响应结构。在测试之前,您必须确保模拟响应符合实际响应。要查看服务响应的结构,您可以使用 Step Functions 执行实际的服务调用,也可以查看这些服务的文档。

第 2 步:向 Step Functions Local 提供模拟配置文件

您可以通过以下方式将模拟配置文件提供给 Step Functions Local:

Docker
注意

如果您使用的是 Docker 版本的 Step Functions Local,则只能使用环境变量提供模拟配置文件。此外,在服务器初始启动时,必须将模拟配置文件挂载到 Step Functions Local 容器上。

将模拟配置文件挂载到 Step Functions Local 容器内的任何目录上。然后,设置一个名为 SFN_MOCK_CONFIG 的环境变量,其中包含容器中模拟配置文件的路径。此方法允许将模拟配置文件命名为任何名称,只要环境变量包含文件路径和名称即可。

以下命令展示了启动 Docker 映像的格式。

docker run -p 8083:8083 --mount type=bind,readonly,source={absolute path to mock config file},destination=/home/StepFunctionsLocal/MockConfigFile.json -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" amazon/aws-stepfunctions-local

以下示例使用该命令启动了 Docker 映像。

docker run -p 8083:8083 --mount type=bind,readonly,source=/Users/admin/Desktop/workplace/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" amazon/aws-stepfunctions-local
JAR File

使用以下任一方式将模拟配置文件提供给 Step Functions Local:

  • 将模拟配置文件放在与 Step FunctionsLocal.jar 相同的目录中。使用此方法时,必须将模拟配置文件命名为 MockConfigFile.json

  • 在运行 Step Functions Local 的会话中,将名为 SFN_MOCK_CONFIG 的环境变量设置为模拟配置文件的完整路径。此方法允许将模拟配置文件命名为任何名称,只要环境变量包含其文件路径和名称即可。在以下示例中,SFN_MOCK_CONFIG 变量设置为指向名为 EnvSpecifiedMockConfig.json 的模拟配置文件,文件位于 /home/workspace 目录中。

    export SFN_MOCK_CONFIG="/home/workspace/EnvSpecifiedMockConfig.json"
注意
  • 如果您没有向 Step Functions Local 提供环境变量 SFN_MOCK_CONFIG,则默认情况下,它将尝试读取在启动 Step Functions Local 的目录中名为 MockConfigFile.json 的模拟配置文件。

  • 如果将模拟配置文件放在与 Step FunctionsLocal.jar 相同的目录中,并设置环境变量 SFN_MOCK_CONFIG,则 Step Functions Local 将读取由环境变量指定的文件。

第 3 步:运行模拟服务集成测试

创建模拟配置文件并将其提供给 Step Functions Local 后,使用模拟服务集成运行模拟配置文件中配置的状态机。然后使用 API 操作检查执行结果。

  1. 根据前面提到的模拟配置文件中的定义创建状态机。

    aws stepfunctions create-state-machine \ --endpoint http://localhost:8083 \ --definition "{\"Comment\":\"Thisstatemachineiscalled:LambdaSQSIntegration\",\"StartAt\":\"LambdaState\",\"States\":{\"LambdaState\":{\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke\",\"Parameters\":{\"Payload.$\":\"$\",\"FunctionName\":\"arn:aws:lambda:region:account-id:function:HelloWorldFunction\"},\"Retry\":[{\"ErrorEquals\":[\"States.ALL\"],\"IntervalSeconds\":2,\"MaxAttempts\":3,\"BackoffRate\":2}],\"Next\":\"SQSState\"},\"SQSState\":{\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::sqs:sendMessage\",\"Parameters\":{\"QueueUrl\":\"https://sqs.us-east-1.amazonaws.com/account-id/myQueue\",\"MessageBody.$\":\"$\"},\"End\":true}}}" \ --name "LambdaSQSIntegration" --role-arn "arn:aws:iam::account-id:role/service-role/LambdaSQSIntegration"
  2. 使用模拟服务集成运行状态机。

    要使用模拟配置文件,请在模拟配置文件中配置的状态机上进行 StartExecution API 调用。为此,请在 StartExecution 使用的状态机 ARN 上添加后缀 #test_nametest_name 是一个测试用例,在同一个模拟配置文件中为状态机配置。

    下面的命令是一个使用 LambdaSQSIntegration 状态机和模拟配置的示例。在此示例中,使用第 1 步:在模拟配置文件中指定模拟服务集成中定义的 HappyPath 测试来执行 LambdaSQSIntegration 状态机。HappyPath 测试包含执行配置,用于处理 LambdaStateSQSState 状态使用 MockedLambdaSuccessMockedSQSSuccess 模拟服务响应进行的模拟服务集成调用。

    aws stepfunctions start-execution \ --endpoint http://localhost:8083 \ --name executionWithHappyPathMockedServices \ --state-machine arn:aws:states:region:account-id:stateMachine:LambdaSQSIntegration#HappyPath
  3. 查看状态机执行响应。

    使用模拟服务集成测试调用 StartExecution 的响应与正常调用 StartExecution 的响应相同,都会返回执行 ARN 和开始日期。

    以下是使用模拟服务集成测试调用 StartExecution 的响应示例:

    { "startDate":"2022-01-28T15:03:16.981000-05:00", "executionArn":"arn:aws:states:region:account-id:execution:LambdaSQSIntegration:executionWithHappyPathMockedServices" }
  4. 通过调用 ListExecutionsDescribeExecutionGetExecutionHistory API 来检查执行结果。

    aws stepfunctions get-execution-history \ --endpoint http://localhost:8083 \ --execution-arn arn:aws:states:region:account-id:execution:LambdaSQSIntegration:executionWithHappyPathMockedServices

    以下示例演示了使用第 2 步所示示例响应中的执行 ARN 调用 GetExecutionHistory 的部分响应。在此示例中,LambdaStateSQSState 的输出是模拟配置文件MockedLambdaSuccessMockedSQSSuccess 中定义的模拟数据。此外,模拟数据的使用方式与执行实际服务集成调用返回的数据相同。此外,在本示例中,LambdaState 的输出作为输入传递给 SQSState

    { "events": [ ... { "timestamp": "2021-12-02T19:39:48.988000+00:00", "type": "TaskStateEntered", "id": 2, "previousEventId": 0, "stateEnteredEventDetails": { "name": "LambdaState", "input": "{}", "inputDetails": { "truncated": false } } }, ... { "timestamp": "2021-11-25T23:39:10.587000+00:00", "type": "LambdaFunctionSucceeded", "id": 5, "previousEventId": 4, "lambdaFunctionSucceededEventDetails": { "output": "{\"statusCode\":200,\"body\":\"\\\"Hello from Lambda!\\\"\"}", "outputDetails": { "truncated": false } } }, ... "timestamp": "2021-12-02T19:39:49.464000+00:00", "type": "TaskStateEntered", "id": 7, "previousEventId": 6, "stateEnteredEventDetails": { "name": "SQSState", "input": "{\"statusCode\":200,\"body\":\"\\\"Hello from Lambda!\\\"\"}", "inputDetails": { "truncated": false } } }, ... { "timestamp": "2021-11-25T23:39:10.652000+00:00", "type": "TaskSucceeded", "id": 10, "previousEventId": 9, "taskSucceededEventDetails": { "resourceType": "sqs", "resource": "sendMessage", "output": "{\"MD5OfMessageBody\":\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\",\"MessageId\":\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\"}", "outputDetails": { "truncated": false } } }, ... ] }

Step Functions 中模拟服务集成的配置文件

不支持 Step Functions Local

Step Functions Local 功能并不完备且不受支持

出于测试目的,您可以考虑使用能够模拟 Step Functions 的第三方解决方案。

作为 Step Functions Local 的替代方案,您可以在部署到您的AWS账户之前使用 TestState API 对状态机逻辑进行单元测试。有关更多信息,请参阅使用 TestState API 测试状态机

要使用模拟服务集成,必须先创建一个名为 MockConfigFile.json 的模拟配置文件,其中包含您的模拟配置。然后将模拟配置文件提供给 Step Functions Local。此配置文件定义了测试用例,其中包含使用模拟服务集成响应的模拟状态。下一节将介绍包含模拟状态和模拟响应的模拟配置结构:

模拟配置文件结构

模拟配置是一个 JSON 对象,包含以下顶级字段:

  • StateMachines - 该对象的字段表示配置为使用模拟服务集成的状态机。

  • MockedResponse - 该对象的字段表示服务集成调用的模拟响应。

以下是一个模拟配置文件示例,其中包含 StateMachine 定义和 MockedResponse

{ "StateMachines":{ "LambdaSQSIntegration":{ "TestCases":{ "HappyPath":{ "LambdaState":"MockedLambdaSuccess", "SQSState":"MockedSQSSuccess" }, "RetryPath":{ "LambdaState":"MockedLambdaRetry", "SQSState":"MockedSQSSuccess" }, "HybridPath":{ "LambdaState":"MockedLambdaSuccess" } } } }, "MockedResponses":{ "MockedLambdaSuccess":{ "0":{ "Return":{ "StatusCode":200, "Payload":{ "StatusCode":200, "body":"Hello from Lambda!" } } } }, "LambdaMockedResourceNotReady":{ "0":{ "Throw":{ "Error":"Lambda.ResourceNotReadyException", "Cause":"Lambda resource is not ready." } } }, "MockedSQSSuccess":{ "0":{ "Return":{ "MD5OfMessageBody":"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51", "MessageId":"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51" } } }, "MockedLambdaRetry":{ "0":{ "Throw":{ "Error":"Lambda.ResourceNotReadyException", "Cause":"Lambda resource is not ready." } }, "1-2":{ "Throw":{ "Error":"Lambda.TimeoutException", "Cause":"Lambda timed out." } }, "3":{ "Return":{ "StatusCode":200, "Payload":{ "StatusCode":200, "body":"Hello from Lambda!" } } } } } }

模拟配置字段参考

以下部分将说明必须在模拟配置中定义的顶层对象字段。

StateMachines

StateMachines 对象定义哪些状态机将使用模拟服务集成。每个状态机的配置都用 StateMachines 的顶级字段表示。字段名称是状态机的名称,值是包含名为 TestCases 的单个字段的对象,其字段代表该状态机的测试用例。

以下语法显示了带有两个测试用例的状态机:

"MyStateMachine": { "TestCases": { "HappyPath": { ... }, "SadPath": { ... } }
TestCases

TestCases 字段表示状态机的各个测试用例。每个状态机的测试用例名称必须是唯一的,每个测试用例的值都是一个对象,指定了状态机中 Task 状态使用的模拟响应。

以下 TestCase 示例将两个 Task 状态与两个 MockedResponses 联系起来:

"HappyPath": { "SomeTaskState": "SomeMockedResponse", "AnotherTaskState": "AnotherMockedResponse" }
MockedResponses

MockedResponses 是一个对象,包含多个具有唯一字段名称的模拟响应对象。模拟响应对象定义了每次调用模拟 Task 状态时的成功结果或错误输出。您可以使用单个整数字符串(例如 “0”、“1”、“2” 和 “3”)或包含整数的范围(例如 “0-1”、“2-3”)来指定调用次数。

模拟 Task 时,必须为每次调用指定一个模拟响应。响应必须包含一个名为 ReturnThrow 的单个字段,其值为模拟 Task 调用的结果或错误输出。如果未指定模拟响应,则状态机执行将失败。

下面是一个包含 ThrowReturn 对象的 MockedResponse 示例。在此示例中,状态机运行的前三次返回 "0-2" 中指定的响应,状态机运行的第四次返回 "3" 中指定的响应。

"SomeMockedResponse": { "0-2": { "Throw": { ... } }, "3": { "Return": { ... } } }
注意

如果使用的是 Map 状态,并且希望确保 Map 状态的响应具有可预测性,请将 maxConcurrency 的值设置为 1。如果您设置的值大于 1,Step Functions Local 将同时运行多次迭代,这将导致各迭代状态的整体执行顺序不可预测。这可能会进一步导致 Step Functions Local 在每次执行时对迭代状态使用不同的模拟响应。

Return

Return 表示为 MockedResponse 对象的一个字段。它指定了模拟 Task 状态的成功结果。

下面是一个 Return 对象的示例,其中包含在 Lambda 函数上调用 Invoke 时的模拟响应:

"Return": { "StatusCode": 200, "Payload": { "StatusCode": 200, "body": "Hello from Lambda!" } }
Throw

Throw 表示为 MockedResponse 对象的一个字段。它指定失败 Task 的错误输出Throw 的值必须是一个对象,其中包含带有字符串值的 ErrorCause 字段。此外,您在 MockConfigFile.json 中指定的 Error 字段的字符串值必须与状态机的 RetryCatch 部分处理的错误相匹配。

下面是一个 Throw 对象的示例,其中包含在 Lambda 函数上调用 Invoke 时的模拟响应:

"Throw": { "Error": "Lambda.TimeoutException", "Cause": "Lambda timed out." }