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 
1. テストデータをリクエスト 
として送信 
[request] 
GET api/user 
id: 1 
[response] 
id: 1 
name: deme0607 
email: deme0607@example.com 
2. レスポンスデータを期待結果 
と比較
それだけで十分? 
• API は様々なコンポーネントと結合して動作 
API 
Server 
User 
API 
Server 
DB 
Load Balancer / Reverse Proxy 
[request] [response]
API結合テスト 
• 実環境で動作しているAPIを実際のクライアン 
トと同じ経路からテスト 
API 
Server 
結合 
テスト 
API 
Server 
DB 
Load Balancer / Reverse Proxy 
[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": 
"deme0607@example.com" 
} 
! 
{ 
"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 
=> 
"deme0607@example.com", 
}; 
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" 
} 
JSON Schema の入力 
! 
{ 
"title": 
"Basic 
Schema", 
"type": 
"object", 
"properties": 
{ 
"id" 
: 
{ 
"type": 
"integer", 
"minimum": 
0 
}, 
"name": 
{ 
"type": 
"string" 
}, 
"birthday": 
{ 
"type": 
"string", 
"format": 
"date" 
} 
} 
} 
正常系データの出力
異常系データの生成 
[ 
["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 
• Twitter 
• https://twitter.com/deme0607
json-fuzz-generator要改善点 
• 正常系データの複数生成 
• 例: 各種境界値 
• 現状、最大値・最小値が定義されているような数値は範囲内のラン 
ダム値を返すような実装 
• 未対応のschema 
• pattern (正規表現) 
• patternにマッチする/しない文字列を自動生成 
• $ref (参照) 系
• 異常系パラメータの精度向上 
• Fuzzingでは桁あふれを起こしうる数値や文字化けを起こしやすい文 
字列を入力することが効果的 
• 現状は単純な異常値しか生成してない 
• stringを期待するデータにintegerを出力 
• 最大値・最小値の範囲から外れる値を出力 
• プロダクトに基づくパラメータの生成 
• 過去にバリデーション漏れ・問題を起こしたパラメータなど 
• ライブラリに同梱するのではなく、ユーザが動的に追加できる仕組 
み

JSON Schema と API テスト YAPC::Asia Tokyo 2014

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