v AWS CDK 2 개발자 안내서입니다. 이전 CDK v1은 2022년 6월 1일에 유지 관리에 들어갔으며 2023년 6월 1일에 지원이 종료되었습니다.
기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.
를 사용하면 AWS CDK인프라가 사용자가 작성하는 다른 코드만큼 테스트할 수 있습니다. 클라우드 및 로컬에서 테스트할 수 있습니다. 이 주제에서는 클라우드에서 테스트하는 방법을 다룹니다. 로컬 테스트에 대한 지침은 섹션을 참조하세요를 사용하여 로컬로 AWS CDK 애플리케이션 테스트 및 빌드 AWS SAMCLI. AWS CDK 앱 테스트에 대한 표준 접근 방식은 AWS CDK의 어설션 모듈과 Jest
AWS CDK 앱에 대해 작성할 수 있는 테스트에는 두 가지 범주가 있습니다.
-
세분화된 어설션은 "이 리소스에는이 값이 있는이 속성이 있습니다"와 같이 생성된 AWS CloudFormation 템플릿의 특정 측면을 테스트합니다. 이러한 테스트는 회귀를 탐지할 수 있습니다. 또한 테스트 기반 개발을 사용하여 새 기능을 개발할 때도 유용합니다. (테스트를 먼저 작성한 다음 올바른 구현을 작성하여 통과할 수 있습니다.) 세분화된 어설션은 가장 자주 사용되는 테스트입니다.
-
스냅샷 테스트는 이전에 저장된 기준 AWS CloudFormation 템플릿과 비교하여 합성된 템플릿을 테스트합니다. 스냅샷 테스트를 통해 리팩터링된 코드가 원본과 정확히 동일한 방식으로 작동하는지 확인할 수 있으므로 자유롭게 리팩터링할 수 있습니다. 의도적으로 변경한 경우 향후 테스트에 사용할 새 기준선을 적용할 수 있습니다. 그러나 CDK 업그레이드로 인해 합성된 템플릿도 변경될 수 있으므로 구현이 올바른지 확인하기 위해 스냅샷에만 의존할 수는 없습니다.
참고
이 주제의 예로 사용되는 TypeScript , Python 및 Java 앱의 전체 버전은 GitHub에서 확인
시작
이러한 테스트를 작성하는 방법을 설명하기 위해 AWS Step Functions 상태 시스템과 AWS Lambda 함수가 포함된 스택을 생성합니다. Lambda 함수는 Amazon SNS 주제를 구독하고 메시지를 상태 시스템에 전달하기만 하면 됩니다.
먼저 CDK Toolkit를 사용하여 빈 CDK 애플리케이션 프로젝트를 생성하고 필요한 라이브러리를 설치합니다. 여기에서 사용할 구문은 모두 기본 CDK 패키지에 들어 있습니다. 이 패키지는 CDK Toolkit로 생성된 프로젝트의 기본 종속성입니다. 하지만 테스트 프레임워크를 설치해야 합니다.
$
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
키 추가 -
devDependencies
섹션에 Jest 및 해당 유형 추가 -
moduleFileExtensions
선언과 함께 새jest
최상위 키 추가
이러한 변경 사항은 다음 개요에 나와 있습니다. package.json
에 표시된 위치에 새 텍스트를 배치합니다. ’...’ 자리 표시자는 변경해서는 안 되는 파일의 기존 부분을 나타냅니다.
{
...
"scripts": {
...
"test": "jest"
},
"devDependencies": {
...
"@types/jest": "^24.0.18",
"jest": "^24.9.0"
},
"jest": {
"moduleFileExtensions": ["js"]
}
}
예제 스택
다음은 이 주제에서 테스트할 스택입니다. 앞서 설명한 것처럼 Lambda 함수와 Step Functions 상태 머신이 포함되어 있으며 하나 이상의 Amazon SNS 주제를 수락합니다. Lambda 함수는 Amazon SNS 주제를 구독하고 상태 시스템에 전달합니다.
앱을 테스트하기 위해 특별한 작업을 수행할 필요는 없습니다. 실제로 이 CDK 스택은 이 가이드의 다른 예제 스택과 어떤 중요한 방식으로도 다르지 않습니다.
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.grantStartExecution(func);
const subscription = new sns_subscriptions.LambdaSubscription(func);
for (const topic of props.topics) {
topic.addSubscription(subscription);
}
}
}
실제로 스택을 인스턴스화하지 않도록 앱의 기본 진입점을 수정합니다. 실수로 배포하고 싶지 않습니다. 테스트는 테스트를 위한 앱과 스택 인스턴스를 생성합니다. 이는 테스트 기반 개발과 결합할 때 유용한 전술입니다. 배포를 활성화하기 전에 스택이 모든 테스트를 통과해야 합니다.
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.
Lambda 함수
예제 스택에는 상태 시스템을 시작하는 Lambda 함수가 포함되어 있습니다. CDK가 Lambda 함수 리소스 생성의 일부로 번들링하고 배포할 수 있도록 이 함수의 소스 코드를 제공해야 합니다.
-
앱의 기본 디렉터리에
start-state-machine
폴더를 생성합니다. -
이 폴더에서 파일을 하나 이상 생성합니다. 예를 들어, 다음 코드를
start-state-machines/index.js
에 저장할 수 있습니다.exports.handler = async function (event, context) { return 'hello world'; };
하지만 실제로 스택을 배포하지 않으므로 모든 파일이 작동합니다.
테스트 실행
다음은 AWS CDK 앱에서 테스트를 실행하는 데 사용하는 명령입니다. 이는 동일한 테스트 프레임워크를 사용하여 모든 프로젝트에서 테스트를 실행하는 데 사용하는 것과 동일한 명령입니다. 빌드 단계가 필요한 언어의 경우 테스트가 컴파일되었는지 확인하기 위해 를 포함합니다.
$
tsc && npm test
세분화된 어설션
생성된 AWS CloudFormation 템플릿에 대해 어설션을 작성하기 때문에 세분화된 어설션으로 스택을 테스트하는 첫 번째 단계는 스택을 합성하는 것입니다.
StateMachineStackStack
에서는 상태 시스템에 전달하기 위해 Amazon SNS 주제를 전달해야 합니다. 따라서 테스트에서는 주제를 포함하는 별도의 스택을 생성합니다.
일반적으로 CDK 앱을 작성할 때 Stack
을 서브클래싱하고 스택의 생성자에서 Amazon SNS 토픽을 인스턴스화할 수 있습니다. 테스트에서는 Stack
을 직접 인스턴스화한 다음, 이 스택을 Topic
의 범위로 전달하여 스택에 연결합니다. 이는 기능적으로 동일하며 덜 상세합니다. 또한 테스트에만 사용되는 스택을 배포하려는 스택과 ‘다른 모습‘으로 만드는 데도 도움이 됩니다.
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);
}
이제 Lambda 함수와 Amazon SNS 구독이 생성되었다고 주장할 수 있습니다.
// 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);
Lambda 함수 테스트는 함수 리소스의 두 가지 특정 속성에 특정 값이 있다고 주장합니다. 기본적으로 hasResourceProperties
메서드는 합성된 CloudFormation 템플릿에 지정된 대로 리소스 속성에 대해 부분 일치를 수행합니다. 이 테스트를 수행하려면 제공된 속성이 존재하고 지정된 값이 있어야 하지만 리소스에는 테스트되지 않은 다른 속성도 있을 수 있습니다.
Amazon SNS 어설션은 합성된 템플릿에 구독이 포함되어 있지만 구독 자체에 대한 내용은 없다고 주장합니다. 주로 리소스 수를 기준으로 를 어설션하는 방법을 설명하기 위해 이 어설션을 포함했습니다. Template
클래스는 CloudFormation 템플릿의 Resources
, Outputs
및 Mapping
섹션에 대해 어설션을 작성하는 보다 구체적인 방법을 제공합니다.
매처
hasResourceProperties
의 기본 부분 일치 동작은 Match
클래스의 매처를 사용하여 변경할 수 있습니다.
매처의 범위는 관대(Match.anyValue
)에서 엄격(Match.objectEquals
)까지입니다. 서로 다른 매칭 방법을 리소스 속성의 서로 다른 부분에 적용하도록 중첩할 수 있습니다. 예를 들어 Match.objectEquals
와 Match.anyValue
를 함께 사용하면 상태 시스템의 IAM 역할을 더 완전하게 테스트할 수 있지만 변경될 수 있는 속성에는 특정 값이 필요하지 않습니다.
// 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"],
],
},
},
},
],
},
})
);
많은 CloudFormation 리소스에는 문자열로 표시되는 직렬화된 JSON 객체가 포함됩니다. Match.serializedJson()
매처를 사용하여 이 JSON 내의 속성을 일치시킬 수 있습니다.
예를 들어 Step Functions 상태 머신은 JSON 기반 Amazon States Language의 문자열을 사용하여 정의됩니다. Match.serializedJson()
을 사용하여 초기 상태가 유일한 단계인지 확인할 것입니다. 다시 말하지만 중첩된 매처를 사용하여 객체의 서로 다른 부분에 서로 다른 종류의 매칭을 적용할 것입니다.
// 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(),
},
},
})
),
});
캡처
속성을 테스트하여 정확한 값을 미리 알 필요 없이 특정 형식을 따르거나 다른 속성과 동일한 값을 갖도록 하는 것이 유용한 경우가 많습니다. assertions
모듈은 Capture
클래스에서 이 기능을 제공합니다.
hasResourceProperties
의 값 대신 Capture
인스턴스를 지정하면 해당 값이 Capture
객체에 유지됩니다. 실제로 캡처된 값은 asNumber()
, asString()
, asObject
를 비롯한 객체의 as
메서드를 사용하여 검색하고 테스트를 거칠 수 있습니다. 매처와 함께 Capture
를 사용하여 직렬화된 JSON 속성을 포함하여 리소스 속성 내에서 캡처할 값의 정확한 위치를 지정합니다.
다음 예에서는 상태 시스템의 시작 상태에 Start
로 시작하는 이름이 있는지 테스트합니다. 또한 이 상태가 시스템의 상태 목록 내에 있는지 테스트합니다.
// 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());
스냅샷 테스트
스냅샷 테스트에서는 합성된 전체 CloudFormation 템플릿을 이전에 저장된 기준(종종 ’마스터’라고 함) 템플릿과 비교합니다. 세분화된 어설션과 달리 스냅샷 테스트는 회귀를 포착하는 데 유용하지 않습니다. 이는 스냅샷 테스트가 전체 템플릿에 적용되고 코드 변경 이외의 사항으로 인해 합성 결과에 작은 또는 작지 않은 차이가 발생할 수 있기 때문입니다. 이러한 변경 사항은 배포에도 영향을 미치지 않을 수 있지만 스냅샷 테스트가 실패합니다.
예를 들어 CDK 구문을 업데이트하여 새로운 모범 사례를 통합하면 합성된 리소스 또는 리소스 구성 방식이 변경될 수 있습니다. 또는 CDK Toolkit를 추가 메타데이터를 보고하는 버전으로 업데이트할 수 있습니다. 컨텍스트 값을 변경하면 합성된 템플릿에도 영향을 미칠 수 있습니다.
스냅샷 테스트는 합성된 템플릿에 영향을 미칠 수 있는 다른 모든 요인을 일정하게 유지하는 한 리팩터링에 큰 도움이 될 수 있습니다. 변경 사항이 의도하지 않게 템플릿을 변경한 경우 즉시 알 수 있습니다. 변경 사항이 의도적인 경우 새 템플릿을 기준으로 수락하면 됩니다.
예를 들어 다음과 같은 DeadLetterQueue
구문이 있는 경우
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(),
});
}
}
다음과 같이 테스트할 수 있습니다.
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();
});
});
테스트 팁
테스트는 테스트하는 코드만큼 오래 지속되며, 자주 읽고 수정됩니다. 따라서 잠시 시간을 내어 가장 잘 쓰는 방법을 고려하는 것이 좋습니다.
설정 라인이나 일반적인 어설션을 복사하여 붙여넣지 마세요. 대신 이 로직을 픽스처 또는 도우미 함수로 리팩터링합니다. 각 테스트가 실제로 테스트한 내용을 반영하는 이름을 사용합니다.
한 번의 테스트로 너무 많이 시도하지 마세요. 가급적이면 테스트는 하나만 테스트해야 합니다. 실수로 해당 동작을 중단하면 정확히 하나의 테스트가 실패하고 테스트 이름이 실패한 항목을 알려주어야 합니다. 그러나 이는 노력하는 데 더 이상 좋습니다. 때로는 부득이하게(또는 실수로) 하나 이상의 동작을 테스트하는 테스트를 작성합니다. 스냅샷 테스트는 이미 설명한 이유로 특히 이 문제가 발생하기 쉬우므로 조금씩 사용합니다.