Blog AWS Indonesia

Pengujian Infrastruktur dengan AWS Cloud Development Kit (CDK)

AWS Cloud Development Kit (CDK) memungkinkan Anda untuk mendeskripsikan infrastruktur aplikasi Anda menggunakan bahasa pemrograman tujuan-umum, seperti naskah, JavaScript atau Python. Ini membuka jalan yang mudah untuk bekerja dengan infrastruktur Anda, seperti menggunakan IDE favorit Anda, mendapatkan keuntungan dari pelengkapan otomatis, menciptakan abstraksi dengan cara yang sudah dimengerti, mendistribusikannya menggunakan manajer paket standar ekosistem Anda, dan tentu saja: menulis pengujian untuk infrastruktur Anda seperti Anda akan menulis pengujian untuk aplikasi Anda.

Dalam posting blog ini Anda akan belajar bagaimana menulis pengujian untuk kode infrastruktur Anda yang memakai TypeScript menggunakan Jest. Kode untuk JavaScript akan sama (tanpa tipe variabel), sedangkan kode untuk Python akan mengikuti pola pengujian yang sama. Sayangnya, tidak ada library Python siap pakai untuk Anda gunakan saat tulisan ini dibuat.

Pendekatan

Pola untuk menulis pengujian untuk infrastruktur sangat mirip dengan bagaimana Anda akan menulis mereka untuk kode aplikasi: Anda mendefinisikan kasus uji (test case) seperti yang biasanya Anda lakukan dalam test framework pilihan Anda. Di dalam kasus uji Anda menginstansiasi konstruksi (construct) seperti yang akan Anda lakukan di aplikasi CDK Anda, dan kemudian Anda membuat pernyataan tentang template AWS CloudFormation yang dihasilkan kode yang Anda tulis.

Satu hal yang berbeda dari pengujian normal adalah pernyataan yang Anda tulis pada kode Anda. TypeScript CDK mempunyai assertion library (@aws -cdk/assert) yang membuatnya mudah untuk membuat pernyataan pada infrastruktur Anda. Bahkan, semua konstruksi di AWS Construct Library yang termasuk CDK diuji dengan cara ini, sehingga kita dapat memastikan mereka lakukan-dan terus melakukan-apa yang seharusnya mereka lakukan. Assertion library kami saat ini hanya tersedia untuk TypeScript dan pengguna JavaScript, tetapi akan dibuat tersedia untuk pengguna bahasa lain akhirnya.

Secara umum, ada beberapa kelas pengujian yang akan Anda tulis:

  • Pengujian snapshot (snapshot test, juga dikenal sebagai pengujian “golden master”). Menggunakan Jest, ini sangat mudah untuk ditulis. Mereka menegaskan bahwa templat CloudFormation kode menghasilkan adalah sama seperti ketika pengujian ditulis. Jika ada perubahan, kerangka uji akan menunjukkan perubahan dalam diff. Jika perubahan tidak disengaja, Anda akan pergi dan memperbarui kode sampai pengujian lulus lagi, dan jika perubahan itu disengaja, Anda akan memiliki opsi untuk menerima templat baru sebagai “golden master” baru.
  • Assertion yang sangat terperinci tentang templat. Pengujian snapshot mudah dan cepat untuk ditulis, dan memberikan tingkat dasar keamanan bahwa perubahan kode Anda tidak mengubah templat yang dihasilkan. Masalahnya dimulai saat Anda sengaja memperkenalkan perubahan. Katakanlah Anda memiliki pengujian snapshot untuk memverifikasi keluaran untuk fitur A, dan Anda sekarang menambahkan fitur B untuk construct Anda. Ini mengubah templat yang dihasilkan, dan pengujian snapshot Anda akan rusak, meskipun fitur A masih bekerja sebagaimana dimaksud. Snapshot tidak dapat memberitahu bagian mana dari templat yang relevan dengan fitur A dan bagian mana yang relevan dengan fitur B. Untuk mengatasi ini, Anda juga dapat menulis assertion yang lebih detail, seperti “sumber daya ini memiliki properti ini” (dan saya tidak peduli dengan yang lain).
  • Pengujian validasi. Salah satu keuntungan dari bahasa pemrograman tujuan umum adalah bahwa kita dapat menambahkan pemeriksaan validasi tambahan dan kesalahan di awal, menghemat waktu uji coba dan kesalahan bagi pengguna construct. Anda akan menguji dengan menggunakan construct dengan cara yang tidak valid dan menegaskan sebuah error terjadi.

Sebuah Contoh: Dead Letter Queue

Katakanlah Anda ingin menulis sebuah construct DeadletterQueue. Dead Letter Queue digunakan untuk menyimpan pesan dari antrian lain jika mereka terlalu sering gagal melakukan pengiriman. Ini umumnya adalah berita buruk jika pesan berakhir di Dead Letter Queue, karena ini menunjukkan ada sesuatu yang salah dengan pemrosesan antrian. Untuk itu, DeadletterQueue Anda akan datang dengan alarm yang dinyalakan jika ada pesan dalam antrian Dead Letter Queue. Pengguna construct dapat melakukan tindakan apapun pada alarm pembakaran, seperti memberi tahu topik SNS.

Mulailah dengan membuat proyek construct library baru yang kosong menggunakan CDK CLI dan memasang beberapa construct library yang akan kita butuhkan:

$ cdk init --language=typescript lib
$ npm install @aws-cdk/aws-sqs @aws-cdk/aws-cloudwatch

Kode CDK mungkin terlihat seperti ini (menempatkan ini dalam berkas bernama lib/dead-letter-queue.ts):

import cloudwatch = require('@aws-cdk/aws-cloudwatch');
import sqs = require('@aws-cdk/aws-sqs');
import { Construct, Duration } from '@aws-cdk/core';

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(),
    });
  }
}

Menulis Pengujian

Anda akan menulis pengujian untuk construct ini. Pertama, mulai dengan menginstal Jest dan assertion library CDK:

$ npm install --save-dev jest @types/jest @aws-cdk/assert

Anda juga harus mengubah berkas package.json dalam proyek Anda untuk memberitahu NPM untuk menjalankan Jest, dan memberitahu Jest jenis file untuk dikumpulkan:

{
  ...
 "scripts": {
    ...
    "test": "jest"
  },
  "devDependencies": {
    ...
    "@types/jest": "^24.0.18",
    "jest": "^24.9.0",
  },
  "jest": {
    "moduleFileExtensions": ["js"]
  }
}

Anda sekarang dapat menulis pengujian. Tempat yang baik untuk memulai adalah memeriksa bahwa periode retensi antrial adalah 2 minggu. Jenis pengujian paling sederhana yang bisa Anda tulis adalah pengujian snapshot, jadi mulailah dengan itu. Menempatkan berikut dalam sebuah berkas bernama test/dead-letter-queue.test.ts:

import { SynthUtils } from '@aws-cdk/assert';
import { Stack } from '@aws-cdk/core';

import dlq = require('../lib/dead-letter-queue');

test('dlq creates an alarm', () => {
  const stack = new Stack();
  new dlq.DeadLetterQueue(stack, 'DLQ');
  expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
});

Anda sekarang dapat mengkompilasi dan menjalankan pengujian:

$ npm run build
$ npm test

Jest akan menjalankan pengujian Anda dan memberitahu Anda bahwa itu telah merekam snapshot dari pengujian Anda.

PASS  test/dead-letter-queue.test.js
 ✓ dlq creates an alarm (55ms)
 › 1 snapshot written.
Snapshot Summary
› 1 snapshot written

Snapshot disimpan dalam direktori bernama __snapshots__. Jika Anda melihat snapshot, Anda akan melihatnya hanya berisi salinan templat CloudFormation yang akan dihasilkan stack kami:

exports[`dlq creates an alarm 1`] = `
Object {
  "Resources": Object {
    "DLQ581697C4": Object {
      "Type": "AWS::SQS::Queue",
    },
    "DLQAlarm008FBE3A": Object {
     "Properties": Object {
        "AlarmDescription": "There are messages in the Dead Letter Queue",
        "ComparisonOperator": "GreaterThanOrEqualToThreshold",
...

Selamat! Anda telah menulis dan menjalankan pengujian pertama Anda. Jangan lupa untuk melakukan direktori snapshot untuk kontrol versi sehingga snapshot akan disimpan dan memiliki versi dengan kode Anda.

Menggunakan snapshot

Untuk memastikan pengujian bekerja, Anda akan memecahkan pengujian untuk memastikan kerusakan terdeteksi. Untuk melakukannya, dalam berkas dead-letter-queue.ts Anda, mengubah periode cloudWatch.Alarm ke 1 menit (bukan default 5 menit), dengan menambahkan argumen periode:

this.messagesInQueueAlarm = new cloudwatch.Alarm(this, 'Alarm', {
  // ...
  period: Duration.minutes(1),
});

Jika Anda sekarang membangun dan menjalankan pengujian lagi, Jest akan memberitahu Anda bahwa templat berubah:

$ npm run build && npm test

FAIL test/dead-letter-queue.test.js
✕ dlq creates an alarm (58ms)

● dlq creates an alarm

expect(received).toMatchSnapshot()

Snapshot name: `dlq creates an alarm 1`

- Snapshot
+ Received

@@ -19,11 +19,11 @@
               },
             ],
             "EvaluationPeriods": 1,
             "MetricName": "ApproximateNumberOfMessagesVisible",
             "Namespace": "AWS/SQS",
     -       "Period": 300,
     +       "Period": 60,
             "Statistic": "Maximum",
             "Threshold": 1,
           },
           "Type": "AWS::CloudWatch::Alarm",
         },

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them.

Jest memberitahu Anda bahwa perubahan yang baru saja Anda buat mengubah atribut Period yang diterbitkan dari 300 ke 60. Anda sekarang memiliki pilihan untuk membatalkan perubahan kode kami jika hasil ini tidak disengaja, atau melakukan untuk snapshot baru jika Anda berniat untuk membuat perubahan ini. Untuk membuat untuk snapshot baru, jalankan:

npm test -- -u

Jest akan memberitahu Anda bahwa itu memperbarui snapshot. Sekarang Anda telah terkunci pada periode alarm baru:

PASS  test/dead-letter-queue.test.js
 ✓ dlq creates an alarm (51ms)

 › 1 snapshot updated.
Snapshot Summary
 › 1 snapshot updated

Berurusan dengan perubahan

Mari kita kembali ke construct DeadLetterQueue. Pesan masuk ke antrian dead letter queue saat ada yang salah dengan pemrosesan antrian utama, dan Anda akan diberi tahu melalui alarm. Setelah memperbaiki masalah dengan pemrosesan antrian, biasanya Anda ingin memindahkan ulang pesan dari antrian dead letter queue, kembali ke antrian utama, agar mereka diproses seperti biasa.

Namun, pesan hanya ada dalam antrian untuk waktu yang terbatas. Untuk mendapatkan kesempatan terbesar untuk memulihkan pesan dari dead letter queue, atur masa pakai pesan dalam dead letter queue (disebut periode retensi) hingga waktu maksimum 2 minggu. Anda membuat perubahan berikut untuk construct DeadletterQueue Anda:

export class DeadLetterQueue extends sqs.Queue {
  constructor(parent: Construct, id: string) {
    super(parent, id, {
      // Maximum retention period
      retentionPeriod: Duration.days(14)
    });
    // ...
  }
}

Sekarang jalankan pengujian lagi:

$ npm run build && npm test
FAIL test/dead-letter-queue.test.js
✕ dlq creates an alarm (79ms)

    ● dlq creates an alarm

    expect(received).toMatchSnapshot()

    Snapshot name: `dlq creates an alarm 1`

    - Snapshot
    + Received

    @@ -1,8 +1,11 @@
      Object {
        "Resources": Object 
          "DLQ581697C4": Object {
    +       "Properties": Object {
    +         "MessageRetentionPeriod": 1209600,
    +       },
            "Type": "AWS::SQS::Queue",
         },
         "DLQAlarm008FBE3A": Object {
           "Properties": Object {
             "AlarmDescription": "There are messages in the Dead Letter Queue",

  › 1 snapshot failed.
Snapshot Summary
  › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them.

Pengujian snapshot gagal lagi, karena Anda menambahkan properti periode retensi. Meskipun pengujian ini hanya dimaksudkan untuk memastikan bahwa construct DeadletterQueue  membuat alarm, itu secara tidak sengaja juga menguji bahwa antrian dibuat dengan pilihan bawaan.

Menulis assertion berdetail pada resource

Pengujian snapshot mudah ditulis dan memiliki kemampuan untuk mendeteksi perubahan yang tidak disengaja. Kami menggunakannya dalam CDK untuk pengujian integrasi kami ketika memvalidasi bagian yang lebih besar dari fungsi keseluruhan. Jika perubahan menyebabkan templat pengujian integrasi menyimpang dari snapshot, kita menggunakannya sebagai pemicu untuk memberitahu bahwa kita perlu melakukan validasi ekstra, misalnya benar-benar meluncurkan templat melalui AWS CloudFormation dan memverifikasi infrastruktur kita masih bekerja.

Dalam rangkaian unit test ekstensif CDK, kita tidak ingin meninjau kembali semua pengujian setiap kali kita membuat perubahan. Untuk menghindari hal ini, kita menggunakan tuntutan kustom di modul @aws -cdk/assert/jest untuk menulis pengujian tuntutan berdetail yang memverifikasi hanya bagian dari perilaku construct pada suatu waktu, yaitu hanya bagian yang berlaku untuk pengujian tertentu. Misalnya, pengujian yang disebut “dlq creates alarm” harus menegaskan bahwa alarm akan dibuat dengan metrik yang sesuai, dan seharusnya tidak membuat tuntutan pada properti antrian yang akan dibuat sebagai bagian dari pengujian itu.

Untuk menulis pengujian ini, Anda akan melihat spesifikasi sumber daya AWS። CloudWatch። Alarm di CloudFormation, dan melihat apa sifat dan nilai-nilai yang Anda gunakan yang dijamin oleh assertion library. Dalam hal ini, Anda tertarik dengan properti Namespace, MetricName dan Dimensions. Anda dapat menggunakan assertion expect(stack).toHaveResource (...) untuk memastikan mereka memiliki nilai-nilai yang Anda inginkan. Untuk mendapatkan akses ke assertion itu, Anda harus terlebih dahulu mengimpor @aws -cdk/assert/jest, yang memperluas assertion yang tersedia ketika Anda mengetik expect (...). Setelah menempatkan ini semua bersama-sama, pengujian Anda akan terlihat seperti ini:

import '@aws-cdk/assert/jest';

// ...
test('dlq creates an alarm', () => {
  const stack = new Stack();

  new dlq.DeadLetterQueue(stack, 'DLQ');

  expect(stack).toHaveResource('AWS::CloudWatch::Alarm', {
    MetricName: "ApproximateNumberOfMessagesVisible",
    Namespace: "AWS/SQS",
    Dimensions: [
      {
        Name: "QueueName",
        Value: { "Fn::GetAtt": [ "DLQ581697C4", "QueueName" ] }
      }
    ],
  });
});

Pengujian ini menegaskan bahwa Alarm dibuat pada metrik approximateNumberOfMessagesVisible dari dead letter queue (dengan cara intrinsik {Fn። getATT}). Jika Anda menjalankan Jest sekarang, itu akan memperingatkan Anda tentang sebuah snapshot yang ada yang tidak digunakan lagi oleh pengujian Anda, jadi singkirkanlah itu dengan menjalankan npm test -- -u.

Sekarang Anda dapat menambahkan pengujian kedua untuk periode penyimpanan:

test('dlq has maximum retention period', () => {
  const stack = new Stack();

  new dlq.DeadLetterQueue(stack, 'DLQ');

  expect(stack).toHaveResource('AWS::SQS::Queue', {
    MessageRetentionPeriod: 1209600
  });
});

Jalankan pengujian untuk memastikan semuanya lulus:

$ npm run build && npm test
 
PASS  test/dead-letter-queue.test.js
  ✓ dlq creates an alarm (48ms)
  ✓ dlq has maximum retention period (15ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total

Sukses!

Memvalidasi konfigurasi construct

Mungkin Anda ingin membuat periode penyimpanan agar dapat dikonfigurasi, sementara memvalidasi bahwa nilai yang diberikan pengguna jatuh ke kisaran yang dapat diterima. Anda akan membuat interface Props untuk construct dan menambahkan pengecekan pada nilai-nilai diperbolehkan diterima oleh construct Anda:

export interface DeadLetterQueueProps {
    /**
     * The amount of days messages will live in the dead letter queue
     *
     * Cannot exceed 14 days.
     *
     * @default 14
     */
    retentionDays?: number;
}

export class DeadLetterQueue extends sqs.Queue {
  public readonly messagesInQueueAlarm: cloudwatch.IAlarm;

  constructor(scope: Construct, id: string, props: DeadLetterQueueProps = {}) {
    if (props.retentionDays !== undefined && props.retentionDays > 14) {
      throw new Error('retentionDays may not exceed 14 days');
    }

    super(scope, id, {
        // Given retention period or maximum
        retentionPeriod: Duration.days(props.retentionDays || 14)
    });
    // ...
  }
}

Untuk menguji bahwa fitur baru Anda benar-benar melakukan apa yang Anda harapkan, Anda akan menulis dua pengujian:

  • Satu memeriksa nilai dikonfigurasi berakhir di templat; dan
  • Satu lagi memberikan nilai yang salah untuk construct dan memeriksa bahwa Anda mendapatkan kesalahan yang Anda harapkan.
test('retention period can be configured', () => {
  const stack = new Stack();

  new dlq.DeadLetterQueue(stack, 'DLQ', {
    retentionDays: 7
  });

  expect(stack).toHaveResource('AWS::SQS::Queue', {
    MessageRetentionPeriod: 604800
  });
});

test('configurable retention period cannot exceed 14 days', () => {
  const stack = new Stack();

  expect(() => {
    new dlq.DeadLetterQueue(stack, 'DLQ', {
      retentionDays: 15
    });
  }).toThrowError(/retentionDays may not exceed 14 days/);
});

Jalankan pengujian untuk memastikan semuanya lulus:

$ npm run build && npm test

PASS  test/dead-letter-queue.test.js
  ✓ dlq creates an alarm (62ms)
  ✓ dlq has maximum retention period (14ms)
  ✓ retention period can be configured (18ms)
  ✓ configurable retention period cannot exceed 14 days (1ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total

Anda telah mengkonfirmasi bahwa fitur Anda berfungsi, dan bahwa Anda benar memvalidasi input pengguna.

Sebagai bonus: Anda tahu dari pengujian sebelumnya yang masih lulus bahwa Anda tidak mengubah perilaku apa pun ketika pengguna tidak menentukan argumen apa pun, yang merupakan berita bagus!

Kesimpulan

Anda telah menulis sebuah construct yang dapat digunakan kembali, dan membuat pengujian untuk fitur-fiturnya dengan pengujian resource assertion dan validasi. Terlepas dari apakah Anda berencana untuk menulis pengujian pada infrastruktur aplikasi atau construct Anda sendiri, atau jika Anda berencana untuk berkontribusi pada CDK di GitHub, saya harap tulisan ini telah memberi Anda beberapa alat mental untuk berpikir tentang pengujian kode infrastruktur Anda.

Akhirnya, dua poin saya ingin menanamkan dalam diri Anda ketika Anda menulis pengujian:

  • Perlakukan kode pengujian seperti Anda akan memperlakukan kode aplikasi. Kode pengujian akan memiliki umur hidup sama panjang dalam kode Anda sebagai kode biasa, dan sama-sama dapat berubah. Jangan salin/tempel baris pengaturan atau assertion umum di seluruh tempat, luangkan waktu ekstra untuk faktor keluar kesamaan ke dalam fungsi pembantu. Diri Anda di masa depan akan berterima kasih padamu.
  • Jangan terlalu banyak menggunakan assertion dalam satu pengujian. Sebaiknya, pengujian harus menguji satu dan hanya satu perilaku. Jika Anda secara tidak sengaja melanggar perilaku itu, Anda akan lebih memilih tepat satu pengujian gagal, dan nama pengujian akan memberitahu Anda persis apa yang Anda rusak. Tidak ada yang lebih buruk daripada mengubah sesuatu yang sepele dan memiliki puluhan pengujian gagal dan perlu diperbarui karena mereka secara tidak sengaja menegaskan beberapa perilaku selain apa pengujian itu untuk. Ini berarti bahwa, walaupun mudah dipakai, Anda harus menggunakan pengujian snapshot secara hemat, karena semua pengujian snapshot akan gagal jika ada perubahan apapun perilaku construct, dan Anda akan harus kembali dan meneliti semua kegagalan untuk memastikan kebenarannya.

Selamat menguji!


Tulisan ini berasal dari artikel Testing infrastructure with the AWS Cloud Development Kit (CDK) yang ditulis oleh Rico Huijbers dan diterjemahkan oleh Andrew Wangsanata.

Petra Barus

Petra Barus

Petra Novandi Barus is Developer Advocate at Amazon Web Services based in Jakarta. He is passionate in helping startups and developers in Indonesia to reinvent on behalf their customers. Prior to AWS, Petra co-founded UrbanIndo.com as CTO. The startup became the largest real-estate portal in Indonesia and then was acquired by 99.co. During that time Petra had been a happy AWS customer for 8 years. Petra is also very active in local tech communities