单元测试AWS:步骤功能

时间:2018-06-29 06:50:58

标签: amazon-web-services unit-testing aws-step-functions

我是AWS的初学者,并且已经创建了下面给出的第一个AWS步骤功能,现在下一步是对该步骤功能进行单元测试。现在,我卡住了lambda函数并对其进行了独立的单元测试,对此一无所知,我该如何进行阶跃函数的单元测试。

我也想到一个问题,值得对阶跃函数进行单元测试,因为它只是一个json,有时会感觉是否可以完成。

我尝试搜索,但互联网或AWS文档上没有任何线索 任何帮助将不胜感激关于此博客或任何示例用例的任何博客 谢谢

{
"Comment": "An example of the Amazon States Language using a choice state.",
"StartAt": "LoginState",
States": {
"LoginState": {
  "Type": "Task",
  "Resource": "arn:aws:lambda:us-east-1:170344690019:function:myfirstLogin",
  "Next": "ChoiceState"
},
"ChoiceState": {
  "Type" : "Choice",
  "Choices": [
    {
      "Variable": "$.success",
      "BooleanEquals": true,
      "Next": "logoutState"
    },
    {
      "Variable": "$.success",
      "BooleanEquals": false,
      "Next": "DefaultState"
    }
  ],
  "Default": "DefaultState"
},

"logoutState": {
  "Type" : "Task",
  "Resource": "arn:aws:lambda:us-east-1:170344690019:function:myFirstLogout",
  "End": true
},


"DefaultState": {
  "Type": "Fail",
  "Error": "DefaultStateError",
  "Cause": "No Matches!"
}

}
}

4 个答案:

答案 0 :(得分:11)

这有点无聊,但会为以下解释提供帮助。在测试状态机时,您正在从单元测试的范围扩展到集成测试。

那为什么要挑剔呢?由于您正在进行集成测试,因此您将需要具有运行状态机的能力,以便可以向其提供输入并验证输出。有两种方法可以自动测试状态机...

  1. 将状态机部署到AWS帐户中的测试环境中,并使用AWS提供的任何工具(cliboto3等)直接调用它。这更接近自动化测试,因为它可以在真实环境中测试状态机。如果将其设置为CI管道的一部分,则将要求您使用您的AWS账户中安装和执行状态机所需的访问权限来配置构建服务器。

  2. 尝试使用类似stepfunctions-local的方法在本地系统或测试环境中模拟正在运行的状态机。如果您的CI管道设置已经在运行现有的单元测试,则此选项可能很有用。这将需要一些努力才能将工具正确安装到CI环境中,但值得这样做。

  3. 我个人最喜欢的...使用localstack。这些家伙在模拟可以在Docker容器中启动并运行的多个AWS服务方面做得非常出色。如果您的lambda使用其他AWS服务,则此功能特别有用。我喜欢在CI环境中运行它以进行集成测试。

  4. 使用AWS SAM CLI。我本人并没有使用太多。它要求您使用无服务器应用程序模型。自从得到正式支持以来,他们的文档确实得到了改善,因此遵循他们的指南和大量示例应该非常容易使用。在CI环境中运行此程序将需要在测试环境中安装该工具。

我希望这会有所帮助。我认为共享此答案中的任何代码都无济于事,因为您要尝试执行的操作并不简单,并且可以通过多种方式实现。例如,CircleCI等CI服务利用Docker容器,使您可以选择生成自己的Docker容器以运行stepfunctions-local或localstack。

编辑

请参阅下面@niqui的the answer。我相信我肯定会赞成此选项在CI环境中进行测试,因为它是由AWS提供和维护的,因此它可以作为stepfunctions-local或localstack的替代方案。

答案 1 :(得分:1)

AWS最近宣布了Step Functions的可下载版本

答案 2 :(得分:0)

要在与StepFunctions Local交互期间模拟Lambda函数,一种解决方案是在测试设置启动的Python线程中创建伪造的Lambda HTTP服务,并使该服务能够解析HTTP请求URL以确定要调用的函数

我已经将此概念实现为pytest固定装置:https://github.com/chehsunliu/pytest-stepfunctions

用法

假设有一个状态机仅收集所有EMR群集ID,我们想在本地对其进行测试。

状态机定义

{
  "StartAt": "ListIds",
  "States": {
    "ListIds": {
      "Type": "Task",
      "Resource": "${ListIdsLambdaArn}",
      "ResultPath": "$.cluster_ids",
      "End": true
    }
  }
}

Lambda代码

my/pkg/emr.py

import boto3


def list_ids(*args, **kwargs):
    emr_client = boto3.client("emr")
    response = emr_client.list_clusters()

    return [item["Id"] for item in response["Clusters"]]

测试代码

tests/test_foo.py

import json
import time
from string import Template

import boto3
from botocore.stub import Stubber


def test_bar(aws_stepfunctions_endpoint_url):
    # Create the definition string.

    definition_template = Template("""
    {
      "StartAt": "ListIds",
      "States": {
        "ListIds": {
          "Type": "Task",
          "Resource": "${ListIdsLambdaArn}",
          "ResultPath": "$.cluster_ids",
          "End": true
        }
      }
    }
    """)
    list_ids_lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:my.pkg.emr.list_ids"
    definition = definition_template.safe_substitute(ListIdsLambdaArn=list_ids_lambda_arn)

    # Create the state machine resource.

    sfn_client = boto3.client("stepfunctions", endpoint_url=aws_stepfunctions_endpoint_url)
    state_machine_arn = sfn_client.create_state_machine(
        name="list-ids", definition=definition, roleArn="arn:aws:iam::012345678901:role/DummyRole"
    )["stateMachineArn"]

    # Mock the Lambda code.

    emr_client = boto3.client("emr")
    mocker.patch("my.pkg.emr.boto3", autospec=True).client.return_value = emr_client

    stubber = Stubber(emr_client)
    stubber.add_response(
        "list_clusters", service_response={"Clusters": [{"Id": "j-00001"}, {"Id": "j-00002"}]}
    )

    # Start and wait until the execution finishes.

    execution_arn = sfn_client.start_execution(
        stateMachineArn=state_machine_arn, name="list-ids-exec", input="{}"
    )["executionArn"]

    with stubber:
        while True:
            response = sfn_client.describe_execution(executionArn=execution_arn)
            if response["status"] != "RUNNING":
                break
            time.sleep(0.5)

    # Validate the results.

    stubber.assert_no_pending_responses()
    assert "SUCCEEDED" == response["status"]
    assert ["j-00001", "j-00002"] == json.loads(response["output"])["cluster_ids"]

运行测试

安装依赖项:

$ pip install boto3 pytest pytest-stepfunctions pytest-mock

下载StepFunctions本地JAR here并执行:

$ java -jar /path/to/StepFunctionsLocal.jar \
    --lambda-endpoint http://localhost:13000 \
    --step-functions-endpoint http://localhost:8083 \
    --wait-time-scale 0

运行测试:

$ python -m pytest -v \
    --pytest-stepfunctions-endpoint-url=http://0.0.0.0:8083 \
    --pytest-stepfunctions-lambda-address=0.0.0.0 \
    --pytest-stepfunctions-lambda-port=13000 \
    ./tests

该测试也可以在Docker Compose中执行,它更易于使用和维护。您可以在我的仓库中查看自述文件。希望此装置可以帮助找到本文的人。

答案 3 :(得分:0)

我遇到了类似的问题,所以我写了一个 AWS unit tester for step functions。它通过使用官方提供的 docker 镜像来工作。

安装:

yarn add step-functions-tester
yarn add mocha chai
const TestRunner = require('step-functions-tester')
const { expect } = require('chai')
let testRunner
describe('Step function tester', function () {
  this.timeout('30s')

  before('Set up test runner', async function () {
    testRunner = new TestRunner()
    await testRunner.setUp()
  })
  afterEach('Clean up', async function () {
    await testRunner.cleanUp()
  })
  after('Tear down', async function () {
    await testRunner.tearDown()
  })

  it('Step function test', async function () {
    // AWS Step Function definition
    const stepFunctionDefinition = {StartAt: 'FirstStep', States: {FirstStep: { /* ... */}}}

    const stepFunctionInput = {}

    // Keys are function names in the step function definition, values are arrays of calls
    const callStubs = {'arn:eu-west:111:mockLambda': [{result: 'First call result'}, {result: 'Second call result'}], /*... */}
    
    const { executions } = await testRunner.run(callStubs, stepFunctionDefinition, stepFunctionInput)
    expect(executions).deep.equal(expectedExecutions)
  })
})