返回文章列表

CDK應用程式自動化測試與除錯實戰

本文探討 AWS CDK 應用程式的自動化測試方法,涵蓋單元測試、整合測試、端對端測試以及快照測試等不同測試型別與策略,同時提供程式碼範例與詳細解說,幫助開發者建構可靠且高效能的雲端應用程式。文章也包含如何在 CI/CD 管線中整合測試流程,並利用 Slack 進行通知,實作全面的自動化測試與佈署流程。

Web 開發 雲端運算

隨著 CDK 應用程式邏輯日益複雜,自動化測試的重要性也隨之提升。測試能及早發現程式碼問題,確保應用程式功能和狀態符合預期,尤其在多人協作開發時,自動化測試更能避免程式碼變更對 CDK 基礎設施造成負面影響。本文將介紹 CDK 測試的型別與策略,並提供實戰範例,說明如何將測試整合至 CDK 開發流程,以及如何檢查日誌和除錯程式碼來排除應用程式故障。前一章已建立 CI/CD 自動化佈署管道,本章將進一步說明如何撰寫 CDK 應用程式的自動化測試,並以程式碼範例展示如何使用 AWS CodePipeline 和 GitHub 進行持續整合。此外,文章也涵蓋設定前端環境變數、建置與佈署命令,以及整合 Slack 通知等實務技巧。

程式碼結構

本文中的程式碼結構是為了讓TypeScript初學者更容易理解而設計的,但並非最佳實踐。可以參考CDK社群中的一些設計模式,例如使用TSyringe進行依賴注入。

檔案參考

可以參考AWS CDK的官方檔案來瞭解如何組態構件(constructs)。也可以透過在VSCode中按Ctrl + Click(Mac:Command + Click)來檢視屬性名稱的輸入屬性。

自動化測試與除錯:AWS CDK 應用程式的實戰

在開發可靠且高效能的應用程式時,測試是不可或缺的一環。對於 AWS CDK 應用程式的開發來說,這一點同樣重要。本章將探討 AWS CDK 應用程式的自動化測試世界,確保應用程式的功能性和狀態符合預期,即使在新增元件或進行變更時也是如此。

自動化測試的重要性

自動化測試的主要目的是盡快執行測試清單,以便快速整合變更並推動軟體演進。隨著 CDK 應用程式邏輯的複雜度增加,自動測試程式碼變得越來越重要。根據雲端架構的不同,您的堆積疊可能對某些變更非常敏感。在多個程式設計師共同開發專案時,確保程式碼和堆積疊變更不會對 CDK 基礎設施產生不利影響至關重要。

測試型別與策略

本章將介紹不同型別的測試、撰寫有效測試的策略,以及將測試整合到 CDK 開發工作流程的最佳實踐。我們還將探討透過檢查日誌和除錯程式碼來排除應用程式故障的其他方法。

CDK 應用程式測試實戰

在前一章中,我們建立了一個完整的 CI/CD 管道,用於自動化佈署 CDK 應用程式。現在,我們將進一步探討如何為這些應用程式撰寫自動化測試。

程式碼範例:使用 AWS CodePipeline 和 GitHub 進行持續整合

// pipelineConfig.ts
export const pipelineConfig = {
  // ...
  pre_build: {
    'on-failure': 'ABORT',
    commands: [
      'cd web',
      'yarn install',
      // 設定前端所需的環境變數
      `echo '{
        "domain_name": "${domainName}",
        "backend_subdomain": "${backendSubdomain}",
        "frontend_subdomain": "${frontendSubdomain}",
        "backend_dev_subdomain": "${backendDevSubdomain}",
        "frontend_dev_subdomain": "${frontendDevSubdomain}"
      }' > src/config.json`,
      'cd ../server',
      'yarn install',
      'cd ../infrastructure',
      'yarn install',
    ],
  },
  // ...
};

內容解密:

  1. 前置建置階段:在 pre_build 階段,我們進入 webserverinfrastructure 目錄,並執行 yarn install 以安裝所需的 Node.js 套件。
  2. 環境變數設定:我們為前端應用程式設定必要的環境變數,並將其寫入 config.json 檔案中。
  3. 建置與佈署命令:根據環境的不同,建置和佈署命令也會有所不同,這些命令在 pipelineConfig.ts 檔案中定義。

Slack 通知整合

為了實作完整的通知系統,我們將 Slack 與 AWS CodePipeline 整合。以下是相關程式碼:

// infrastructure/lib/constructs/Pipeline/index.ts
const slackConfig = new SlackChannelConfiguration(this, 'SlackChannel', {
  slackChannelConfigurationName: `${props.environment}-Pipeline-Slack-Channel-Config`,
  slackWorkspaceId: workspaceId || '',
  slackChannelId: channelId || '',
});

const snsTopic = new Topic(this, `${props.environment}-Pipeline-SlackNotificationsTopic`);
const rule = new NotificationRule(this, 'NotificationRule', {
  source: this.pipeline,
  events: [
    'codepipeline-pipeline-pipeline-execution-failed',
    'codepipeline-pipeline-pipeline-execution-canceled',
    // ...
  ],
  targets: [snsTopic],
});
rule.addTarget(slackConfig);

圖表翻譯:

此圖示展示了 Slack 與 AWS CodePipeline 的整合架構。

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表翻譯:

rectangle "事件" as node1
rectangle "通知" as node2

node1 --> node2

@enduml
  1. Slack 設定:使用 SlackChannelConfiguration 設定 Slack 工作區和頻道。
  2. SNS 主題:建立 SNS 主題來處理管道事件,並將 Slack 設定為目標。

測試與除錯 AWS CDK 應用程式

理解測試術語

在探討 CDK 測試之前,我們先來澄清幾個重要的測試相關定義。首先,我們需要了解不同型別的測試:

  • 單元測試(Unit Tests):單元測試涉及對每個函式進行獨立測試,以確保在給定預期和非預期輸入時,函式能夠傳回預期的輸出或優雅地處理錯誤。
  • 整合測試(Integration Tests):整合測試是一種比單元測試更高層級的測試,通常涉及多個模組之間的互動。它們確保不同的函式和模組能夠協同工作,以實作特定的功能。
  • 端對端測試(End-to-End Tests, E2E):端對端測試提供了最高層級的測試,通常涉及對真實系統執行測試場景。
  • 迴歸測試(Regression Testing):迴歸測試是在系統發生變化時進行的,用於發現不一致性和損壞的行為。

在我們的經驗中,測試的層級越高,就越能有效地發現程式碼中的問題。這是因為端對端測試涉及多個元件、函式和模組,所有這些都必須正確地協同工作,才能使測試場景成功透過。

編寫測試的價值

編寫測試的價值可以總結為以下兩點:

  • 測試是之前功能的檢查清單。當它們透過時,我們知道新增的功能並沒有破壞之前的特性。
  • 隨著對程式碼函式庫的信心增加,開發者的生產力也會提高。我們不再那麼擔心無意中引入錯誤。

測試 CDK 應用程式的多種方式

CDK 測試通常透過以下兩種方式進行:

  • 細粒度斷言(Fine-Grained Assertions):這種測試類別似於單元測試,用於檢測迴歸問題。它們使用 AWS CDK 斷言模組(AWS CDK Assertions Module)進行,該模組與常見的測試框架(如 Jest)整合,基本上是測試 CDK 應用程式中的某些模組是否具有特定的屬性。
  • 快照測試(Snapshot Tests):快照測試更接近於整合測試,它們不關心如何組態 CDK 應用程式。它們將合成後的 CDK 堆積疊輸出與之前的狀態進行比較,以檢測變化。快照測試是重構 CDK 應用程式的好方法,同時確保輸出結果相同。

AWS 建議結合使用這兩種測試方法,以確保 CDK 應用程式的完整性。快照測試非常有趣,因為它們讓我們可以自由地重構程式碼,只需關注合成過程的結果。

程式碼範例:使用 AWS CDK 斷言模組進行細粒度斷言

const cdk = require('aws-cdk-lib');
const { Template } = require('aws-cdk-lib/assertions');
const { MyStack } = require('../lib/my-stack');

test('MyStack has a specific resource', () => {
  const app = new cdk.App();
  const stack = new MyStack(app, 'MyStack');
  const template = Template.fromStack(stack);
  
  template.hasResourceProperties('AWS::EC2::Instance', {
    ImageId: 'ami-0c94855ba95c71c99',
  });
});

內容解密:

此範例展示瞭如何使用 AWS CDK 斷言模組來驗證 CDK 堆積疊中是否存在特定的資源屬性。我們首先建立了一個 MyStack 例項,然後使用 Template.fromStack 方法生成堆積疊的範本。最後,我們使用 hasResourceProperties 方法來斷言堆積疊中是否存在一個具有特定 ImageId 的 EC2 例項。

快照測試的優勢與挑戰

快照測試讓我們能夠放心地重構 CDK 應用程式,同時確保輸出結果的一致性。然而,當 CDK 程式函式庫升級時,快照測試也可能會受到影響。因此,建議在單獨的變更中進行程式函式庫升級,以便更好地隔離和處理相關問題。

測試與故障排除 AWS CDK 應用程式

執行測試

本章的程式碼附帶了一些預先組態的細粒度測試和快照測試,您可以藉此練習執行這些測試。這些測試並非詳盡無遺(即未涵蓋所有功能),且在您的專案中組態測試的最佳方式可能與此不同。然而,希望這能啟發您進行改進。讓我們來看看如何執行本章的測試。

組態環境

就像前一章一樣,當我們為不同的環境執行 CDK 堆積疊時,我們需要在第六章的基礎設施目錄中建立一個 .env 檔案,以傳遞一些有關佈署的值來測試堆積疊。我們將此檔案命名為 .env.testing,以便僅在執行測試時才會被讀取。因此,請繼續在基礎設施目錄下建立一個 .env.testing 檔案,並填入與第五章 .env 檔案類別似的值:

CDK_DEFAULT_ACCOUNT=<您的 12 位 AWS 帳戶 ID>
CDK_DEFAULT_REGION=us-east-1

CDK 需要這些環境變數來填充 CloudFormation 輸出中的某些值。這些值也可以在佈署堆積疊時透過 --profile 旗標傳遞給堆積疊,但由於我們在此使用 jest 和 yarn 命令執行測試,因此需要使用環境變數傳遞它們。

AWS 帳戶 ID

您的 AWS 帳戶 ID 是一個 12 位的唯一識別碼。從 AWS 控制檯或 CLI 工具中檢索該值的步驟可以在 https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html 找到。

接下來,我們需要建置前端程式碼。為此,請切換到 web 資料夾並執行以下命令:

$ yarn

接著執行以下命令:

$ yarn build:dev

接下來,在基礎設施目錄下,安裝所有依賴項後,只需執行以下命令:

$ yarn test

您應該能夠看到一個輸出,告知您總共成功執行了三個斷言測試和一個快照測試:

測試組態

讓我們來看看這些測試是如何組態的。如前所述,這是與第五章相同的 CDK 應用程式,新增了您將在 infrastructure/test 目錄中找到的測試。您將看到一個名為 chapter-6-stack.test.ts 的檔案,其中包含我們的測試邏輯,以及一個名為 __snapshots__ 的目錄,該目錄是由 CDK 的工具在最初執行快照測試時自動產生的。讓我們先檢查測試檔案:

describe('Testing Chapter 6 code.', () => {
  // 使用斷言測試:
  test('The stack has a ECS cluster configured in the right way.', () => {
    // 一些測試程式碼
  });
  test('The stack has a RDS instance configured in the right way.', () => {
    // 一些測試程式碼
  });
  test('The stack has the VPC configured in the right way.', () => {
    // 一些測試程式碼
  });
  // 使用快照測試:
  it('Matches the snapshot.', () => {
    // 一些測試程式碼
  });
});

前三個測試是我們使用 test 宣告的斷言測試,而最後一個測試(快照測試)是使用 it 函式宣告的。這兩個函式的工作方式相同,但我們在此使用它們來區分這兩種測試。讓我們首先介紹斷言測試。

斷言測試

斷言測試是一種驗證,它確認一段程式碼是否按照預期執行。在 AWS CDK 的背景下,這些測試可用於確保由 CDK 結構操作的資源被正確建立、修改或刪除。AWS CDK 中斷言測試的其他潛在用途包括驗證 AWS 服務的正確使用、許可證的正確設定以及 CDK 應用程式和堆積疊的正確佈署。這些測試涉及定義 CDK 堆積疊將建立的資源的一組預定屬性,然後將它們與建立的資源的實際屬性進行比較。如果實際屬性與預期屬性匹配,則測試透過;如果它們不匹配,則測試失敗。在測試失敗的情況下,開發人員會收到警示以調查和解決問題。

讓我們檢查第一個測試。此測試旨在確保我們為 TODO 應用程式建立的 ECS 群集按照我們預期的方式組態。為此,我們必須透過建立 CDK App 和 Chapter6Stack 的例項來設定測試:

const app = new App();
const chapter6Stack = new Chapter6Stack(app, 'Chapter6Stack', {
  env: {
    region: parsed?.CDK_DEFAULT_REGION,
    account: parsed?.CDK_DEFAULT_ACCOUNT,
  },
});

程式碼詳解:

此段程式碼建立了一個 CDK App 和一個 Chapter6Stack 的例項,並傳遞必要的環境變數。這是為了確保堆積疊在正確的區域和帳戶下被建立和測試。

接下來的程式碼區塊是從 CDK 堆積疊中提取 CloudFormation 範本,以便對其執行某些斷言:

const template = Template.fromStack(chapter6Stack);
template.resourceCountIs('AWS::ECS::Cluster', 1);
template.resourceCountIs('AWS::ECS::TaskDefinition', 1);
template.resourceCountIs('AWS::ECS::Service', 1);

程式碼詳解:

此段程式碼從 Chapter6Stack 中提取 CloudFormation 範本,並斷言該範本中包含正確數量的 ECS 群集、TaskDefinition 和 Service 資源。這確保了我們的堆積疊按照預期建立了必要的資源。

我們的下一個斷言更深入。template.hasResourceProperties 函式確保某個資源(在本例中為 TaskDefinition 類別)具有我們在斷言中定義的屬性。其中一些屬性可以包括記憶體、Docker 映象和埠組態:

template.hasResourceProperties('AWS::ECS::TaskDefinition', {
  ContainerDefinitions: [
    {
      Environment: [
        {
          Name: Match.exact('NODE_ENV'),
          Value: Match.exact('test'),
        },
      ],
      Essential: true,
      Image: Match.objectEquals({
        'Fn::Sub': Match.stringLikeRegexp('ecr'), // 一個包含 ECR 的字串。
      }),
      LogConfiguration: {
        LogDriver: Match.stringLikeRegexp('awslogs'),
        // ...
      },
      Memory: 256,
      Name: Match.exact('Express-test'),
      PortMappings: [
        {
          ContainerPort: 80,
          HostPort: 0,

程式碼詳解:

此段程式碼檢查 TaskDefinition 資源是否具有正確的屬性,例如環境變數、Docker 映象、記錄組態、記憶體和埠對映。這確保了我們的 TaskDefinition 資源按照預期被組態。