Asial Blog

Recruit! Asialで一緒に働きませんか?

REST APIのテストをFrisbyで自動化する

カテゴリ :
バックエンド(プログラミング)
タグ :
テスト
REST
PHP
node.js
幸せになりたい

 どうも、中本(特に冷やし五目味噌タンメン+バター)にハマっている高橋です!

 最近のアプリケーション開発といえば、フロントエンドはサーバサイドが準備したAPI経由でデータを取得したり保存したりという構成が人気のようです。そこで「API、ちゃんと動いてるんかなぁ?」というテストを書いて、実際にリクエスト&レスポンスで検証してみようと思います。

 今回テスティングフレームワークとして使用する Frisby(フリスビー) は簡単に書けて高速に動作するというのが持ち味の REST API のテスティングフレームワークです。投げて返ってくるFrisbeeと掛けているのでしょうか?これドヤ顔で言われるとちょっと腹立ちますが、こういうネーミングセンスには関心させられます。笑


◯インストール



 今回は「frisbytest」というディレクトリ内で作業をしていきたいと思います。
 コンソールを起動したら以下のコマンドを実行してFrisbyを扱う準備を進めます。

  1. mkdir frisbytest
  2. cd frisbytest
  3. npm install frisby
  4. npm install -g jasmine-node

 jasmine-nodeもインストールしておきました。
 これで「*spec.js」にマッチするファイルをテストしてくれます。
 詳しくは https://github.com/mhevery/jasmine-node#usage

◯テストをする



 次はテストするために「first_spec.js」というファイルにテストを書いていきたいと思います。またテストコードは「spec」というディレクトリ以下に作っていくのが慣習になので、それに倣って進めます。テスト対象は偶然見つけたアニメマップ(http://animemap.net/pages/api/)という公開APIを使ってみます。

  1. mkdir spec
  2. cd spec
  3. vim first_spec.js

first_spec.js


  1. var frisby = require('frisby');
  2. frisby.create('GET 東京都の番組データ')
  3.     .get('http://animemap.net/api/table/tokyo.json')
  4.     .expectJSON({
  5.         response: {
  6.             item: [
  7.                 {
  8.                     title: 'pupa(ピューパ)'
  9.                 }
  10.             ]
  11.         }
  12.     })
  13.     .toss();
(コピペしても行番号はコピーされません^^/)

さっそく実行してみましょう!

  1. jasmine-node .

  1. yuya[/Users/yuya/work/frisbytest/spec]% jasmine-node .
  2. F
  3. Failures:
  4.   1) Frisby Test: GET 東京都の番組データ
  5. [ GET http://animemap.net/api/table/tokyo.json ]
  6.    Message:
  7.      Error: Expected string 'とある飛空士への恋歌' to match string 'pupa(ピューパ)' on key 'title'
  8.    Stacktrace:
  9.      Error: Expected string 'とある飛空士への恋歌' to match string 'pupa(ピューパ)' on key 'title'
  10.     at _jsonContains (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1209:17)
  11.     at _jsonContains (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1188:9)
  12.     at _jsonContains (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1188:9)
  13.     at _jsonContains (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1188:9)
  14.     at jasmine.Matchers.toContainJson (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1124:12)
  15.     at null. (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:676:24)
  16.     at null. (/Users/yuya/work/frisbytest/node_modules/frisby/lib/frisby.js:1033:43)
  17. Finished in 0.276 seconds
  18. 1 test, 1 assertion, 1 failure, 0 skipped

 あれ、テストがこけてしまいましたね.....と見せかけてこれで正解です!
 基本的にテストは赤(失敗)で始めるのが鉄則ですのでこれでOK!大丈夫!安心して!間違いない!一切問題なし!心配ご無用なのです!!

 「Error: Expected string 'とある飛空士への恋歌' to match string 'pupa(ピューパ)' on key 'title'」というメッセージがありますので、改めて期待値を修正します。

  1. var frisby = require('frisby');
  2.  
  3. frisby.create('GET 東京都の番組データ')
  4.     .get('http://animemap.net/api/table/tokyo.json')
  5.     .expectJSON({
  6.         response: {
  7.             item: [
  8.                 {
  9.                     title: 'とある飛空士への恋歌'
  10.                 }
  11.             ]
  12.         }
  13.     })
  14.     .toss();

さて、これでもう一度 「jasmine-node .」 を実行してみましょう!

  1. yuya[/Users/yuya/work/frisbytest/spec]% jasmine-node .
  2. .
  3. Finished in 0.886 seconds
  4. 1 test, 2 assertions, 0 failures, 0 skipped

 よし!緑(成功)になりましたね!おめでとうございます!!

 というようにたったのこれだけでWebAPIのテストが出来ました。
この要領で全APIのテストケースを作成すれば、サーバ側でなんかしらの修正をしても、いつでも全てのAPIの動作チェックが出来ますので、これはもうなんだか幸せになれそうな気がしてきますね!

◯学習



 基本的な使い方を覚えたら、次はFrisbyを効率的に学習する方法についてです。

 Frisbyをインストールした時にサンプルコード(node_modules/frisby/examples/)が付いてきますので、そのコードを眺めるのが手っ取り早いかと思います。

 また、Frisbyのソースコード(node_modules/frisby/lib/frisby.js)は1300行程度ですので、これを読んでしまうのもオススメです。というのも、まだドキュメント化されていないメソッドがあったりで情報が少ないためです。でもなんにせよソース読んだほうがスッキリしますよね。

◯Frisbyでテストを書くコツ



 コツというか経験をユースケースにして重要な機能を紹介します。これを覚えたらFrisbyでテストを書くのは困らないかなぁと思います。

・リクエストヘッダの一括設定と個別追加&除去


  1. frisby.globalSetup({
  2.     request: {
  3.         headers: {'X-CUSTOM-HEADER-VALUE': 'カスタムヘッダです'}
  4.     }
  5. });
  6.  
  7. frisby.create('カスタムヘッダありで実行A')
  8.     .... // いろんな処理
  9.     .toss();
  10.  
  11. frisby.create('カスタムヘッダありで実行B')
  12.     .... // いろんな処理
  13.     .toss();
  14.  
  15. frisby.create('カスタムヘッダあり + 追加で実行')
  16.     .addHeader('X-CUSTOM-HEADER-MORE-VALUE', 'さらに追加')
  17.     .... // いろんな処理
  18.     .toss();
  19.  
  20. frisby.create('カスタムヘッダなしで実行')
  21.     .removeHeader('X-CUSTOM-HEADER-VALUE')
  22.     .... // いろんな処理
  23.     .toss();

・GET以外のメソッド(POST、PUT、DELETE)を使う


  1. frisby.create('POSTメソッドで実行')
  2.     .post('http://localhost/something', {hoge: 'huga'})
  3.     .... // いろんな処理
  4.     .toss();
  5.  
  6. frisby.create('PUTメソッドで実行')
  7.     .put('http://localhost/something/10', {hoge: 'hugahuga'})
  8.     .... // いろんな処理
  9.     .toss();
  10.  
  11. frisby.create('DELETEメソッドで実行')
  12.     .delete('http://localhost/something/10')
  13.     .... // いろんな処理
  14.     .toss();
  15.  
  16. frisby.create('HEADメソッドも対応してる')
  17.     .head('http://localhost/something/10')
  18.     .... // いろんな処理
  19.     .toss();
  20.  
  21. frisby.create('PATCHメソッドも対応してる')
  22.     .patch('http://localhost/something/10', {hoge: 'hugahuga'})
  23.     .... // いろんな処理
  24.     .toss();

・404ページかどうか(ステータスコード)をチェックする


  1. frisby.create('404 ページかどうか')
  2.     .get('https://www.google.co.jp/hogehoge')
  3.     .expectStatus(404)
  4.     .toss();

・ヘッダーを検証する(完全一致)


  1. frisby.create('ヘッダーを検証する')
  2.     .get('https://www.google.co.jp')
  3.     .expectHeader('content-type', 'text/html; charset=Shift_JIS')
  4.     .toss();

・ヘッダーを検証する(部分一致)


  1. frisby.create('ヘッダーを検証する')
  2.     .get('https://www.google.co.jp')
  3.     .expectHeaderContains('content-type', 'text/html')
  4.     .toss();

・依存性のあるテストの対応(レスポンスがJSONのみ対応)


  1. frisby.create('テストA')
  2.     .get('http://animemap.net/api/table/tokyo.json')
  3.     .afterJSON(function (data)
  4.     {
  5.         frisby.create('テストAの結果に依存するテストB')
  6.             .get('http://localhost/something', {data: data})
  7.             .... // いろんな処理
  8.             .toss();
  9.     })
  10.     .toss();

・レスポンスヘッダやレスポンスボディを確認する


  1. frisby.create('他にも色々と確認できるメソッド')
  2.     .get('http://animemap.net/api/table/tokyo.json')
  3.     .inspectHeaders()
  4.     .inspectBody()
  5.     .inspectJSON() // inspectBody()をJSONパースしたもの
  6.     .inspectStatus()
  7.     .inspectRequest()
  8.     .inspectResponse()
  9.     .toss();

・タイムアウトの制限時間を設定する


  1. frisby.create('タイムアウトを10秒に設定(デフォルトは5000ミリ秒)')
  2.     .timeout(10000)
  3.     .get('http://animemap.net/api/table/tokyo.json')
  4.     .toss();

・Basic認証がある場合


  1. frisby.create('Basic認証を突破!')
  2.     .auth('authname', 'authpassword')
  3.     .get('http://animemap.net/api/table/tokyo.json')
  4.     .toss();


◯まとめ



 今回のサンプルソースではGETメソッドしか試していませんが、実際のアプリケーションではPUTメソッドやDELETEメッドも当然実装されますから、これらをテストするために毎回cURL等で確認するのは手間だと思います。状況にもよるでしょうが、何回か実行する可能性があるのなら自動化しておいて損はないでしょう。

 最後に、これらは成果物にはならないかもしれませんが、心の健康と変更に対する勇気を得るためにもテストコードを書いてみてはいかがでしょうか?

それでは!