Ini adalah Panduan Pengembang AWS CDK v2. CDKV1 yang lebih lama memasuki pemeliharaan pada 1 Juni 2022 dan mengakhiri dukungan pada 1 Juni 2023.
Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.
AWS CDK Aplikasi uji
Dengan AWS CDK, infrastruktur Anda dapat diuji seperti kode lain yang Anda tulis. Pendekatan standar untuk menguji AWS CDK aplikasi menggunakan modul pernyataan dan kerangka kerja pengujian populer seperti Jest for TypeScript dan atau Pytest untuk JavaScript Python. AWS CDK
Ada dua kategori tes yang dapat Anda tulis untuk AWS CDK aplikasi.
-
Pernyataan berbutir halus menguji aspek spesifik dari AWS CloudFormation templat yang dihasilkan, seperti “sumber daya ini memiliki properti ini dengan nilai ini.” Tes ini dapat mendeteksi regresi. Mereka juga berguna saat Anda mengembangkan fitur baru menggunakan pengembangan berbasis tes. (Anda dapat menulis tes terlebih dahulu, kemudian membuatnya lulus dengan menulis implementasi yang benar.) Pernyataan berbutir halus adalah tes yang paling sering digunakan.
-
Tes snapshot menguji template yang disintesis terhadap AWS CloudFormation template dasar yang disimpan sebelumnya. Tes snapshot memungkinkan Anda melakukan refactor secara bebas, karena Anda dapat yakin bahwa kode refactored bekerja persis dengan cara yang sama seperti aslinya. Jika perubahan itu disengaja, Anda dapat menerima baseline baru untuk pengujian masa depan. Namun, CDK peningkatan juga dapat menyebabkan templat yang disintesis berubah, sehingga Anda tidak dapat hanya mengandalkan snapshot untuk memastikan implementasi Anda benar.
Versi lengkap aplikasi TypeScript, Python, dan Java yang digunakan sebagai contoh dalam topik ini tersedia di. GitHub
Memulai
Untuk mengilustrasikan cara menulis tes ini, kita akan membuat tumpukan yang berisi mesin AWS Step Functions status dan AWS Lambda fungsi. Fungsi Lambda berlangganan SNS topik Amazon dan hanya meneruskan pesan ke mesin status.
Pertama, buat proyek CDK aplikasi kosong menggunakan CDK Toolkit dan instal pustaka yang kita perlukan. Konstruksi yang akan kita gunakan semuanya ada dalam CDK paket utama, yang merupakan dependensi default dalam proyek yang dibuat dengan Toolkit. CDK Namun, Anda harus menginstal kerangka pengujian Anda.
- TypeScript
-
mkdir state-machine && cd state-machine
cdk init --language=typescript
npm install --save-dev jest @types/jest
Buat direktori untuk pengujian Anda.
mkdir test
Edit proyek package.json
untuk memberi tahu NPM cara menjalankan Jest, dan untuk memberi tahu Jest jenis file apa yang harus dikumpulkan. Perubahan yang diperlukan adalah sebagai berikut.
-
Tambahkan test
kunci baru ke scripts
bagian
-
Tambahkan Jest dan jenisnya ke bagian devDependencies
-
Tambahkan kunci jest
tingkat atas baru dengan deklarasi moduleFileExtensions
Perubahan ini ditunjukkan pada garis besar berikut. Tempatkan teks baru di tempat yang ditunjukkanpackage.json
. Placeholder “...” menunjukkan bagian file yang ada yang tidak boleh diubah.
{
...
"scripts": {
...
"test": "jest"
},
"devDependencies": {
...
"@types/jest": "^24.0.18",
"jest": "^24.9.0"
},
"jest": {
"moduleFileExtensions": ["js"]
}
}
- JavaScript
-
mkdir state-machine && cd state-machine
cdk init --language=javascript
npm install --save-dev jest
Buat direktori untuk pengujian Anda.
mkdir test
Edit proyek package.json
untuk memberi tahu NPM cara menjalankan Jest, dan untuk memberi tahu Jest jenis file apa yang harus dikumpulkan. Perubahan yang diperlukan adalah sebagai berikut.
-
Tambahkan test
kunci baru ke scripts
bagian
-
Tambahkan Jest ke bagian devDependencies
-
Tambahkan kunci jest
tingkat atas baru dengan deklarasi moduleFileExtensions
Perubahan ini ditunjukkan pada garis besar berikut. Tempatkan teks baru di tempat yang ditunjukkanpackage.json
. Placeholder “...” menunjukkan bagian file yang ada yang tidak boleh diubah.
{
...
"scripts": {
...
"test": "jest"
},
"devDependencies": {
...
"jest": "^24.9.0"
},
"jest": {
"moduleFileExtensions": ["js"]
}
}
- Python
-
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
- Java
-
mkdir state-machine && cd-state-machine
cdk init --language=java
Buka proyek di Java pilihan AndaIDE. (Di Eclipse, gunakan File> Import > Existing Maven Projects.)
- C#
-
mkdir state-machine && cd-state-machine
cdk init --language=csharp
Buka src\StateMachine.sln
di Visual Studio.
Klik kanan solusi di Solution Explorer dan pilih Add > New Project. Cari MSTest C # dan tambahkan Proyek MSTest Uji untuk C #. (Nama default TestProject1
baik-baik saja.)
Klik kanan TestProject1
dan pilih Add > Project Reference, dan tambahkan StateMachine
proyek sebagai referensi.
Contoh tumpukan
Inilah tumpukan yang akan diuji dalam topik ini. Seperti yang telah kami jelaskan sebelumnya, ini berisi fungsi Lambda dan mesin status Step Functions, dan menerima satu atau beberapa topik Amazon. SNS Fungsi Lambda berlangganan SNS topik Amazon dan meneruskannya ke mesin negara.
Anda tidak perlu melakukan sesuatu yang istimewa untuk membuat aplikasi dapat diuji. Faktanya, CDK tumpukan ini tidak berbeda dengan cara penting apa pun dari tumpukan contoh lainnya dalam Panduan ini.
- TypeScript
-
Tempatkan kode berikut dilib/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.grantStartExecution(func);
const subscription = new sns_subscriptions.LambdaSubscription(func);
for (const topic of props.topics) {
topic.addSubscription(subscription);
}
}
}
- JavaScript
-
Tempatkan kode berikut dilib/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.grantStartExecution(func);
const subscription = new sns_subscriptions.LambdaSubscription(func);
for (const topic of props.topics) {
topic.addSubscription(subscription);
}
}
}
module.exports = { StateMachineStack }
- Python
-
Tempatkan kode berikut distate_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.grant_start_execution(func)
subscription = sns_subscriptions.LambdaSubscription(func)
for topic in topics:
topic.add_subscription(subscription)
- Java
-
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.grantStartExecution(func);
final ITopicSubscription subscription = new LambdaSubscription(func);
for (final Topic topic : topics) {
topic.addSubscription(subscription);
}
}
}
- C#
-
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.GrantStartExecution(func);
foreach (Topic topic in props?.Topics ?? new Topic[0])
{
var subscription = new LambdaSubscription(func);
}
}
}
}
Kita akan memodifikasi titik masuk utama aplikasi sehingga kita tidak benar-benar membuat instance stack kita. Kami tidak ingin menyebarkannya secara tidak sengaja. Pengujian kami akan membuat aplikasi dan instance tumpukan untuk pengujian. Ini adalah taktik yang berguna bila dikombinasikan dengan pengembangan berbasis tes: pastikan tumpukan melewati semua pengujian sebelum Anda mengaktifkan penerapan.
- TypeScript
-
Dalam 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.
- JavaScript
-
Dalam 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.
- Python
-
Dalam 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()
- Java
-
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();
}
}
- C#
-
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();
}
}
}
Fungsi Lambda
Contoh tumpukan kami mencakup fungsi Lambda yang memulai mesin status kami. Kita harus menyediakan kode sumber untuk fungsi ini sehingga CDK dapat membundel dan menyebarkannya sebagai bagian dari pembuatan sumber daya fungsi Lambda.
-
Buat folder start-state-machine
di direktori utama aplikasi.
-
Di folder ini, buat setidaknya satu file. Misalnya, Anda dapat menyimpan kode berikut distart-state-machines/index.js
.
exports.handler = async function (event, context) {
return 'hello world';
};
Namun, file apa pun akan berfungsi, karena kami tidak akan benar-benar menyebarkan tumpukan.
Menjalankan tes
Sebagai referensi, berikut adalah perintah yang Anda gunakan untuk menjalankan pengujian di AWS CDK aplikasi Anda. Ini adalah perintah yang sama yang akan Anda gunakan untuk menjalankan pengujian dalam proyek apa pun menggunakan kerangka pengujian yang sama. Untuk bahasa yang memerlukan langkah build, sertakan itu untuk memastikan pengujian Anda telah dikompilasi.
- TypeScript
-
tsc && npm test
- JavaScript
-
npm test
- Python
-
python -m pytest
- Java
-
mvn compile && mvn test
- C#
-
Bangun solusi Anda (F6) untuk menemukan pengujian, lalu jalankan pengujian (Test > Run All Tests). Untuk memilih tes mana yang akan dijalankan, buka Test Explorer (Test > Test Explorer).
Atau:
dotnet test src
Pernyataan berbutir halus
Langkah pertama untuk menguji tumpukan dengan pernyataan berbutir halus adalah mensintesis tumpukan, karena kami menulis pernyataan terhadap template yang dihasilkan. AWS CloudFormation
Kami StateMachineStackStack
mengharuskan kami meneruskannya SNS topik Amazon untuk diteruskan ke mesin negara. Jadi dalam pengujian kami, kami akan membuat tumpukan terpisah untuk memuat topik.
Biasanya, saat menulis CDK aplikasi, Anda dapat mensubclass Stack
dan membuat instance topik SNS Amazon di konstruktor tumpukan. Dalam pengujian kami, kami membuat instance Stack
secara langsung, lalu meneruskan tumpukan ini sebagai cakupan, melampirkannya ke tumpukan. Topic
Ini setara secara fungsional dan kurang bertele-tele. Ini juga membantu membuat tumpukan yang hanya digunakan dalam pengujian “terlihat berbeda” dari tumpukan yang ingin Anda terapkan.
- TypeScript
-
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);
}
- JavaScript
-
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);
- Python
-
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)
- Java
-
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)
- C#
-
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
}
}
}
Sekarang kita dapat menegaskan bahwa fungsi Lambda dan langganan SNS Amazon telah dibuat.
- TypeScript
-
// 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);
- JavaScript
-
// 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);
- Python
-
# 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)
- Java
-
// 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);
- C#
-
// 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);
Uji fungsi Lambda kami menegaskan bahwa dua properti tertentu dari sumber daya fungsi memiliki nilai tertentu. Secara default, hasResourceProperties
metode melakukan kecocokan sebagian pada properti sumber daya seperti yang diberikan dalam CloudFormation template yang disintesis. Tes ini mengharuskan properti yang disediakan ada dan memiliki nilai yang ditentukan, tetapi sumber daya juga dapat memiliki properti lain, yang tidak diuji.
SNSPernyataan Amazon kami menegaskan bahwa template yang disintesis berisi langganan, tetapi tidak ada tentang langganan itu sendiri. Kami menyertakan pernyataan ini terutama untuk menggambarkan bagaimana menegaskan jumlah sumber daya. Template
Kelas menawarkan metode yang lebih spesifik untuk menulis pernyataan terhadapResources
,Outputs
, dan Mapping
bagian dari template. CloudFormation
Matcher
Perilaku pencocokan paral default hasResourceProperties
dapat diubah menggunakan matcher dari kelas. Match
Matcher berkisar dari lunak () hingga ketat (Match.anyValue
). Match.objectEquals
Mereka dapat disarangkan untuk menerapkan metode pencocokan yang berbeda ke berbagai bagian properti sumber daya. Menggunakan Match.objectEquals
dan Match.anyValue
bersama-sama, misalnya, kita dapat menguji IAM peran mesin status lebih lengkap, sementara tidak memerlukan nilai spesifik untuk properti yang dapat berubah.
- TypeScript
-
// 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"],
],
},
},
},
],
},
})
);
- JavaScript
-
// 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"],
],
},
},
},
],
},
})
);
- Python
-
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",
],
],
},
},
},
],
},
}
),
)
- Java
-
// 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")
)
)
)
))
))
));
- C#
-
// 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" }
}
}
}
}
}
}
}
}
}
}
}
}
}));
Banyak CloudFormation sumber daya termasuk JSON objek serial yang direpresentasikan sebagai string. Match.serializedJson()
Matcher dapat digunakan untuk mencocokkan properti di dalamnya. JSON
Misalnya, mesin status Step Functions didefinisikan menggunakan string di Amazon States Language JSON berbasis. Kita akan gunakan Match.serializedJson()
untuk memastikan bahwa status awal kita adalah satu-satunya langkah. Sekali lagi, kita akan menggunakan pencocokan bersarang untuk menerapkan berbagai jenis pencocokan ke berbagai bagian objek.
- TypeScript
-
// 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(),
},
},
})
),
});
- JavaScript
-
// 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(),
},
},
})
),
});
- Python
-
# 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(),
},
},
}
)
),
},
)
- Java
-
// 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()
)
)
))
)
));
- C#
-
// 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() }
}}
}}
})
)}});
Menangkap
Seringkali berguna untuk menguji properti untuk memastikan mereka mengikuti format tertentu, atau memiliki nilai yang sama dengan properti lain, tanpa perlu mengetahui nilai pastinya sebelumnya. assertions
Modul ini menyediakan kemampuan ini di Capture
kelasnya.
Dengan menentukan Capture
instance di tempat nilai dihasResourceProperties
, nilai itu dipertahankan dalam objek. Capture
Nilai yang ditangkap sebenarnya dapat diambil menggunakan as
metode objek, termasukasNumber()
,asString()
, danasObject
, dan diuji. Gunakan Capture
dengan matcher untuk menentukan lokasi yang tepat dari nilai yang akan ditangkap dalam properti sumber daya, termasuk properti serialJSON.
Contoh berikut menguji untuk memastikan bahwa status awal mesin status kami memiliki nama yang dimulai denganStart
. Ini juga menguji bahwa keadaan ini ada dalam daftar negara bagian dalam mesin.
- TypeScript
-
// 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());
- JavaScript
-
// 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());
- Python
-
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()
- Java
-
// 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());
- C#
-
// 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()));
Tes snapshot
Dalam pengujian snapshot, Anda membandingkan seluruh template yang disintesis dengan CloudFormation template baseline yang disimpan sebelumnya (sering disebut “master”). Tidak seperti pernyataan berbutir halus, pengujian snapshot tidak berguna dalam menangkap regresi. Ini karena pengujian snapshot berlaku untuk seluruh template, dan hal-hal selain perubahan kode dapat menyebabkan perbedaan kecil (atau not-so-small) dalam hasil sintesis. Perubahan ini bahkan mungkin tidak memengaruhi penerapan Anda, tetapi masih akan menyebabkan pengujian snapshot gagal.
Misalnya, Anda dapat memperbarui CDK konstruksi untuk menggabungkan praktik terbaik baru, yang dapat menyebabkan perubahan pada sumber daya yang disintesis atau bagaimana mereka diatur. Atau, Anda dapat memperbarui CDK Toolkit ke versi yang melaporkan metadata tambahan. Perubahan nilai konteks juga dapat mempengaruhi template yang disintesis.
Namun, tes snapshot dapat sangat membantu dalam refactoring, selama Anda mempertahankan semua faktor lain yang dapat memengaruhi templat yang disintesis. Anda akan segera tahu jika perubahan yang Anda buat telah mengubah template secara tidak sengaja. Jika perubahan disengaja, cukup terima template baru sebagai baseline.
Misalnya, jika kita memiliki DeadLetterQueue
konstruksi ini:
- TypeScript
-
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(),
});
}
}
- JavaScript
-
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 }
- Python
-
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(),
)
- Java
-
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;
}
}
- C#
-
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()
});
}
}
}
Kita bisa mengujinya seperti ini:
- TypeScript
-
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();
});
});
- JavaScript
-
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();
});
});
- Python
-
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
- Java
-
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());
}
}
- C#
-
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());
}
}
}
Kiat untuk tes
Ingat, tes Anda akan hidup selama kode yang mereka uji, dan mereka akan dibaca dan dimodifikasi sesering mungkin. Oleh karena itu, perlu waktu sejenak untuk mempertimbangkan cara terbaik untuk menulisnya.
Jangan salin dan tempel baris pengaturan atau pernyataan umum. Sebaliknya, refaktorkan logika ini menjadi perlengkapan atau fungsi pembantu. Gunakan nama baik yang mencerminkan apa yang sebenarnya diuji oleh setiap tes.
Jangan mencoba melakukan terlalu banyak dalam satu tes. Lebih disukai, tes harus menguji satu dan hanya satu perilaku. Jika Anda secara tidak sengaja merusak perilaku itu, tepat satu tes akan gagal, dan nama tes akan memberi tahu Anda apa yang gagal. Namun, ini lebih ideal untuk diperjuangkan; terkadang Anda pasti akan (atau tidak sengaja) menulis tes yang menguji lebih dari satu perilaku. Tes snapshot, untuk alasan yang telah kami jelaskan, terutama rentan terhadap masalah ini, jadi gunakan dengan hemat.