

这是 AWS CDK v2 开发者指南。旧版 CDK v1 于 2022 年 6 月 1 日进入维护阶段，并于 2023 年 6 月 1 日终止支持。

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

# 测试 AWS CDK 应用程序
<a name="testing"></a>

使用 AWS CDK，您的基础架构可以像您编写的任何其他代码一样具有可测试性。您可以在云端和本地进行测试。本主题介绍如何在云端进行测试。有关本地测试的指导，请参阅[使用 SA AWS M CLI 在本地测试和构建 AWS CDK 应用程序](testing-locally.md)。[测试 AWS CDK 应用程序的标准方法使用 AWS CDK 的[断言](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions-readme.html)模块和流行的测试框架，例如 [Jest for and 和 Python 的 JavaScript Pytest](https://jestjs.io/)。 TypeScript ](https://docs.pytest.org/en/6.2.x/)

您可以为 AWS CDK 应用程序编写两类测试。
+  **细粒度的断言**测试生成 AWS CloudFormation 模板的特定方面，例如 “此资源具有此值的此属性”。这些测试可以检测回归。当您使用测试驱动开发来开发新功能时，这些测试也会很有用。（您可以先编写一个测试，然后编写正确的实现使其通过。） 细粒度断言是最常使用的测试。
+  **快照测试**根据先前存储的基线 AWS CloudFormation 模板测试合成后的模板。快照测试让您可以自由重构，因为您可以确保重构后的代码与原始代码的工作方式完全相同。如果这些更改是有意的，您可以接受新的基线，用于今后的测试。但是，CDK 升级也可能导致合成模板发生变化，因此您不能仅依靠快照来确保实现是正确的。

**注意**  
本主题中用作示例的 TypeScript、Python 和 Java 应用程序的完整版本[可在上找到 GitHub](https://github.com/cdklabs/aws-cdk-testing-examples/)。

## 开始使用
<a name="testing-getting-started"></a>

为了说明如何编写这些测试，我们将创建一个包含 Ste AWS p Functions 状态机和一个 AWS Lambda 函数的堆栈。Lambda 函数订阅了 Amazon SNS 主题，只需将消息转发到状态机即可。

首先，使用 CDK 工具包创建一个空的 CDK 应用程序项目并安装我们需要的库。我们将使用的构造都在 CDK 主程序包中，该包是使用 CDK 工具包创建的项目中的默认依赖项。但您必须安装测试框架。

**Example**  

```
$ mkdir state-machine && cd state-machine
cdk init --language=typescript
npm install --save-dev jest @types/jest
```
为您的测试创建一个目录。  

```
$ mkdir test
```
编辑项目的 `package.json`，以告诉 NPM 如何运行 Jest，并告诉 Jest 要收集什么类型的文件。必要的更改如下所示。  
+ 向 `scripts` 部分添加新的 `test` 密钥
+ 将 Jest 及其类型添加到 `devDependencies` 部分
+ 添加带有 `moduleFileExtensions` 声明的新 `jest` 顶层密钥
这些更改如下图所示。将新文本放置在 `package.json` 中指示的位置。“…”占位符表示文件中不应更改的现有部分。  

```
{
  ...
  "scripts": {
    ...
    "test": "jest"
  },
  "devDependencies": {
    ...
    "@types/jest": "^24.0.18",
    "jest": "^24.9.0"
  },
  "jest": {
    "moduleFileExtensions": ["js"]
  }
}
```

```
$ mkdir state-machine && cd state-machine
$ cdk init --language=javascript
$ npm install --save-dev jest
```
为您的测试创建一个目录。  

```
$ mkdir test
```
编辑项目的 `package.json`，以告诉 NPM 如何运行 Jest，并告诉 Jest 要收集什么类型的文件。必要的更改如下所示。  
+ 向 `scripts` 部分添加新的 `test` 密钥
+ 将 Jest 添加到 `devDependencies` 部分
+ 添加带有 `moduleFileExtensions` 声明的新 `jest` 顶层密钥
这些更改如下图所示。将新文本放置在 `package.json` 中指示的位置。“…”占位符表示文件中不应更改的现有部分。  

```
{
  ...
  "scripts": {
    ...
    "test": "jest"
  },
  "devDependencies": {
    ...
    "jest": "^24.9.0"
  },
  "jest": {
    "moduleFileExtensions": ["js"]
  }
}
```

```
$ mkdir state-machine && cd state-machine
$ cdk init --language=python
$ source .venv/bin/activate # On Windows, run '.\venv\Scripts\activate' instead
$ python -m pip install -r requirements.txt
$ python -m pip install -r requirements-dev.txt
```

```
$ mkdir state-machine && cd-state-machine
$ cdk init --language=java
```
在首选的 Java IDE 中打开项目。（在 Eclipse 中，使用**文件** > **导入** > 现有 Maven 项目。）

```
$ mkdir state-machine && cd-state-machine
$ cdk init --language=csharp
```
在 Visual Studio 中打开 `src\StateMachine.sln`。  
在“解决方案资源管理器”中右键单击该解决方案，然后选择**添加** > **新建项目**。搜索 C MSTest \$1 并为 C\$1 添加**MSTest 测试项目**。（默认名称 `TestProject1`就可以了。）  
右键单击 `TestProject1` 并选择**添加** > **项目引用**，然后添加 `StateMachine` 项目作为引用。

## 示例堆栈
<a name="testing-app"></a>

以下是本主题中将要测试的堆栈。如前所述，其包含了一个 Lambda 函数和一个 Step Functions 状态机，并接受一个或多个 Amazon SNS 主题。Lambda 函数订阅了 Amazon SNS 主题并将其转发到状态机。

您无需执行任何特殊操作来让应用程序具有可测试性。实际上，该 CDK 堆栈与本指南中的其他示例堆栈没有任何重要区别。

**Example**  
将以下代码放在 `lib/state-machine-stack.ts` 中：  

```
import * as cdk from "aws-cdk-lib";
import * as sns from "aws-cdk-lib/aws-sns";
import * as sns_subscriptions from "aws-cdk-lib/aws-sns-subscriptions";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sfn from "aws-cdk-lib/aws-stepfunctions";
import { Construct } from "constructs";

export interface StateMachineStackProps extends cdk.StackProps {
  readonly topics: sns.Topic[];
}

export class StateMachineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StateMachineStackProps) {
    super(scope, id, props);

    // In the future this state machine will do some work...
    const stateMachine = new sfn.StateMachine(this, "StateMachine", {
      definition: new sfn.Pass(this, "StartState"),
    });

    // This Lambda function starts the state machine.
    const func = new lambda.Function(this, "LambdaFunction", {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: "handler",
      code: lambda.Code.fromAsset("./start-state-machine"),
      environment: {
        STATE_MACHINE_ARN: stateMachine.stateMachineArn,
      },
    });
    stateMachine.grants.startExecution(func);

    const subscription = new sns_subscriptions.LambdaSubscription(func);
    for (const topic of props.topics) {
      topic.addSubscription(subscription);
    }
  }
}
```
将以下代码放在 `lib/state-machine-stack.js` 中：  

```
const cdk = require("aws-cdk-lib");
const sns = require("aws-cdk-lib/aws-sns");
const sns_subscriptions = require("aws-cdk-lib/aws-sns-subscriptions");
const lambda = require("aws-cdk-lib/aws-lambda");
const sfn = require("aws-cdk-lib/aws-stepfunctions");

class StateMachineStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    // In the future this state machine will do some work...
    const stateMachine = new sfn.StateMachine(this, "StateMachine", {
      definition: new sfn.Pass(this, "StartState"),
    });

    // This Lambda function starts the state machine.
    const func = new lambda.Function(this, "LambdaFunction", {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: "handler",
      code: lambda.Code.fromAsset("./start-state-machine"),
      environment: {
        STATE_MACHINE_ARN: stateMachine.stateMachineArn,
      },
    });
    stateMachine.grants.startExecution(func);

    const subscription = new sns_subscriptions.LambdaSubscription(func);
    for (const topic of props.topics) {
      topic.addSubscription(subscription);
    }
  }
}

module.exports = { StateMachineStack }
```
将以下代码放在 `state_machine/state_machine_stack.py` 中：  

```
from typing import List

import aws_cdk.aws_lambda as lambda_
import aws_cdk.aws_sns as sns
import aws_cdk.aws_sns_subscriptions as sns_subscriptions
import aws_cdk.aws_stepfunctions as sfn
import aws_cdk as cdk

class StateMachineStack(cdk.Stack):
    def __init__(
        self,
        scope: cdk.Construct,
        construct_id: str,
        *,
        topics: List[sns.Topic],
        **kwargs
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # In the future this state machine will do some work...
        state_machine = sfn.StateMachine(
            self, "StateMachine", definition=sfn.Pass(self, "StartState")
        )

        # This Lambda function starts the state machine.
        func = lambda_.Function(
            self,
            "LambdaFunction",
            runtime=lambda_.Runtime.NODEJS_18_X,
            handler="handler",
            code=lambda_.Code.from_asset("./start-state-machine"),
            environment={
                "STATE_MACHINE_ARN": state_machine.state_machine_arn,
            },
        )
        state_machine.grants.start_execution(func)

        subscription = sns_subscriptions.LambdaSubscription(func)
        for topic in topics:
            topic.add_subscription(subscription)
```

```
package software.amazon.samples.awscdkassertionssamples;

import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.sns.ITopicSubscription;
import software.amazon.awscdk.services.sns.Topic;
import software.amazon.awscdk.services.sns.subscriptions.LambdaSubscription;
import software.amazon.awscdk.services.stepfunctions.Pass;
import software.amazon.awscdk.services.stepfunctions.StateMachine;

import java.util.Collections;
import java.util.List;

public class StateMachineStack extends Stack {
    public StateMachineStack(final Construct scope, final String id, final List<Topic> topics) {
        this(scope, id, null, topics);
    }

    public StateMachineStack(final Construct scope, final String id, final StackProps props, final List<Topic> topics) {
        super(scope, id, props);

        // In the future this state machine will do some work...
        final StateMachine stateMachine = StateMachine.Builder.create(this, "StateMachine")
                .definition(new Pass(this, "StartState"))
                .build();

        // This Lambda function starts the state machine.
        final Function func = Function.Builder.create(this, "LambdaFunction")
                .runtime(Runtime.NODEJS_18_X)
                .handler("handler")
                .code(Code.fromAsset("./start-state-machine"))
                .environment(Collections.singletonMap("STATE_MACHINE_ARN", stateMachine.getStateMachineArn()))
                .build();
        stateMachine.getGrants().startExecution(func);

        final ITopicSubscription subscription = new LambdaSubscription(func);
        for (final Topic topic : topics) {
            topic.addSubscription(subscription);
        }
    }
}
```

```
using Amazon.CDK;
using Amazon.CDK.AWS.Lambda;
using Amazon.CDK.AWS.StepFunctions;
using Amazon.CDK.AWS.SNS;
using Amazon.CDK.AWS.SNS.Subscriptions;
using Constructs;

using System.Collections.Generic;

namespace AwsCdkAssertionSamples
{
    public class StateMachineStackProps : StackProps
    {
        public Topic[] Topics;
    }

    public class StateMachineStack : Stack
    {

        internal StateMachineStack(Construct scope, string id, StateMachineStackProps props = null) : base(scope, id, props)
        {
            // In the future this state machine will do some work...
            var stateMachine = new StateMachine(this, "StateMachine", new StateMachineProps
            {
                Definition = new Pass(this, "StartState")
            });

            // This Lambda function starts the state machine.
            var func = new Function(this, "LambdaFunction", new FunctionProps
            {
                Runtime = Runtime.NODEJS_18_X,
                Handler = "handler",
                Code = Code.FromAsset("./start-state-machine"),
                Environment = new Dictionary<string, string>
                {
                    { "STATE_MACHINE_ARN", stateMachine.StateMachineArn }
                }
            });
            stateMachine.Grants.StartExecution(func);

            foreach (Topic topic in props?.Topics ?? new Topic[0])
            {
                var subscription = new LambdaSubscription(func);
            }

        }
    }
}
```

我们将修改应用程序的主入口点，这样我们就不会实际上将堆栈实例化。我们不想意外地将其部署。我们的测试将创建一个应用程序和一个用于测试的堆栈实例。与测试驱动开发结合使用时，这是一种有用的策略：确保在启用部署之前堆栈通过所有测试。

**Example**  
In `bin/state-machine.ts`:  

```
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";

const app = new cdk.App();

// Stacks are intentionally not created here -- this application isn't meant to
// be deployed.
```
In `bin/state-machine.js`:  

```
#!/usr/bin/env node
const cdk = require("aws-cdk-lib");

const app = new cdk.App();

// Stacks are intentionally not created here -- this application isn't meant to
// be deployed.
```
In `app.py`:  

```
#!/usr/bin/env python3
import os

import aws_cdk  as cdk

app = cdk.App()

# Stacks are intentionally not created here -- this application isn't meant to
# be deployed.

app.synth()
```

```
package software.amazon.samples.awscdkassertionssamples;

import software.amazon.awscdk.App;

public class SampleApp {
    public static void main(final String[] args) {
        App app = new App();

        // Stacks are intentionally not created here -- this application isn't meant to be deployed.

        app.synth();
    }
}
```

```
using Amazon.CDK;

namespace AwsCdkAssertionSamples
{
    sealed class Program
    {
        public static void Main(string[] args)
        {
            var app = new App();

            // Stacks are intentionally not created here -- this application isn't meant to be deployed.

            app.Synth();
        }
    }
}
```

## Lambda 函数
<a name="testing-lambda"></a>

我们的示例堆栈包含了一个启动状态机的 Lambda 函数。我们必须提供此函数的源代码，以便 CDK 可以将其作为创建 Lambda 函数资源的一部分进行捆绑和部署。
+ 在应用程序的主目录中创建文件夹 `start-state-machine`。
+ 在此文件夹中，请至少创建一个文件。例如，您可以将以下代码保存在 `start-state-machines/index.js` 中。

  ```
  exports.handler = async function (event, context) {
  	return 'hello world';
  };
  ```

  然而，任何文件都可以起作用，因为我们实际上并不会部署堆栈。

## 运行测试
<a name="testing-running-tests"></a>

以下是用于在 AWS CDK 应用程序中运行测试的命令，以供参考。这些命令与您在使用同一测试框架的任何项目中运行测试时使用的命令相同。对于需要构建步骤的语言，请将其包括在内以确保已编译测试。

**Example**  

```
$ tsc && npm test
```

```
$ npm test
```

```
$ python -m pytest
```

```
$ mvn compile && mvn test
```
构建解决方案（F6）以发现测试，然后运行测试（**测试** > **运行所有测试**）。要选择运行哪些测试，请打开“测试资源管理器”（**测试** > **测试资源管理器**）。  
或：  

```
$ dotnet test src
```

## 细粒度断言
<a name="testing-fine-grained"></a>

使用细粒度断言测试堆栈的第一步是合成堆栈，因为我们正在根据生成的模板编写断言。 AWS CloudFormation 

我们的 `StateMachineStackStack` 要求我们向其转发要发送到状态机的 Amazon SNS 主题。因此，在我们的测试中，我们将创建一个单独的堆栈来包含主题。

通常，在编写 CDK 应用程序时，可以在堆栈的构造函数中子类化 `Stack` 并实例化 Amazon SNS 主题。在我们的测试中，我们直接实例化 `Stack`，然后将此堆栈作为 `Topic` 的作用域进行传递，并将其附加到该堆栈中。这在功能上是等效的，而且不那么冗长。它还有助于使仅在测试中使用的堆栈与您打算部署的堆栈“看起来不同”。

**Example**  

```
import { Capture, Match, Template } from "aws-cdk-lib/assertions";
import * as cdk from "aws-cdk-lib";
import * as sns from "aws-cdk-lib/aws-sns";
import { StateMachineStack } from "../lib/state-machine-stack";

describe("StateMachineStack", () => {
  test("synthesizes the way we expect", () => {
    const app = new cdk.App();

    // Since the StateMachineStack consumes resources from a separate stack
    // (cross-stack references), we create a stack for our SNS topics to live
    // in here. These topics can then be passed to the StateMachineStack later,
    // creating a cross-stack reference.
    const topicsStack = new cdk.Stack(app, "TopicsStack");

    // Create the topic the stack we're testing will reference.
    const topics = [new sns.Topic(topicsStack, "Topic1", {})];

    // Create the StateMachineStack.
    const stateMachineStack = new StateMachineStack(app, "StateMachineStack", {
      topics: topics, // Cross-stack reference
    });

    // Prepare the stack for assertions.
    const template = Template.fromStack(stateMachineStack);
  })
})
```

```
const { Capture, Match, Template } = require("aws-cdk-lib/assertions");
const cdk = require("aws-cdk-lib");
const sns = require("aws-cdk-lib/aws-sns");
const { StateMachineStack } = require("../lib/state-machine-stack");

describe("StateMachineStack", () => {
  test("synthesizes the way we expect", () => {
    const app = new cdk.App();

    // Since the StateMachineStack consumes resources from a separate stack
    // (cross-stack references), we create a stack for our SNS topics to live
    // in here. These topics can then be passed to the StateMachineStack later,
    // creating a cross-stack reference.
    const topicsStack = new cdk.Stack(app, "TopicsStack");

    // Create the topic the stack we're testing will reference.
    const topics = [new sns.Topic(topicsStack, "Topic1", {})];

    // Create the StateMachineStack.
    const StateMachineStack = new StateMachineStack(app, "StateMachineStack", {
      topics: topics, // Cross-stack reference
    });

    // Prepare the stack for assertions.
    const template = Template.fromStack(stateMachineStack);
  })
})
```

```
from aws_cdk import aws_sns as sns
import aws_cdk as cdk
from aws_cdk.assertions import Template

from app.state_machine_stack import StateMachineStack

def test_synthesizes_properly():
    app = cdk.App()

    # Since the StateMachineStack consumes resources from a separate stack
    # (cross-stack references), we create a stack for our SNS topics to live
    # in here. These topics can then be passed to the StateMachineStack later,
    # creating a cross-stack reference.
    topics_stack = cdk.Stack(app, "TopicsStack")

    # Create the topic the stack we're testing will reference.
    topics = [sns.Topic(topics_stack, "Topic1")]

    # Create the StateMachineStack.
    state_machine_stack = StateMachineStack(
        app, "StateMachineStack", topics=topics  # Cross-stack reference
    )

    # Prepare the stack for assertions.
    template = Template.from_stack(state_machine_stack)
```

```
package software.amazon.samples.awscdkassertionssamples;

import org.junit.jupiter.api.Test;
import software.amazon.awscdk.assertions.Capture;
import software.amazon.awscdk.assertions.Match;
import software.amazon.awscdk.assertions.Template;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.services.sns.Topic;

import java.util.*;

import static org.assertj.core.api.Assertions.assertThat;

public class StateMachineStackTest {
    @Test
    public void testSynthesizesProperly() {
        final App app = new App();

        // Since the StateMachineStack consumes resources from a separate stack (cross-stack references), we create a stack
        // for our SNS topics to live in here. These topics can then be passed to the StateMachineStack later, creating a
        // cross-stack reference.
        final Stack topicsStack = new Stack(app, "TopicsStack");

        // Create the topic the stack we're testing will reference.
        final List<Topic> topics = Collections.singletonList(Topic.Builder.create(topicsStack, "Topic1").build());

        // Create the StateMachineStack.
        final StateMachineStack stateMachineStack = new StateMachineStack(
                app,
                "StateMachineStack",
                topics // Cross-stack reference
        );

        // Prepare the stack for assertions.
        final Template template = Template.fromStack(stateMachineStack)
    }
}
```

```
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Amazon.CDK;
using Amazon.CDK.AWS.SNS;
using Amazon.CDK.Assertions;
using AwsCdkAssertionSamples;

using ObjectDict = System.Collections.Generic.Dictionary<string, object>;
using StringDict = System.Collections.Generic.Dictionary<string, string>;

namespace TestProject1
{
    [TestClass]
    public class StateMachineStackTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            var app = new App();

            // Since the StateMachineStack consumes resources from a separate stack (cross-stack references), we create a stack
            // for our SNS topics to live in here. These topics can then be passed to the StateMachineStack later, creating a
            // cross-stack reference.
            var topicsStack = new Stack(app, "TopicsStack");

            // Create the topic the stack we're testing will reference.
            var topics = new Topic[] { new Topic(topicsStack, "Topic1") };

            // Create the StateMachineStack.
            var StateMachineStack = new StateMachineStack(app, "StateMachineStack", new StateMachineStackProps
            {
                Topics = topics
            });

            // Prepare the stack for assertions.
            var template = Template.FromStack(stateMachineStack);

            // test will go here
        }
    }
}
```

现在我们可以断言已创建 Lambda 函数和 Amazon SNS 订阅。

**Example**  

```
    // Assert it creates the function with the correct properties...
    template.hasResourceProperties("AWS::Lambda::Function", {
      Handler: "handler",
      Runtime: "nodejs14.x",
    });

    // Creates the subscription...
    template.resourceCountIs("AWS::SNS::Subscription", 1);
```

```
    // Assert it creates the function with the correct properties...
    template.hasResourceProperties("AWS::Lambda::Function", {
      Handler: "handler",
      Runtime: "nodejs14.x",
    });

    // Creates the subscription...
    template.resourceCountIs("AWS::SNS::Subscription", 1);
```

```
# Assert that we have created the function with the correct properties
    template.has_resource_properties(
        "AWS::Lambda::Function",
        {
            "Handler": "handler",
            "Runtime": "nodejs14.x",
        },
    )

    # Assert that we have created a subscription
    template.resource_count_is("AWS::SNS::Subscription", 1)
```

```
        // Assert it creates the function with the correct properties...
        template.hasResourceProperties("AWS::Lambda::Function", Map.of(
                "Handler", "handler",
                "Runtime", "nodejs14.x"
        ));

         // Creates the subscription...
        template.resourceCountIs("AWS::SNS::Subscription", 1);
```

```
            // Prepare the stack for assertions.
            var template = Template.FromStack(stateMachineStack);

            // Assert it creates the function with the correct properties...
            template.HasResourceProperties("AWS::Lambda::Function", new StringDict {
                { "Handler", "handler"},
                { "Runtime", "nodejs14x" }
            });

            // Creates the subscription...
            template.ResourceCountIs("AWS::SNS::Subscription", 1);
```

我们的 Lambda 函数测试断言函数资源的两个特定属性具有特定值。默认情况下，该`hasResourceProperties`方法对合成 CloudFormation 模板中给出的资源属性执行部分匹配。此测试要求存在提供的属性并具有指定的值，但资源也可以具有其他未经测试的属性。

我们的 Amazon SNS 断言称，合成的模板包含订阅，但没有关于订阅本身的任何信息。我们包含此断言主要是为了说明如何断言资源数量。该`Template`类提供了更具体的方法来针对 CloudFormation 模板的`Resources``Outputs`、和`Mapping`部分编写断言。<a name="testing-fine-grained-matchers"></a>

 **匹配器**   
可以使用 ` [Match](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions.Match.html#methods) ` 类中的*匹配程序*来更改 `hasResourceProperties` 的默认部分匹配行为。  
匹配程序的范围从宽松（`Match.anyValue`）到严格（`Match.objectEquals`）。这些程序可以嵌套，以便将不同的匹配方法应用于资源属性的不同部分。例如，将 `Match.objectEquals` 和 `Match.anyValue` 结合使用，我们可以更全面地测试状态机的 IAM 角色，而不需要可能更改的属性的特定值。  

**Example**  

```
    // Fully assert on the state machine's IAM role with matchers.
    template.hasResourceProperties(
      "AWS::IAM::Role",
      Match.objectEquals({
        AssumeRolePolicyDocument: {
          Version: "2012-10-17",		 	 	 
          Statement: [
            {
              Action: "sts:AssumeRole",
              Effect: "Allow",
              Principal: {
                Service: {
                  "Fn::Join": [
                    "",
                    ["states.", Match.anyValue(), ".amazonaws.com"],
                  ],
                },
              },
            },
          ],
        },
      })
    );
```

```
    // Fully assert on the state machine's IAM role with matchers.
    template.hasResourceProperties(
      "AWS::IAM::Role",
      Match.objectEquals({
        AssumeRolePolicyDocument: {
          Version: "2012-10-17",		 	 	 
          Statement: [
            {
              Action: "sts:AssumeRole",
              Effect: "Allow",
              Principal: {
                Service: {
                  "Fn::Join": [
                    "",
                    ["states.", Match.anyValue(), ".amazonaws.com"],
                  ],
                },
              },
            },
          ],
        },
      })
    );
```

```
from aws_cdk.assertions import Match

    # Fully assert on the state machine's IAM role with matchers.
    template.has_resource_properties(
        "AWS::IAM::Role",
        Match.object_equals(
            {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",		 	 	 
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "states.",
                                            Match.any_value(),
                                            ".amazonaws.com",
                                        ],
                                    ],
                                },
                            },
                        },
                    ],
                },
            }
        ),
    )
```

```
        // Fully assert on the state machine's IAM role with matchers.
        template.hasResourceProperties("AWS::IAM::Role", Match.objectEquals(
                Collections.singletonMap("AssumeRolePolicyDocument", Map.of(
                        "Version", "2012-10-17",		 	 	 
                        "Statement", Collections.singletonList(Map.of(
                                "Action", "sts:AssumeRole",
                                "Effect", "Allow",
                                "Principal", Collections.singletonMap(
                                        "Service", Collections.singletonMap(
                                                "Fn::Join", Arrays.asList(
                                                        "",
                                                        Arrays.asList("states.", Match.anyValue(), ".amazonaws.com")
                                                )
                                        )
                                )
                        ))
                ))
        ));
```

```
            // Fully assert on the state machine's IAM role with matchers.
            template.HasResource("AWS::IAM::Role", Match.ObjectEquals(new ObjectDict
            {
                { "AssumeRolePolicyDocument", new ObjectDict
                    {
                        { "Version", "2012-10-17"		 	 	  },
                        { "Action", "sts:AssumeRole" },
                        { "Principal", new ObjectDict
                            {
                                { "Version", "2012-10-17"		 	 	  },
                                { "Statement", new object[]
                                    {
                                        new ObjectDict {
                                            { "Action", "sts:AssumeRole" },
                                            { "Effect", "Allow" },
                                            { "Principal", new ObjectDict
                                                {
                                                    { "Service", new ObjectDict
                                                        {
                                                            { "", new object[]
                                                                { "states", Match.AnyValue(), ".amazonaws.com" }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }));
```
许多 CloudFormation 资源都包含以字符串形式表示的序列化 JSON 对象。`Match.serializedJson()` 匹配程序可用于匹配此 JSON 中的属性。  
例如，Step Functions 状态机是使用基于 JSON 的 [Amazon States Language](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html) 中的字符串定义的。我们将使用 `Match.serializedJson()` 来确保我们的初始状态是唯一的步骤。同样，我们将使用嵌套匹配程序将不同类型的匹配应用于对象的不同部分。  

**Example**  

```
    // Assert on the state machine's definition with the Match.serializedJson()
    // matcher.
    template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
      DefinitionString: Match.serializedJson(
        // Match.objectEquals() is used implicitly, but we use it explicitly
        // here for extra clarity.
        Match.objectEquals({
          StartAt: "StartState",
          States: {
            StartState: {
              Type: "Pass",
              End: true,
              // Make sure this state doesn't provide a next state -- we can't
              // provide both Next and set End to true.
              Next: Match.absent(),
            },
          },
        })
      ),
    });
```

```
    // Assert on the state machine's definition with the Match.serializedJson()
    // matcher.
    template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
      DefinitionString: Match.serializedJson(
        // Match.objectEquals() is used implicitly, but we use it explicitly
        // here for extra clarity.
        Match.objectEquals({
          StartAt: "StartState",
          States: {
            StartState: {
              Type: "Pass",
              End: true,
              // Make sure this state doesn't provide a next state -- we can't
              // provide both Next and set End to true.
              Next: Match.absent(),
            },
          },
        })
      ),
    });
```

```
    # Assert on the state machine's definition with the serialized_json matcher.
    template.has_resource_properties(
        "AWS::StepFunctions::StateMachine",
        {
            "DefinitionString": Match.serialized_json(
                # Match.object_equals() is the default, but specify it here for clarity
                Match.object_equals(
                    {
                        "StartAt": "StartState",
                        "States": {
                            "StartState": {
                                "Type": "Pass",
                                "End": True,
                                # Make sure this state doesn't provide a next state --
                                # we can't provide both Next and set End to true.
                                "Next": Match.absent(),
                            },
                        },
                    }
                )
            ),
        },
    )
```

```
        // Assert on the state machine's definition with the Match.serializedJson() matcher.
        template.hasResourceProperties("AWS::StepFunctions::StateMachine", Collections.singletonMap(
                "DefinitionString", Match.serializedJson(
                        // Match.objectEquals() is used implicitly, but we use it explicitly here for extra clarity.
                        Match.objectEquals(Map.of(
                                "StartAt", "StartState",
                                "States", Collections.singletonMap(
                                        "StartState", Map.of(
                                                "Type", "Pass",
                                                "End", true,
                                                // Make sure this state doesn't provide a next state -- we can't provide
                                                // both Next and set End to true.
                                                "Next", Match.absent()
                                        )
                                )
                        ))
                )
        ));
```

```
            // Assert on the state machine's definition with the Match.serializedJson() matcher
            template.HasResourceProperties("AWS::StepFunctions::StateMachine", new ObjectDict
            {
                { "DefinitionString", Match.SerializedJson(
                    // Match.objectEquals() is used implicitly, but we use it explicitly here for extra clarity.
                    Match.ObjectEquals(new ObjectDict {
                        { "StartAt", "StartState" },
                        { "States", new ObjectDict
                        {
                            { "StartState", new ObjectDict {
                                { "Type", "Pass" },
                                { "End", "True" },
                                // Make sure this state doesn't provide a next state -- we can't provide
                                // both Next and set End to true.
                                { "Next", Match.Absent() }
                            }}
                        }}
                    })
                )}});
```<a name="testing-fine-grained-capture"></a>

 **捕获**   
测试属性通常很有用，以确保其遵循特定的格式，或者与其他属性具有相同的值，而无需事先知道它们的确切值。`assertions` 模块在其 ` [Capture](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions.Capture.html) ` 类中提供了此功能。  
通过指定一个 `Capture` 实例来代替 `hasResourceProperties` 中的值，该值将保留在 `Capture` 对象中。可以使用对象的 `as` 方法（包括 `asNumber()`、`asString()` 和 `asObject`）来检索实际捕获的值，并对其进行测试。将 `Capture` 与匹配程序一起使用，以指定要捕获的值在资源属性（包括序列化的 JSON 属性）中的确切位置。  
以下示例进行了测试，以确保状态机的启动状态名称以 `Start` 开头。它还会测试了该状态是否存在于计算机的状态列表中。  

**Example**  

```
    // Capture some data from the state machine's definition.
    const startAtCapture = new Capture();
    const statesCapture = new Capture();
    template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
      DefinitionString: Match.serializedJson(
        Match.objectLike({
          StartAt: startAtCapture,
          States: statesCapture,
        })
      ),
    });

    // Assert that the start state starts with "Start".
    expect(startAtCapture.asString()).toEqual(expect.stringMatching(/^Start/));

    // Assert that the start state actually exists in the states object of the
    // state machine definition.
    expect(statesCapture.asObject()).toHaveProperty(startAtCapture.asString());
```

```
    // Capture some data from the state machine's definition.
    const startAtCapture = new Capture();
    const statesCapture = new Capture();
    template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
      DefinitionString: Match.serializedJson(
        Match.objectLike({
          StartAt: startAtCapture,
          States: statesCapture,
        })
      ),
    });

    // Assert that the start state starts with "Start".
    expect(startAtCapture.asString()).toEqual(expect.stringMatching(/^Start/));

    // Assert that the start state actually exists in the states object of the
    // state machine definition.
    expect(statesCapture.asObject()).toHaveProperty(startAtCapture.asString());
```

```
import re

    from aws_cdk.assertions import Capture

    # ...

    # Capture some data from the state machine's definition.
    start_at_capture = Capture()
    states_capture = Capture()
    template.has_resource_properties(
        "AWS::StepFunctions::StateMachine",
        {
            "DefinitionString": Match.serialized_json(
                Match.object_like(
                    {
                        "StartAt": start_at_capture,
                        "States": states_capture,
                    }
                )
            ),
        },
    )

    # Assert that the start state starts with "Start".
    assert re.match("^Start", start_at_capture.as_string())

    # Assert that the start state actually exists in the states object of the
    # state machine definition.
    assert start_at_capture.as_string() in states_capture.as_object()
```

```
        // Capture some data from the state machine's definition.
        final Capture startAtCapture = new Capture();
        final Capture statesCapture = new Capture();
        template.hasResourceProperties("AWS::StepFunctions::StateMachine", Collections.singletonMap(
                "DefinitionString", Match.serializedJson(
                        Match.objectLike(Map.of(
                                "StartAt", startAtCapture,
                                "States", statesCapture
                        ))
                )
        ));

        // Assert that the start state starts with "Start".
        assertThat(startAtCapture.asString()).matches("^Start.+");

        // Assert that the start state actually exists in the states object of the state machine definition.
        assertThat(statesCapture.asObject()).containsKey(startAtCapture.asString());
```

```
            // Capture some data from the state machine's definition.
            var startAtCapture = new Capture();
            var statesCapture = new Capture();
            template.HasResourceProperties("AWS::StepFunctions::StateMachine", new ObjectDict
            {
                { "DefinitionString", Match.SerializedJson(
                    new ObjectDict
                    {
                        { "StartAt", startAtCapture },
                        { "States", statesCapture }
                    }
                )}
            });

            Assert.IsTrue(startAtCapture.ToString().StartsWith("Start"));
            Assert.IsTrue(statesCapture.AsObject().ContainsKey(startAtCapture.ToString()));
```

## 快照测试
<a name="testing-snapshot"></a>

在*快照测试*中，您可以将整个合成 CloudFormation 模板与先前存储的基线（通常称为 “主模板”）模板进行比较。与细粒度断言不同，快照测试在捕捉回归方面没有用。这是因为快照测试适用于整个模板，而除了代码更改之外的事情可能会导致合成结果出现微小（或 not-so-small）差异。这些更改甚至可能不会影响您的部署，但仍会导致快照测试失败。

例如，您可能会更新 CDK 构造以包含新的最佳实践，这可能会导致合成资源或其组织方式发生变化。或者，您可以将 CDK Toolkit 更新为报告其他元数据的版本。对上下文值的更改也会影响合成模板。

但是，只要保持所有其他可能影响合成模板的因素不变，快照测试就会对重构有很大帮助。如果您所做的更改无意中更改了模板，您将立即收到通知。如果更改是有意的，则只需接受新模板作为基线模板即可。

例如，如果我们有这样的 `DeadLetterQueue` 构造：

**Example**  

```
export class DeadLetterQueue extends sqs.Queue {
  public readonly messagesInQueueAlarm: cloudwatch.IAlarm;

  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Add the alarm
    this.messagesInQueueAlarm = new cloudwatch.Alarm(this, 'Alarm', {
      alarmDescription: 'There are messages in the Dead Letter Queue',
      evaluationPeriods: 1,
      threshold: 1,
      metric: this.metricApproximateNumberOfMessagesVisible(),
    });
  }
}
```

```
class DeadLetterQueue extends sqs.Queue {

  constructor(scope, id) {
    super(scope, id);

    // Add the alarm
    this.messagesInQueueAlarm = new cloudwatch.Alarm(this, 'Alarm', {
      alarmDescription: 'There are messages in the Dead Letter Queue',
      evaluationPeriods: 1,
      threshold: 1,
      metric: this.metricApproximateNumberOfMessagesVisible(),
    });
  }
}

module.exports = { DeadLetterQueue }
```

```
class DeadLetterQueue(sqs.Queue):
    def __init__(self, scope: Construct, id: str):
        super().__init__(scope, id)

        self.messages_in_queue_alarm = cloudwatch.Alarm(
            self,
            "Alarm",
            alarm_description="There are messages in the Dead Letter Queue.",
            evaluation_periods=1,
            threshold=1,
            metric=self.metric_approximate_number_of_messages_visible(),
        )
```

```
public class DeadLetterQueue extends Queue {
    private final IAlarm messagesInQueueAlarm;

    public DeadLetterQueue(@NotNull Construct scope, @NotNull String id) {
        super(scope, id);

        this.messagesInQueueAlarm = Alarm.Builder.create(this, "Alarm")
                .alarmDescription("There are messages in the Dead Letter Queue.")
                .evaluationPeriods(1)
                .threshold(1)
                .metric(this.metricApproximateNumberOfMessagesVisible())
                .build();
    }

    public IAlarm getMessagesInQueueAlarm() {
        return messagesInQueueAlarm;
    }
}
```

```
namespace AwsCdkAssertionSamples
{
    public class DeadLetterQueue : Queue
    {
        public IAlarm messagesInQueueAlarm;

        public DeadLetterQueue(Construct scope, string id) : base(scope, id)
        {
            messagesInQueueAlarm = new Alarm(this, "Alarm", new AlarmProps
            {
                AlarmDescription = "There are messages in the Dead Letter Queue.",
                EvaluationPeriods = 1,
                Threshold = 1,
                Metric = this.MetricApproximateNumberOfMessagesVisible()
            });
        }
    }
}
```

我们可以这样测试：

**Example**  

```
import { Match, Template } from "aws-cdk-lib/assertions";
import * as cdk from "aws-cdk-lib";
import { DeadLetterQueue } from "../lib/dead-letter-queue";

describe("DeadLetterQueue", () => {
  test("matches the snapshot", () => {
    const stack = new cdk.Stack();
    new DeadLetterQueue(stack, "DeadLetterQueue");

    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
  });
});
```

```
const { Match, Template } = require("aws-cdk-lib/assertions");
const cdk = require("aws-cdk-lib");
const { DeadLetterQueue } = require("../lib/dead-letter-queue");

describe("DeadLetterQueue", () => {
  test("matches the snapshot", () => {
    const stack = new cdk.Stack();
    new DeadLetterQueue(stack, "DeadLetterQueue");

    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
  });
});
```

```
import aws_cdk_lib as cdk
from aws_cdk_lib.assertions import Match, Template

from app.dead_letter_queue import DeadLetterQueue

def snapshot_test():
    stack = cdk.Stack()
    DeadLetterQueue(stack, "DeadLetterQueue")

    template = Template.from_stack(stack)
    assert template.to_json() == snapshot
```

```
package software.amazon.samples.awscdkassertionssamples;

import org.junit.jupiter.api.Test;
import au.com.origin.snapshots.Expect;
import software.amazon.awscdk.assertions.Match;
import software.amazon.awscdk.assertions.Template;
import software.amazon.awscdk.Stack;

import java.util.Collections;
import java.util.Map;

public class DeadLetterQueueTest {
    @Test
    public void snapshotTest() {
        final Stack stack = new Stack();
        new DeadLetterQueue(stack, "DeadLetterQueue");

        final Template template = Template.fromStack(stack);
        expect.toMatchSnapshot(template.toJSON());
    }
}
```

```
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Amazon.CDK;
using Amazon.CDK.Assertions;
using AwsCdkAssertionSamples;

using ObjectDict = System.Collections.Generic.Dictionary<string, object>;
using StringDict = System.Collections.Generic.Dictionary<string, string>;

namespace TestProject1
{
    [TestClass]
    public class StateMachineStackTest

    [TestClass]
    public class DeadLetterQueueTest
    {
    [TestMethod]
        public void SnapshotTest()
        {
            var stack = new Stack();
            new DeadLetterQueue(stack, "DeadLetterQueue");

            var template = Template.FromStack(stack);

            return Verifier.Verify(template.ToJSON());
        }
    }
}
```

## 测试提示
<a name="testing-tips"></a>

请记住，您的测试持续时间将与其测试的代码一样长，且会被经常读取和修改。因此，有必要花时间考虑如何最好地编写测试。

请勿复制和粘贴设置行或常见断言。相反，应将此逻辑重构为夹具或辅助函数。使用能反映每项测试实际测试内容的有效名称。

请勿试图在一次测试中执行太多操作。一个测试最好只测试一种行为。如果您不小心破坏了这种行为，那么只有一个测试会失败，测试名称会告诉您哪一个测试失败。但是，这更像是一个值得努力实现的理想；有时您会不可避免地（或无意中）编写测试多个行为的测试。快照测试特别容易出现此问题，原因我们已经描述过，因此请谨慎使用快照测试。

# 使用 AWS SAM CLI 在本地测试和构建 AWS CDK 应用程序
<a name="testing-locally"></a>

您可以使用 AWS SAM CLI 在本地测试和构建使用 AWS 云开发工具包 (AWS CDK) 定义的无服务器应用程序。由于 AWS SAM CLI 是在 AWS CDK 项目结构中运行的，因此您仍然可以使用 [AWS CDK CLI 参考](cli.md)来创建、修改和部署 AWS CDK 应用程序。

有关使用 AWS SAM 的详细信息，请参阅《AWS Serverless Application Model 开发人员指南》**中的 [AWS SAM 入门](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html)。

**Topics**
+ [开始使用本地测试](testing-locally-getting-started.md)
+ [使用 AWS SAM 在本地测试 AWS CDK 应用程序](testing-locally-with-sam-cli.md)
+ [使用 AWS SAM 构建 AWS CDK 应用程序](testing-locally-build-with-sam-cli.md)

# 开始使用本地测试
<a name="testing-locally-getting-started"></a>

本主题描述了结合使用 AWS SAM CLI 和 AWS CDK 应用程序所需的内容，并提供了构建和本地测试简单 AWS CDK 应用程序的说明。

## 先决条件
<a name="testing-locally-getting-started-prerequisites"></a>

要在本地进行测试，必须安装 AWS SAM CLI。有关安装说明，请参阅[安装 AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/getting_started.html)。

## 创建和本地测试 AWS CDK 应用程序
<a name="testing-locally-getting-started-tutorial"></a>

要使用 AWS SAM CLI 在本地测试 AWS CDK 应用程序，您必须拥有包含 Lambda 函数的 AWS CDK 应用程序。使用以下步骤创建带有 Lambda 函数的基本 AWS CDK 应用程序。有关更多信息，请参阅《AWS 云开发工具包 (AWS CDK) 开发人员指南》**中的 [Creating a serverless application using the AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/serverless_example.html)。<a name="testing-locally-getting-started-tutorial-init"></a>

 **步骤 1：创建 AWS CDK 应用程序**   
在本教程中，初始化一个使用 TypeScript 的 AWS CDK 应用程序。  
要运行的命令：  

```
$ mkdir cdk-sam-example
$ cd cdk-sam-example
$ cdk init app --language typescript
```<a name="testing-locally-getting-started-tutorial-lambda"></a>

 **第 2 步：将 Lambda 函数添加到应用程序**   
使用以下内容替换 `lib/cdk-sam-example-stack.ts` 中的代码：  

```
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class CdkSamExampleStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new lambda.Function(this, 'MyFunction', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'app.lambda_handler',
      code: lambda.Code.fromAsset('./my_function'),
    });
  }
}
```<a name="testing-locally-getting-started-tutorial-code"></a>

 **第 3 步：添加 Lambda 函数代码**   
创建名为 `my_function` 的目录。在此目录中，创建名为 `app.py` 的文件。  
要运行的命令：  

**Example**  

```
$ mkdir my_function
$ cd my_function
$ touch app.py
```

```
$ mkdir my_function
$ cd my_function
$ type nul > app.py
```

```
$ mkdir my_function
$ cd my_function
$ New-Item -Path "app.py”
```
将以下代码添加到 `app.py`：

```
def lambda_handler(event, context):
    return "Hello from SAM and the CDK!"
```<a name="testing-locally-getting-started-tutorial-function"></a>

 **第 4 步：测试 Lambda 函数**   
您可以使用 AWS SAM CLI 在本地调用您在 AWS CDK 应用程序中定义的 Lambda 函数。为此，您需要函数构造标识符和合成 AWS CloudFormation 模板的路径。  
运行以下命令返回到 `lib` 目录：  

```
$  cd ..
```
 **要运行的命令：**  

```
$  cdk synth --no-staging
```

```
$  sam local invoke MyFunction --no-event -t ./cdk.out/CdkSamExampleStack.template.json
```
 **输出示例：**  

```
Invoking app.lambda_handler (python3.9)

START RequestId: 5434c093-7182-4012-9b06-635011cac4f2 Version: $LATEST
"Hello from SAM and the CDK!"
END RequestId: 5434c093-7182-4012-9b06-635011cac4f2
REPORT RequestId: 5434c093-7182-4012-9b06-635011cac4f2	Init Duration: 0.32 ms	Duration: 177.47 ms	Billed Duration: 178 ms	Memory Size: 128 MB	Max Memory Used: 128 MB
```

# 使用 AWS SAM 在本地测试 AWS CDK 应用程序
<a name="testing-locally-with-sam-cli"></a>

通过在 AWS CDK 应用程序的项目根目录中运行以下命令，您可以使用 AWS SAM CLI 在本地测试 AWS CDK 应用程序：
+  ` [sam local invoke](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html) ` 
+  ` [sam local start-api](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html) ` 
+  ` [sam local start-lambda](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-lambda.html) ` 

在使用 AWS CDK 应用程序运行任何 `sam local` 命令之前，必须先运行 `cdk synth`。

运行 `sam local invoke` 时，您需要要调用的函数构造标识符以及合成 AWS CloudFormation 模板的路径。如果应用程序使用嵌套堆栈，为了解决命名冲突，您还需要定义函数的堆栈名称。

 **用法**：  

```
# Invoke the function FUNCTION_IDENTIFIER declared in the stack STACK_NAME
$  sam local invoke <OPTIONS> <STACK_NAME/FUNCTION_IDENTIFIER>

# Start all APIs declared in the AWS CDK application
$  sam local start-api -t <./cdk.out/CdkSamExampleStack.template.json> <OPTIONS>

# Start a local endpoint that emulates AWS Lambda
$  sam local start-lambda -t <./cdk.out/CdkSamExampleStack.template.json> <OPTIONS>
```

## 示例
<a name="testing-cdk-applications-examples"></a>

考虑使用以下示例声明的堆栈和函数：

```
app = new HelloCdkStack(app, "HelloCdkStack",
   ...
)
class HelloCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    ...
    new lambda.Function(this, 'MyFunction', {
        ...
    });

    new HelloCdkNestedStack(this, 'HelloNestedStack' ,{
        ...
    });
  }
}

class HelloCdkNestedStack extends cdk.NestedStack {
  constructor(scope: Construct, id: string, props?: cdk.NestedStackProps) {
    ...
    new lambda.Function(this, 'MyFunction', {
        ...
    });
    new lambda.Function(this, 'MyNestedFunction', {
        ...
    });
  }
}
```

以下命令可在本地调用上面示例中定义的 Lambda 函数：

```
# Invoke MyFunction from the HelloCdkStack
$ sam local invoke -t <./cdk.out/HelloCdkStack.template.json> <MyFunction>
```

```
# Invoke MyNestedFunction from the HelloCdkNestedStack
$ sam local invoke -t <./cdk.out/HelloCdkStack.template.json> <MyNestedFunction>
```

```
# Invoke MyFunction from the HelloCdkNestedStack
$ sam local invoke -t <./cdk.out/HelloCdkStack.template.json> <HelloNestedStack/MyFunction>
```

# 使用 AWS SAM 构建 AWS CDK 应用程序
<a name="testing-locally-build-with-sam-cli"></a>

AWS SAM CLI 为使用 ` [sam build](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html) ` 构建 Lambda 函数和 AWS CDK 应用程序中定义的层提供支持。

对于使用 zip 构件的 Lambda 函数，请在运行 `sam local` 命令之前运行 `cdk synth`。`sam build` 不是必需项。

如果您的 AWS CDK 应用程序使用映像类型的函数，请在运行 `sam local` 命令之前运行 `cdk synth` 然后运行 `sam build`。运行 `sam build` 时，AWS SAM 不会构建使用运行时特定构造的 Lambda 函数或层，例如 ` [NodejsFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) `。`sam build` 不支持[捆绑资产](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.BundlingOptions.html)。

## 示例
<a name="testing-locally-build-with-sam-cli-examples"></a>

从 AWS CDK 项目根目录运行以下命令将构建应用程序。

```
$ sam build -t <./cdk.out/CdkSamExampleStack.template.json>
```