JavaScript で Compute アプリのエンドツーエンドテストを自動化
テストの自動化は先進的なアプリケーション開発において極めて重要な役割を担っており、Fastly の Compute プラットフォームでエッジアプリケーションを構築する場合も例外ではありません。現在、カスタムシェルスクリプトを使用して Compute アプリケーションのテストを実行することが可能ですが、これをより自然に行える、もっと便利な方法を提供したいと私たちは考えました。そこで JavaScript でテストを記述することを可能にする Compute Application Testing ライブラリをご用意しました。
このライブラリにより、以下のように簡単に新しいテストを作成できます。
describe('/user.json', () => {
it('returns 200 status and valid username', async () => {
const resp = await app.fetch('/user.json');
assert.strictEqual(resp.status, 200);
const data = await resp.json();
assert.ok(/^\w+$/.test(data.username));
});
});
このブログ記事では、シェルスクリプトと新しい Compute Application Testing ライブラリによるテストを比較します。Compute アプリケーションの構築に使用したプログラミング言語にかかわらず、コードをテストするベストな方法を選ぶ際にご参考にしてください。
ソフトウェア開発プロセスで使用される手法のひとつに自動化されたテストがあります。コードが意図されたように動作することを実証し、コードに対する変更によって既存の機能が損なわれないことを確認するのがテストの目的です。これを継続的デリバリーなどの戦略と組み合わせることで、信頼性の高いソフトウェアの構築とデプロイが可能になり、開発チームは常に出荷可能で品質の高いプロダクトを用意できます。
テストは数種類あります。「ユニット」テストでは、ソフトウェアを構成する最小サイズのパーツが個別にテストされ、機能やクラス、API レベルで行われることがよくあります。「統合」テストでは、ソフトウェアを構成するさまざまなユニット間のインタラクションを検証します。最後に「エンドツーエンド」テストはアプリケーション全体を対象とするテストを指し、特定の入力によって想定通りの出力が生み出されるかどうかを検証します。
これらすべてのレベルのテストを実施することが大切です。このブログ記事では、Fastly Compute アプリケーションが想定通りの出力を生み出し続けることを保証する効果的な方法としてエンドツーエンドテストを取り上げます。
Compute アプリケーションをエンドツーエンドでテストする方法
Fastly Compute は、エッジサーバーで構成される Fastly のグローバルネットワーク全体を通じて WebAssembly モジュールを実行するプラットフォームで、Rust や Go、JavaScript を含む任意の言語からコンパイルできます。開発とテスト目的で、Fastly は Compute 向けのローカルテスト環境を提供しています。
Fastly Compute で動作しているアプリケーションのエンドツーエンドテストの内容を見てみましょう。Compute アプリケーションは HTTP API としてサービスを提供します。そこで、このようなテストを遂行する方法のひとつとして、テスト環境で Compute アプリケーションを起動させ、以下の cURL コマンドのシェルスクリプトを実行する GitHub アクションのワークフローを使用することが可能です。
.github/workflows/tests.yaml (excerpt)
jobs:
curl-test:
name: Build Compute Edge service, and test with curl
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Fastly CLI
uses: fastly/compute-actions/setup@main
- name: Install Edge code dependencies
run: npm install
- name: Build and start Compute application
run: fastly compute serve > ./out.txt & (tail -f ./out.txt &) | grep -qF 'Listening on http://127.0.0.1:7676'
- name: Run tests
run: ./curl-tests/test.sh
./curl-tests/test.sh
#! /bin/bash
echo "test: returns 200 and the text \"Hello, World!\" for /"
OUTPUT=$(curl -vs "http://127.0.0.1:7676/" 2>&1)
grep -qF "200 OK" <<< "$OUTPUT" || { echo 'status not 200' ; exit 1; }
grep -qF "Hello, World!" <<< "$OUTPUT" || { echo 'unexpected response content' ; exit 1; }
echo "test: returns 200 and json including \"newField\": \"newValue\" for /json"
OUTPUT=$(curl -vs "http://127.0.0.1:7676/json" 2>&1)
grep -qF "200 OK" <<< "$OUTPUT" || { echo 'status not 200' ; exit 1; }
grep -qF "\"newField\":\"newValue\"" <<< "$OUTPUT" || { echo 'unexpected response content' ; exit 1; }
シェルスクリプトで若干テキストをいじる必要があるものの、これらのテストによって、プログラムに変更が加えられても想定通りの出力が得られることを確認できます。
JavaScript で記述されたテスト
テストの記述と実施を目的に設計されたフレームワークを利用するなど、多くのプログラミング言語、プラットフォーム、コミュニティによって、より優れたオプションが提供されています。
私たちは Fastly Compute でも、以下のように Fastly Compute のサービス向けにシンプルなテストを記述できる機能が欲しいと考えました。
it('returns 200 and the text "Hello, World!" for /', async () => {
const resp = await app.fetch('/');
assert.strictEqual(resp.status, 200);
assert.ok((await resp.text()).includes('Hello, World!'));
});
it('returns 200 and json including "newField": "newValue" for /json', async () => {
const resp = await app.fetch('/json');
assert.strictEqual(resp.status, 200);
const responseJson = await resp.json();
assert.strictEqual(responseJson['data']?.['newField'], 'newValue');
});
そこで、この用途向けに「Compute Application Testing for JavaScript」というライブラリを構築しました。
注 : エンドツーエンドテストはプログラムの入力と出力によってのみ機能します。従って、テストとその対象となる Compute アプリケーションが異なる言語で記述されていても問題ありません。このライブラリによって、Compute アプリケーションに対するエンドツーエンドテストを JavaScript で記述できます。
JavaScript は非常に人気が高く、広く普及している言語です。JavaScript は Web リクエストの実行、JSON のネイティブ解析、正規表現などのユーティリティ、オブジェクトや配列などのデータ構造へのアクセス、npm が提供する広範なパッケージライブラリとの連携などのサポートにおいて非常に優れています。このような機能性の高さを考えると、JavaScript を使用してエンドツーエンドテストを記述するのはごく自然に感じられました。入力と出力を確認するだけなので、Compute アプリケーションで使用されている言語は関係ありません。
JavaScript ライブラリの Compute Application Testing for JavaScript には、npm で @fastly/compute-testing を検索することでアクセスできます。テストアプリケーションは Node.js で実行されるので、テストアプリケーションを Compute アプリケーションから切り離すことをお勧めします。以下のように、これを開発依存関係として追加できます。
npm install --save-dev @fastly/compute-testing
次に、個々のテストの内容を見てみましょう。
標準的な Node.js のテストランナーや、Jest などの BDD スタイルのランナーに慣れている方には、馴染みのある内容かと思います。テストケースには「returns 200 and the text "Hello, World!" for /」という名前が使用されています。undefined「app」という変数を使用して Compute アプリケーションの「/」パスを呼び出します。undefinedそしてステータスコードとレスポンスのテキストコンテンツに関するアサーションを実行します。
「app」変数とは何かと疑問に思われるかもしれません。これは ComputeApplication のインスタンスで、ローカルテスト環境で Compute アプリケーションのインスタンス作成を可能にするクラスです。この変数は Compute アプリケーションのインスタンスを表し、以下のように使用されます。
import { ComputeApplication } from '@fastly/compute-testing';
describe('Edge app', () => {
const app = new ComputeApplication();
before(async () => {
await app.start({
// Set 'appRoot' to the directory in which to start the app. This is usually
// the directory that contains the 'fastly.toml' file.
appRoot: path.join(__dirname, '..'),
});
});
after(async() => {
await app.shutdown();
});
// ...tests here
});
このクラスのインスタンスが作成されると、Fastly CLI が呼び出され、appRoot に Compute アプリケーションのインスタンスが生成されます。このインスタンスが稼働すると、テストは「.fetch()」を呼び出すだけで、インスタンスに対してリクエストを実行することが可能になります。これはグローバル関数の fetch と同じパラメータを取得するため、URL オブジェクトや文字列、Request オブジェクトを最初のパラメータとしてパスし、必要に応じてヘッダーを設定できます。そして、返された Response オブジェクトのステータス、ヘッダー、コンテンツを検証します。また、Response でビルトインの JSON 逆シリアル化など、その他の便利な機能も使用できます。
it('returns 200 and json including "newField": "newValue" for /json', async () => {
const resp = await app.fetch('/json');
assert.strictEqual(resp.status, 200);
const responseJson = await resp.json();
assert.strictEqual(responseJson['data']?.['newField'], 'newValue');
});
表現力の高いテストを記述するのに利用できる JavaScript 機能が他にもたくさんあります。例えば、Request を使用して JSON、フォームデータ (FormData を使用)、ストリーミング blob を送信できます。そして Response オブジェクトが返され、ネイティブストリーミングや JSON 解析にアクセスできるので、HTTP レスポンスの確認に役立ちます。
もちろん Node.js を使用すれば、npm でアクセス可能なあらゆるパッケージの利用が可能になります。HTML DOM を解析する必要がある場合、JSDOM を使用できます。JSON Web Token (JWT) をサポートする必要がおありですか? jsonwebtoken で解決できます。この記事では Node.js のテストランナーとアサーションを使用していますが、Jest や Mocha、Chai など、Node.js で動作する別のテストフレームワークやアサーションライブラリを使用したい場合もあるかもしれません。これらもすべて利用可能です。テストで表現する必要がある意図が何であれ、Node.js 向けに JavaScript でテストを記述することで、それを実現できます。
現在、以下のようにこれらのテストを GitHub ワークフローで実施できるようになりました。
.github/workflows/tests.yaml (excerpt)
jobs:
compute-testing-test:
name: Test Compute Edge service using compute-testing
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Fastly CLI
uses: fastly/compute-actions/setup@main
- name: Install Edge code dependencies
run: npm install
- name: Install Test code dependencies
working-directory: ./test
run: npm install
- name: Run tests
working-directory: ./test
run: npm test
このワークフローをトリガーすると、以下のように整然とした形式でテスト名と統計データが表示されます。
この例で使用したコードとテストの詳細にご興味がおありの方は、私の GitHub ページ (https://github.com/harmony7/compute-testing-demo) をご覧ください。
ご自身のコードをお試しください
JavaScript によるテストの記述は、スクリプトの使用に代わる優れたテストの作成方法です。Compute Application Testing for JavaScript に npm からアクセスしてすぐにお試しいただけます。Compute アプリケーションを検証するテストをいかに簡単に構築して実行できるか、また構築したテストを CI テストにどれほど容易に追加できるか実際に体験してみてください。
注 : @fastly/compute-testing は Fastly Labs のプロダクトとして提供されています。ご利用の条件については Fastly Labs のWebサイトをご確認ください。
詳細やサンプルについては、GitHub のプロジェクトページもぜひご覧ください。
Fastly では、お客様が使い慣れたツールを使用してより多くのコードを開発し、エッジで実行することを可能にするツールを提供できるよう日々、取り組んでいます。お客様がどのように Fastly のツールを活用しているか、私たちはとても興味があります。Fastly のコミュニティフォーラムに参加して皆さまの取り組みについてお聞かせください。