Step Functionsをローカル環境でテストできるStep Functions Localのモックサービス統合機能がリリースされたので試してみた

2022.02.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

上記の記事で紹介されていた Step Functions をローカル環境で実行する方法を実際にためしてみました。

Step Functions Local について

従来、AWS Lambda や Amazon SQS と連携した Step Functions をテストするには実際の AWS リソースのエンドポイントを叩く必要がありました。Step Functions Local ではローカル環境で Lambda などのサービスのレスポンスをモックすることで完全に隔離された環境で Step Functions の動作確認を行うことができます。

本記事の大まかな流れ

  1. SAM のサンプルプロジェクトを Pull
  2. Docker 環境でテストを実行

サンプルプロジェクトはaws-samplesのレポジトリに用意してあるものを利用します。テストに利用するテストケースとモックレスポンスも全てサンプルレポジトリのものを利用するので写経する必要はありません。

サンプルプロジェクトの概要

レポジトリの README の説明と重複しますが大まかに説明すると、カスタマーのコメントを感情分析してポジティブなもののみDynamoDBに保存、ネガティブなもの、住所やidentityなどの情報が不足しているものはDBには保存されずEvent Bridgeに送られるというものです。

参考元

テストしたい内容

  1. Identity(メールアドレスと顧客 ID) と住所の認識に成功したか
  2. Comprehend.DetectSentiment API を実行した後、ポジティブなセンチメントが検出されたか
  3. DynamoDB への書き込みが成功したか
  4. Event Bridge の Event Bus に正常にイベントがパブリッシュされたか

mock configuration file を記述

モックで返したい場所のレスポンス内容を JSON 形式で記述します。 以下はカスタマーのIdentityが有効かどうかを返却する Lambda のモックレスポンスの例です。

Step Functions Local は返却値の形式のバリデーションはできないため、AWS ドキュメントに記載されているレスポンス形式をモックに記述する必要があります。 以下は Lambda 関数の返り値をモックしたものですが、内容は Lambda のレスポンス形式に沿っている必要があります。レスポンスの形式を確認する手取り早い手段として実際の API を CLI から呼び出してレスポンスの形式を確かめる方法があります。

"CheckIdentityLambdaMockedSuccess": {
  "0": {
    "Return": {
      "StatusCode": 200,
      "Payload": {
        "statusCode": 200,
        "body": "{\"approved\":true,\"message\":\"identity validation passed\"}"
      }
    }
  }
}

参考元:aws-stepfunctions-examples

感情分析の処理のモック

次にカスタマーのコメントがポジティブなものかを判別する AWS Comprehend を使った感情分析処理のモックレスポンスを以下のように記述します。 AWS Comprehend API の返り値の形式はこちらを参考にしてください。

"DetectSentimentRetryOnErrorWithSuccess": {
  "0-2": {
    "Throw": {
      "Error": "InternalServerException",
      "Cause": "Server Exception while calling DetectSentiment API in Comprehend Service"
    }
  },
  "3": {
    "Return": {
      "Sentiment": "POSITIVE",
      "SentimentScore": {
        "Mixed": 0.00012647535,
        "Negative": 0.00008031699,
        "Neutral": 0.0051454515,
        "Positive": 0.9946478
      }
    }
  }
}

テストケースを定義

モックレスポンスの定義が完了したので次にテストケースを定義します。モック内容の全文はこちらを参照してください。

"RetryOnServiceExceptionTest": {
  "Check Identity": "CheckIdentityLambdaMockedSuccess",
  "Check Address": "CheckAddressLambdaMockedSuccess",
  "DetectSentiment": "DetectSentimentRetryOnErrorWithSuccess",
  "Add to FollowUp": "AddToFollowUpSuccess",
  "CustomerAddedToFollowup": "CustomerAddedToFollowupSuccess"
}

ここまでがテストケースとモックデータの大まかな説明です。次は実際にサンプルプロジェクトのソースコードを使って Step Functions Local を動かしてみます。

レポジトリをクローン

レポジトリからサンプルプロジェクトを Pull します。

git clone https://github.com/aws-samples/aws-stepfunctions-examples.git
# app-local-testing-mock-config/へ移動
cd aws-stepfunctions-examples/sam/app-local-testing-mock-config

Docker を起動

Step Functions Local の Docker Image を Pull します。 Docker の Daemon が起動していることを確認して以下のコマンドを実行してください。

docker pull amazon/aws-stepfunctions-local

次に Docker Run を実行します。

docker run -p 8083:8083 \
  --mount type=bind,readonly,source=$(pwd)/statemachine/test/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \
  -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" \
  amazon/aws-stepfunctions-local

Step Functions Local の State Machine を作成

Docker が起動した状態で以下のコマンドを実行してモックに利用する State Machine を作成します。

aws stepfunctions create-state-machine \
  --endpoint-url http://localhost:8083 \
  --definition file://statemachine/local_testing.asl.json \
  --name "LocalTesting" \
  --role-arn "arn:aws:iam::123456789012:role/DummyRole"

以下のレスポンスが返るので、この State Machine の ARN を利用して Step Functions Local でテストを実行します。

{
  "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:LocalTesting",
  "creationDate": "2022-02-08T01:15:39.239000+09:00"
}

HappyPath のテストを実行

正常系のテストを実行します。期待する結果はカスタマーのidentityと住所が入力されている、ポジティブな内容のコメントを検知、FollowUpのDynamoDBにカスタマーのデータを格納するCustomerAddedToFollowupが呼び出される、です。

参考元

aws stepfunctions start-execution \
  --endpoint http://localhost:8083 \
  --name executionWithHappyPathMockedServices \
  --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:LocalTesting#HappyPathTest \
  --input file://events/sfn_valid_input.json

--inputには正常系のサンプルデータを渡しています。

sfn_valid_input.json

{
  "data": {
    "firstname": "Jane",
    "lastname": "Doe",
    "identity": {
      "email": "jdoe@example.com",
      "ssn": "123-45-6789"
    },
    "address": {
      "street": "123 Main St",
      "city": "Columbus",
      "state": "OH",
      "zip": "43219"
    },
    "comments": "I am glad to sign-up for this service. Looking forward to different options."
  }
}

結果を確認する

Identication と住所の識別に成功し、感情分析でポジティブなコメントを認識したのちに呼び出される想定の関数、CustomerAddedToFollowupの処理がちゃんと呼び出されたかを以下のコマンドで確認します。

aws stepfunctions get-execution-history \
  --endpoint http://localhost:8083 \
  --execution-arn arn:aws:states:us-east-1:123456789012:execution:LocalTesting:executionWithHappyPathMockedServices \
  --query 'events[?type==`TaskStateExited` && stateExitedEventDetails.name==`CustomerAddedToFollowup`]'

レスポンスは以下です。

[
  {
    "timestamp": "2022-02-08T01:19:32.611000+09:00",
    "type": "TaskStateExited",
    "id": 32,
    "previousEventId": 31,
    "stateExitedEventDetails": {
      "name": "CustomerAddedToFollowup",
      "output": "{\"StatusCode\":200,\"Payload\":{\"statusCode\":200}}",
      "outputDetails": {
        "truncated": false
      }
    }
  }
]

CustomerAddedToFollowupが呼び出されたことが確認できました。

Negative Path のテストを実行

次に、感情分析の結果がネガティブだった場合を想定したテストを実行してみましょう。想定する結果はNegativeSentimentDetectedが呼ばれていることです。

参考元

aws stepfunctions start-execution \
  --endpoint http://localhost:8083 \
  --name executionWithNegativeSentimentMockedServices \
  --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:LocalTesting#NegativeSentimentTest \
  --input file://events/sfn_valid_input.json

レスポンスを確認します。

aws stepfunctions get-execution-history \
  --endpoint http://localhost:8083 \
  --execution-arn arn:aws:states:us-east-1:123456789012:execution:LocalTesting:executionWithNegativeSentimentMockedServices \
  --query 'events[?type==`TaskStateExited` && stateExitedEventDetails.name==`NegativeSentimentDetected`]'

結果:

[
  {
    "timestamp": "2022-02-08T01:39:51.073000+09:00",
    "type": "TaskStateExited",
    "id": 27,
    "previousEventId": 26,
    "stateExitedEventDetails": {
      "name": "NegativeSentimentDetected",
      "output": "{\"Payload\":{\"Entries\":[{\"EventId\":\"abc123\"}],\"FailedEntryCount\":0}}",
      "outputDetails": {
        "truncated": false
      }
    }
  }
]

References