Download - JSON Schema と API テスト YAPC::Asia Tokyo 2014
JSON Schema と
API テスト
2014/08/29 (Sat) YAPC::Asia Tokyo 2014
清水 直樹
自己紹介• 清水 直樹 (@deme0607)
• SWET @ DeNA
• SWET: Software Engineer in Test
• 2013年 4月 新卒入社
自己紹介②• テスト用のライブラリ
• randexp-multibyte
• https://rubygems.org/gems/randexp-multibyte
• Rubyist Magazine 「Ruby 初心者の新卒エンジニアが
gem パッケージ公開に至るまで」
• http://magazine.rubyist.net/?0046-RandexMultibyteGem
今日の話• API 結合テストとは?
• JSON Schema とは?
• JSON Schema を API 結合テストに活用
API結合テスト
API (単体) テスト
API Server
Test
[request] GET api/user id: 1
1. テストデータをリクエストとして送信
[response] id: 1 name: deme0607 email: [email protected]
2. レスポンスデータを期待結果と比較
それだけで十分?• API は様々なコンポーネントと結合して動作
API Server
User
API Server
Load Balancer / Reverse Proxy
DB
[request] [response]
API結合テスト• 実環境で動作しているAPIを実際のクライアントと同じ経路からテスト
API Server
結合テスト
API Server
Load Balancer / Reverse Proxy
DB
[request] [response]
API結合テスト 実施フロー• 仕様ドキュメントから、正常系・異常系テストリクエストデータを作成
• リクエストデータ ≒ テストケース
• 仕様とリクエストデータから、期待するレスポンスを定義
• テスト用クライアントからリクエストを送り、レスポンスを検証
今日の話• API 結合テストとは?
• JSON Schema とは?
• JSON Schema を API 結合テストに活用
JSON Schema とは?• JSONで表現されるデータに対してデータ定義するSchemaを記述する枠組み
• json-schema.org で公開されている
• 現在、draft4
例• JSON データ
!
• 日本語の仕様
!
!
• JSON Schema{ "id": 12345678, "name": "Naoki Shimizu", "email": "[email protected]" }
!{ "type": "object", "properties": { "id": { "type": "integer", "minimum": 10000000 }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } }
フィールド 型 詳細
id integer ユーザのidを表す。10000000以上の値。
name string ユーザの名前を表す文字列。
email stringユーザのメールアドレス。 RFC5322形式の文字列。
JSON Schema, 何が嬉しい?• データの検証にも使える
• Machine Readable なデータの定義ができるので、Validatorの入力にできる
• 仕様と実装の乖離が減る
• 上記のようなValidatorを活用し、APIのリクエスト・レスポンスを検証
• グローバル対応
• JSONは機械にも人間にも読みやすい
• Validator を使ってAPIサーバのリクエスト・レスポンスのSchemaとの整合性を検証
• perl-JSV: Perlのデータに対する JSON Schema Validator
• https://github.com/zigorou/perl-JSV
use JSON; use JSV::Validator; my $request = { id => 12345678, name => "Naoki Shimizu", email => "[email protected]", }; my $schema = decode_json($json_file); my $validator = JSV::Validator-‐>new; my $result = $validator-‐>validate($schema, $request); if ($result) { ...
JSON Schema について詳しくは
WEB+DB Press vol.82 特集1 「Web API デザインの鉄則」をご覧ください
今日の話• API 結合テストとは?
• JSON Schema とは?
• JSON Schema を API 結合テストに活用
(再) API結合テスト 実施フロー• 仕様ドキュメントから、正常系・異常系テストリクエストデータを作成
• リクエストデータ ≒ テストケース
• 仕様とリクエストデータから、期待するレスポンスを定義
• テスト用クライアントからリクエストを送り、レスポンスを検証
API 仕様が JSON Schema で書かれていたら?
• 正常系・異常系のリクエストデータを自動生成できるかも?
• 仕様とリクエストデータから、期待するレスポンスも自動で定義できるかも?
• 仕様から、クライアントも自動生成できるかも?
APIの結合テスト、 全部自動でできちゃう?
そんなうまい話はありません
だが、今よりもっと楽する ことはできるはず
やりたいこと• JSON Schema で記述された API の仕様から
• 正常系・異常系のリクエストデータ生成
• 期待するレスポンスの定義
• APIクライアントの生成
json-fuzz-generator• JSON Schema から、そのSchemaに対して正常系・異常系のデータを生成
• Ruby のライブラリ
• 異常系のデータはFuzzingに基いている
• 誤りの含まれたデータを次々に入力するテスト手法
デモ
正常系データの生成
# require "json-‐fuzz-‐generator" # JSON::Fuzz::Generator.default_param(schema_file) { "id" => 0, "name" => "hoge", "birthday” => "1992-‐06-‐27" }
!{ "title": "Basic Schema", "type": "object", "properties": { "id" : { "type": "integer", "minimum": 0 }, "name": { "type": "string" }, "birthday": { "type": "string", "format": "date" } } }
JSON Schema の入力
正常系データの出力
異常系データの生成[ ["sample", "array"], true, 73, nil, 0.34259093948835795, "hoge", {"id"=>"a", "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>"1", "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>0.1, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>["sample", "array"], "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>false, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>nil, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>0.0, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>{}, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>"hoge", "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>-‐1, "name"=>"hoge", "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>["sample", "array"], “birthday"=>"1992-‐06-‐27"}, !
{"id"=>0, "name"=>true, "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>97, "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>nil, "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>0.7547537108664406, "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>{}, "birthday"=>"1992-‐06-‐27"}, {"id"=>0, "name"=>"hoge", "birthday"=>["sample", "array"]}, {"id"=>0, "name"=>"hoge", "birthday"=>false}, {"id"=>0, "name"=>"hoge", "birthday"=>11}, {"id"=>0, "name"=>"hoge", "birthday"=>nil}, {"id"=>0, "name"=>"hoge", "birthday"=>0.5380909041403419}, {"id"=>0, "name"=>"hoge", "birthday"=>{}}, {"id"=>0, "name"=>"hoge", "birthday"=>"2010-‐01-‐32"}, {"id"=>0, "name"=>"hoge", "birthday"=>"n2010-‐01-‐01"}, {"id"=>0, "name"=>"hoge", "birthday"=>"2010-‐1-‐01"}, {"id"=>0, "name"=>"hoge", "birthday"=>"2010-‐01-‐1"}, {"id"=>0, "name"=>"hoge", "birthday"=>"2010-‐01-‐01n"}, ]
JSON Schema から自動生成 したリクエストデータは、 テストケースとして十分か?
残念ながらNoです
• ドメイン知識に基づくケースは生成できない
• JSON Schemaではデータのフォーマット以上のことは定義できない
• (例) 友達にメッセージを送るAPIで、「友達でないユーザへのメッセージ送信」という異常系リクエスト
• ドメイン知識が必要なケースの設計に集中できる
API結合テスト 自動化への道• リクエストデータの生成
• レスポンスの検証
• APIクライアントの生成
リクエストデータの生成• フォーマットによるもの
• json-fuzz-generator によって生成できる
• ドメインの特性によるもの
• JSON Schema からの生成は不可能
レスポンスの検証• フォーマット
• JSON Schema による Validator で可能
• perl-JSV (Perl), json-schema (Ruby)
• APIのロジックに基づくもの
• 例: リクエストで指定したユーザidのデータが返ってくる
• JSON Schema からは不可能
APIクライアントの自動生成• jsonism で生成可能 (Ruby ライブラリ)
client = Jsonism::Client.new(schema: schema) client.methods(false) #=> [:create_app, :delete_app, :info_app, :list_app, :update_app] # GET /apps client.list_app # GET /apps/1 client.info_app(id: 1) # POST /apps client.create_app(name: "alpha") # PATCH /apps/1 client.update_app(id: 1, name: "bravo") # DELETE /apps/1 client.delete_app(id: 1)
API結合テスト 自動化への道
リクエスト生成 レスポンス検証クライアント 生成
フォーマット ドメイン特性 フォーマット ロジック
fuzz-json-generator ★ perl-JSV
json-schema ★ jsonism
ドメイン知識やロジック部分に集中したテスト設計が可能
まとめ• JSON Schema で仕様を記述すると開発でもテストでも利点がある
• JSON Schema を使って、API結合テストの自動化に取り組んでいる
• 自動化が進むと、より高品質な開発・テストに集中できる
ありがとうございました• json-fuzz-generator
• https://rubygems.org/gems/json-fuzz-generator
• https://twitter.com/deme0607
json-fuzz-generator要改善点• 正常系データの複数生成
• 例: 各種境界値
• 現状、最大値・最小値が定義されているような数値は範囲内のランダム値を返すような実装
• 未対応のschema
• pattern (正規表現)
• patternにマッチする/しない文字列を自動生成
• $ref (参照) 系
• 異常系パラメータの精度向上
• Fuzzingでは桁あふれを起こしうる数値や文字化けを起こしやすい文字列を入力することが効果的
• 現状は単純な異常値しか生成してない
• stringを期待するデータにintegerを出力
• 最大値・最小値の範囲から外れる値を出力
• プロダクトに基づくパラメータの生成
• 過去にバリデーション漏れ・問題を起こしたパラメータなど
• ライブラリに同梱するのではなく、ユーザが動的に追加できる仕組み