はじめに
Serverless Advent Calendar 2017 の6日目です。
サーバーレスの技術を使うとWeb APIの開発が非常にお手軽になりました。
ところで質問。
ちゃんとテスト書いてますか??
この記事では、サーバーレスで作ったWeb API (別にサーバーレスで作ってなくてもいいけど) のAPIテストをサーバーレスでやっちゃおう、という小ネタです。 実運用ができるかどうかはやってみないとわかりません。
Web APIのテストって?
デプロイ後のWeb API、皆さんはどのようにテストされていますか?
- curlなどを使ってShellScrpitでテスト組む?
- いわゆるxUnit系のテストフレームワークを使ってテストする?
- 何かしらのSaaSを使ってテスト?
- Rest Client (Postmanなど) を使って手動でテスト^^;
などでしょうか?
ここで提案です。
サーバーレスでやればいいんじゃね??
AWS Lambdaをテストランナーとして使う
概要
Nodeでやります。
JavaScriptのテストフレームワークは、MochaやJasmine、Jestなどが有名です。最近だとAVAが流行っていますかね。 これらは基本的にはLocal環境でテストしたり、CI/CD環境でテストをまわすのに利用されます。
が、今回は上記のようなテストフレームワークは一切使わず、Lambdaでテストを書いてみます。
テスト対象のAPI
すごーくシンプルな、下記のようなCRUDなAPIを想定します。
API | description |
---|---|
POST /users | ユーザリソース作成 |
GET /users/{userId} | ユーザリソース取得 |
PUT /users/{userId} | ユーザリソース更新 |
DELETE /users/{userId} | ユーザリソース削除 |
テストを書く!
テストランナーはLambdaにするとしても、Web APIをたたいたり、Assertionなどは外部パッケージに頼ります。
とりあえず定番の supertest
と should
を使います。
コードの内容としては以下のようなものになっています。
- POST /users で34歳のTaroというユーザを作成し、
- GET /users/{userId} でユーザ情報を参照し、
- PUT /usert/{userId} でTaro -> Hanakoに改名し、
- DELETE /users/{userId} でユーザを削除する
const Supertest = require('supertest');
const should = require('should');
const agent = Supertest.agent('https://your-api-host/your-api-resource-path');
const testShouldPostUserSucceeds = () => {
const user = {
name: 'Taro',
age: 34
};
return new Promise((resolve) => {
agent.post('/users')
.set('Accept', 'application/json')
.send(user)
.expect((res) => {
res.status.should.equal(200);
res.body.should.hasOwnProperty('userId');
res.body.name.should.equal('Taro');
res.body.age.should.equal(34);
}).end((err, res) => {
if (err) {
console.log('[Failed] testShouldPostUserSucceeds');
throw err;
}
console.log('[Passed] testShouldPostUserSucceeds');
resolve(res.body.userId);
});
});
}
const testShouldGetUserSucceeds = (userId) => {
return new Promise((resolve) => {
agent.get(`/users/${userId}`)
.set('Accept', 'application/json')
.expect((res) => {
res.status.should.equal(200);
res.body.userId.should.equal(userId);
res.body.name.should.equal('Taro');
res.body.age.should.equal(34);
}).end((err, res) => {
if (err) {
console.log('[Failed] testShouldGetUserSucceeds');
throw err;
}
console.log('[Passed] testShouldGetUserSucceeds');
resolve(res.body.userId);
});
});
}
const testShouldPutUserSucceeds = (userId) => {
const user = {
name: 'Hanako',
age: 34
}
return new Promise((resolve) => {
agent.put(`/users/${userId}`)
.set('Accept', 'application/json')
.send(user)
.expect((res) => {
res.status.should.equal(200);
res.body.userId.should.equal(userId);
res.body.name.should.equal('Hanako');
res.body.age.should.equal(34);
})
.end((err, res) => {
if (err) {
console.log('[Failed] testShouldPutUserSucceeds');
throw err;
}
console.log('[Passed] testShouldPutUserSucceeds');
resolve(res.body.userId);
});
});
}
const testShouldDeleteUserSucceeds = (userId) => {
return new Promise((resolve) => {
agent.delete(`/users/${userId}`)
.set('Accept', 'application/json')
.expect((res) => {
res.status.should.equal(200);
res.body.userId.should.equal(userId);
}).end((err, res) => {
if (err) {
console.log('[Failed] testShouldDeleteUserSucceeds');
throw err;
}
console.log('[Passed] testShouldDeleteUserSucceeds');
resolve();
});
});
}
exports.handler = (event, context) => testShouldPostUserSucceeds()
.then(userId => testShouldGetUserSucceeds(userId))
.then(userId => testShouldPutUserSucceeds(userId))
.then(userId => testShouldDeleteUserSucceeds(userId))
.then(() => {
context.succeed('Test Passed');
}).catch(() => {
context.fail('Test Failed');
});
- Node 6.x でTranspile無しで動くように書いています。
- ESLintにはめちゃくちゃ怒られる書き方になっているのでご注意を。
- Supertestなどの説明は省いているので他の記事をご参照ください。
これをLambdaで実行するとどうなるか?
テスト成功時
上記のコードをLambdaで実行し、テストに全部成功すると、例えばAWSのConsoleで確認すると以下の図のようになります。
テスト失敗時
一方で、 testShouldPutUserSucceeds
をちょっといじって、Taro -> Hanakoの改名に失敗するような、Failするテストに変更してみると、
res.status.should.equal(200);
res.body.userId.should.equal(userId);
// res.body.name.should.equal('Hanako');
res.body.name.should.equal('Taro');
res.body.age.should.equal(34);
下記の図のように、失敗したテストがLambdaの実行ログでわかるのです!!
所感
これはいったい誰トクなのか?
たぶん下記のようなメリットがあるはず。
- CloudWatch Eventsなどから定期実行できる
- 定期的に動かせば、APIの死活監視にも使える
- Lambdaの実行結果をとりあえずSNSにでも飛ばしておけば、メールだったりWebhookでどっかにポストしたり、通知の柔軟性が高い
ただ、冒頭に書いた通り、本当にこんなものが必要なのかはよくわからない。
もし気が向いたら (需要がありそうなら) フレームワーク化してOSS化を検討したいところ。