Este é o Guia do Desenvolvedor AWS CDK v2. O CDK v1 antigo entrou em manutenção em 1º de junho de 2022 e encerrou o suporte em 1º de junho de 2023.
As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.
Com o AWS CDK, sua infraestrutura pode ser tão testável quanto qualquer outro código que você escrever. Você pode testar na nuvem e localmente. Este tópico aborda como testar na nuvem. Para obter orientação sobre testes locais, consulteTeste e crie AWS CDK aplicativos localmente com o AWS SAM CLI. A abordagem padrão para testar AWS CDK aplicativos usa o módulo AWS CDK de asserções do e estruturas de teste populares, como Jest
Há duas categorias de testes que você pode escrever para AWS CDK aplicativos.
-
Asserções refinadas testam aspectos específicos do AWS CloudFormation modelo gerado, como “esse recurso tem essa propriedade com esse valor”. Esses testes podem detectar regressões. Eles também são úteis quando você desenvolve novos atributos usando o desenvolvimento orientado a testes. (Você pode escrever um teste primeiro e depois fazê-lo ser aprovado escrevendo uma implementação correta.) Afirmações minuciosas são os testes usados com mais frequência.
-
Os testes instantâneos testam o modelo sintetizado em relação a um AWS CloudFormation modelo de linha de base armazenado anteriormente. Os testes de snapshots possibilitam a você refatorar livremente, pois você pode ter certeza de que o código refatorado funciona exatamente da mesma forma que o original. Se as alterações foram intencionais, é possível aceitar uma nova linha de base para futuros testes. No entanto, as atualizações do CDK também podem fazer com que os modelos sintetizados sejam alterados, então você não pode confiar apenas em snapshots para garantir que sua implementação esteja correta.
nota
Versões completas dos TypeScript aplicativos Python e Java usados como exemplos neste tópico estão disponíveis
Conceitos básicos
Para ilustrar como escrever esses testes, criaremos uma pilha que contém uma máquina de AWS Step Functions estados e uma AWS Lambda função. A função do Lambda está inscrita em um tópico do Amazon SNS e simplesmente encaminha a mensagem para a máquina de estado.
Primeiro, crie um projeto de aplicação CDK vazio usando o CDK Toolkit e instalando as bibliotecas de que precisaremos. Os constructos que usaremos estão todos no pacote principal do CDK, que é uma dependência padrão em projetos criados com o CDK Toolkit. Entretanto, você deve instalar sua estrutura de teste.
$
mkdir state-machine && cd state-machine cdk init --language=typescript npm install --save-dev jest @types/jest
Crie um diretório para seus testes.
$
mkdir test
Edite os package.json
do projetos para dizer ao NPM como executar o Jest e para dizer ao Jest quais tipos de arquivos coletar. As mudanças necessárias são as seguintes.
-
Adicione uma nova chave
test
à seçãoscripts
-
Adicione Jest e seus tipos à seção
devDependencies
-
Adicione uma nova chave de nível superior
jest
com uma declaraçãomoduleFileExtensions
Essas mudanças são mostradas no esboço a seguir. Coloque o novo texto onde indicado em package.json
. Os espaços reservados “...” indicam partes existentes do arquivo que não devem ser alteradas.
{
...
"scripts": {
...
"test": "jest"
},
"devDependencies": {
...
"@types/jest": "^24.0.18",
"jest": "^24.9.0"
},
"jest": {
"moduleFileExtensions": ["js"]
}
}
A pilha de exemplos
Aqui está a pilha que será testada neste tópico. Como descrevemos anteriormente, ele contém uma função do Lambda e uma máquina de estado Step Functions e aceita um ou mais tópicos do Amazon SNS. A função do Lambda está inscrita nos tópicos do Amazon SNS e os encaminha para a máquina de estado.
Você não precisa fazer nada especial para tornar a aplicação testável. Na verdade, essa pilha de CDK não é diferente em nada importante das outras pilhas de exemplo neste Guia.
Coloque o seguinte código em 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);
}
}
}
Modificaremos o ponto de entrada principal da aplicação para que não instanciemos realmente nossa pilha. Não queremos implantá-lo acidentalmente. Nossos testes criarão uma aplicação e uma instância da pilha para testes. Essa é uma tática útil quando combinada com o desenvolvimento orientado a testes: certifique-se de que a pilha seja aprovada em todos os testes antes de habilitar a implantação.
Em 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.
A função do Lambda
Nossa pilha de exemplos inclui uma função do Lambda que inicia nossa máquina de estado. Precisamos fornecer o código-fonte dessa função para que o CDK possa agrupá-la e implantá-la como parte da criação do recurso da função do Lambda.
-
Crie a pasta
start-state-machine
no diretório principal da aplicação. -
Nessa pasta, crie pelo menos um arquivo. Por exemplo, você pode salvar o código a seguir no
start-state-machines/index.js
.exports.handler = async function (event, context) { return 'hello world'; };
No entanto, qualquer arquivo funcionará, já que não vamos realmente implantar a pilha.
Execução de testes
Para referência, aqui estão os comandos que você usa para executar testes no seu AWS CDK aplicativo. Esses são os mesmos comandos que você usaria para executar os testes em qualquer projeto usando a mesma estrutura de teste. Para linguagens que exigem uma etapa de compilação, inclua-a para garantir que seus testes tenham sido compilados.
$
tsc && npm test
Afirmações minuciosas
A primeira etapa para testar uma pilha com afirmações refinadas é sintetizar a pilha, porque estamos escrevendo afirmações com base no modelo gerado. AWS CloudFormation
Nosso StateMachineStackStack
requer que passemos o tópico do Amazon SNS para ser encaminhado para a máquina estadual. Então, em nosso teste, criaremos uma pilha separada para conter o tópico.
Normalmente, ao escrever uma aplicação CDK, você pode criar uma subclasse Stack
e instanciar o tópico do Amazon SNS no construtor da pilha. Em nosso teste, instanciamos o Stack
diretamente e, em seguida, passamos essa pilha como escopo, anexando-a à pilha Topic
. Isso é funcionalmente equivalente e menos detalhado. Também ajuda a fazer com que as pilhas usadas somente em testes “pareçam diferentes” das pilhas que você pretende implantar.
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);
}
Agora podemos afirmar que a função do Lambda e a assinatura do Amazon SNS foram criadas.
// 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);
Nosso teste de função do Lambda afirma que duas propriedades específicas do recurso da função têm valores específicos. Por padrão, o hasResourceProperties
método executa uma correspondência parcial nas propriedades do recurso, conforme indicado no modelo sintetizado CloudFormation . Esse teste exige que as propriedades fornecidas existam e tenham os valores especificados, mas o recurso também pode ter outras propriedades que não foram testadas.
Nossa afirmação do Amazon SNS afirma que o modelo sintetizado contém uma assinatura, mas não diz nada sobre a assinatura em si. Incluímos essa afirmação principalmente para ilustrar como afirmar a contagem de recursos. A Template
classe oferece métodos mais específicos para escrever afirmações nas Mapping
seções Resources
Outputs
, e do CloudFormation modelo.
Combinadores
O comportamento padrão de correspondência parcial do hasResourceProperties
pode ser alterado usando combinadores da classe Match
.
Os combinadores variam de tolerantes (Match.anyValue
) a estritos (Match.objectEquals
). Eles podem ser agrupados para aplicar diferentes métodos de correspondência a diferentes partes das propriedades do recurso. Usando Match.objectEquals
e Match.anyValue
juntos, por exemplo, podemos testar o perfil do IAM da máquina de estado de forma mais completa, sem exigir valores específicos para propriedades que possam mudar.
// 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"],
],
},
},
},
],
},
})
);
Muitos CloudFormation recursos incluem objetos JSON serializados representados como strings. O combinador Match.serializedJson()
pode ser usado para combinar propriedades dentro desse JSON.
Por exemplo, as máquinas de estado do Step Functions são definidas usando uma string na Amazon States Language baseada em JSON. Usaremos Match.serializedJson()
para garantir que nosso estado inicial seja a única etapa. Novamente, usaremos combinadores aninhados para aplicar diferentes tipos de correspondência a diferentes partes do objeto.
// 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(),
},
},
})
),
});
Capturar
Geralmente, é útil testar propriedades para garantir que elas sigam formatos específicos ou tenham o mesmo valor de outra propriedade, sem precisar saber seus valores exatos com antecedência. O módulo assertions
fornece esse recurso em sua classe Capture
.
Ao especificar uma instância Capture
no lugar de um valor no hasResourceProperties
, esse valor é retido no objeto Capture
. O valor real capturado pode ser recuperado usando os métodos as
do objeto, incluindo asNumber()
, asString()
e asObject
, e passando por testes. Use Capture
com um combinador para especificar a localização exata do valor a ser capturado nas propriedades do recurso, incluindo propriedades JSON serializadas.
O exemplo a seguir testa para garantir que o estado inicial de nossa máquina de estado tenha um nome que comece com Start
. Ele também testa se esse estado está presente na lista de estados da máquina.
// 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());
Testes instantâneos
No teste de instantâneo, você compara todo o modelo sintetizado com um CloudFormation modelo de linha de base armazenado anteriormente (geralmente chamado de “mestre”). Ao contrário das afirmações refinadas, o teste instantâneo não é útil para capturar regressões. Isso ocorre porque o teste de instantâneo se aplica a todo o modelo, e coisas além das alterações no código podem causar pequenas (ou not-so-small) diferenças nos resultados da síntese. Essas alterações podem nem mesmo afetar sua implantação, mas ainda assim farão com que um teste instantâneo falhe.
Por exemplo, você pode atualizar um constructo de CDK para incorporar uma nova prática recomendada, que pode causar alterações nos recursos sintetizados ou na forma como eles são organizados. Como alternativa, você pode atualizar o CDK Toolkit para uma versão que relata metadados adicionais. Alterações nos valores de contexto também podem afetar o modelo sintetizado.
No entanto, os testes instantâneos podem ser de grande ajuda na refatoração, desde que você mantenha constantes todos os outros fatores que possam afetar o modelo sintetizado. Você saberá imediatamente se uma alteração que você fez alterou involuntariamente o modelo. Se a alteração for intencional, basta aceitar o novo modelo como linha de base.
Por exemplo, se tivermos ess constructo 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(),
});
}
}
Podemos testá-lo assim:
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();
});
});
Dicas para testes
Lembre-se de que seus testes durarão tanto quanto o código testado e serão lidos e modificados com a mesma frequência. Portanto, vale a pena considerar a melhor forma de escrevê-las.
Não copie e cole linhas de configuração ou afirmações comuns. Em vez disso, refatore essa lógica em funções fixas ou auxiliares. Use bons nomes que reflitam o que cada teste realmente testa.
Não tente fazer muita coisa em um teste. De preferência, um teste deve testar apenas um comportamento. Se você quebrar acidentalmente esse comportamento, exatamente um teste falhará e o nome do teste deverá indicar o que falhou. No entanto, esse é mais um ideal a ser buscado; às vezes, você inevitavelmente (ou inadvertidamente) escreve testes que testam mais de um comportamento. Os testes instantâneos são, por motivos que já descrevemos, especialmente propensos a esse problema, portanto, use-os com moderação.