AWS CDK Aplikasi uji - AWS Cloud Development Kit (AWS CDK) v2

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.

catatan

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. TemplateKelas 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. assertionsModul ini menyediakan kemampuan ini di Capturekelasnya.

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.