見出し画像

「API自動テスト_レスポンスアサーションのコーディングルール化のすすめ」

こんにちは。SHIFTのテスト自動化エンジニアの松岡です。

ここ1年ほど、お客様のAPI自動テストのプロジェクトに参画しておりました。

今回はNodeJSでのfrisbyによるテスト実装についての注意点を記載したいと思います。

frisby/jasmineを使用したAPIのテストで、レスポンスJSONのアサーションを行うことは多いと思いますが、レスポンスアサーション部分のコーディングがある程度自由であるため、人によって記載が異なったり、可読性が低下してしまったりします。

私がプロジェクトに参画した当初も、コーディングルールが決まっておらず、初期メンバーが実装したテストコードの読解とメンテナンスに苦労したことがありました。

ここではレスポンスJSONのアサーションの記述の標準として、一例をご紹介したいと思います。

return frisby 
.get('http://www.foobar.com/hoge') 
.then((res) => { 
// ここの書き方を統一します 
}) 
.done(done) 

▼共通

.then((res) => { 
const json = res.json; 
}) 

レスポンスからレスポンスJSONを取り出し、定数に格納します。

res.jsonと毎回書くよりもコード量、typoを減らせるようにします。

▼レスポンススキーマチェックが行われてない場合、またはnullになる可能性もある可変項目の場合

.then((res) => { 
const json = res.json; 
// ①キー項目確認 
expect(json).toHaveProperty('label'); 
// ②値の存在確認(null不可かつ空文字不可の項目) 
expect(json.label.length).toBeGreaterThan(0); 
// ②'値の存在確認(null不可の項目) 
expect(json.label).not.toBeNull(); 
// ③値の確認 
expect(json.label).toBe(foobar); 
// ①と③をあわせた記載 
expect(json).toHaveProperty('label', foobar); 
}) 

レスポンススキーマチェックを行わない場合、まずアサーション対象のキー項目が存在するか確認します(①)。

値が存在すること(nullでない、空文字でない)を確認する場合は、①のあとにアサーション対象項目のlengthを確認しています(②)。

空文字を許容する場合はnull“でない”ことのみをnot.toBeNullで確認します(②')。

値の存在確認ではなく、正しい値が事前にわかっている場合(上記の例では変数foobarに格納)、toBeで直接アサーションします(③)。

①と③を同時に行うこともできます。toHavePropertyの第2引数にチェックしたい値/変数を設定します。

▼レスポンススキーマの定義とレスポンススキーマチェックが行われている場合

.then((res) => { 
const json = res.json; 
// ②値の存在確認 
expect(json.label.length).toBeGreaterThan(0); 
// ③値の確認 
expect(json.label).toBe(foobar); 
})

キー項目の確認はレスポンススキーマチェックで担保できているため、①を省略して記述します。

データタイプごとの記載

文字列型は上記で示したとおりです。 ここでは文字列型以外の記載方法を示します。

▼数値

.then((res) => { 
const json = res.json; 
// ①値の存在確認 
expect(json.label_number).not.toBeNull(); 
// ②値確認 
expect(json.label_number).toBe(bar); 
}) 

数値型は空文字がないためnot.toBeNullで値の存在確認を行っています(①)。

値確認は文字列型と同じくtoBeで行います(②)。ここでは変数barの値と等しいことを確認しています。

▼boolean

.then((res) => { 
const json = res.json; 
// true確認 
expect(json.label_bool_1).toBeTruthy(); 
// false確認 
expect(json.label_bool_2).toBeFalsy(); 
}) 

▼配列

.then((res) => { 
const json = res.json; 
// ①配列であることの確認 
expect(Array.isArray(json.label_array)).toBeTruthy(); 
// ②配列の要素数確認(から配列でないことの確認) 
expect(json.label_array.length).toBeGreaterThan(0); 
// ②'配列の要素数確認(固定の場合) 
expect(json.label_array.length).toBe(3) 
}) 

配列の場合、事前にArray.isArrayで配列であることの検証を行います。これがbooleanで配列かどうかの判定を返すのでtoBeTruthy()で判定します(①)。もちろん、レスポンススキーマで配列であることの確認が取れている場合は省略します。

空配列([])でないことを確認するために、lengthを検証しています(②)。

配列数が固定の場合は直接toBeで数値と比較します(②')。条件によって配列数が変わるようなAPIの場合、事前に変数に格納しておくと良いかもしれません。

▼アンチパターン

.then((res) => { 
const json = res.json; 
expect(json.label_bool).toBe(true); 
expect(json.label).not.toBe(null); 
}) 

上記のような書き方は可読性を下げるため、それぞれtoBeTruthy()、not.toBeNull()に統一したほうが良いでしょう。

▼Array of Object

.then((res) => { 
// ①変数定義 
let objVal = null; 
const json = res.json; 
// ②対象取得 
for(let i = 0; i < json.label_arry_of_obj.length; i += 1) { 
if (json.label_arry_of_obj[i].id === idVal) { 
objVal = json.label_arry_of_obj[i]; 
break; 
} 
} 
// ③取得確認 
expect(objVal).not.toBeNull(); 
// ④子要素の検証 
expect(objeVal.label_text).toBe('hogehoge'); 
…… 
}) 

検索を行い、オブジェクトの配列を返すようなAPIの場合、配列内の特定のオブジェクトのみ検証したい場合の記述を共通化します。

まずthenブロックの最初に、対象のオブジェクトを格納する変数を定義しておきます(①)。初期値をnullにしておくことで、後続のステップでの取得確認を行いやすくします。

配列項目をfor分でループし、対象のオブジェクトが見つかったら、①で定義した変数に格納します(②)。ここでは配列項目label_arry_of_objの子項目idが、予め値を設定しておいた変数idValと等しいオブジェクトをアサーションの対象としています。

for文を抜けたら、①で定義した変数がnullでないことを確認することで、アサーション対象が取得できていることを確認します(③)。

その後、これまでと同じように子要素を検証していきます(④)。


まとめ

ここであげたのは一例ですが、チームでのテスト実装の場合、上記のようなルールで属人化をなるべく排除し、APIの仕様が変わってもメンテナンス性を高く維持することができると思われます。また、コーディングルールをドキュメント化しておくと、のちの新規参入メンバーに対する導入もスムーズになるので併せておすすめしておきます。
――――――――――――――――――――――――――――――――――

執筆者プロフィール:松岡 直人                    SIerでのJava、jqueryの開発経験を経て、SHIFTに入社。 SHIFTでは入社当初より画面/APIテスト自動化に関わる。 「人間が最低限しか頑張らない自動化」を目指している。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら 
https://recruit.shiftinc.jp/career/