KCF Labo Blog

KDDI Commerce Forwardの開発ブログ

Robot Framework、RESTinstanceでWebAPIのテストをする

はじめまして。エンジニアの河野です。

最近、担当しているプロダクトのリグレッションテストを実施しようとしていて、何か良いツールないかと探した結果、Robot Frameworkを使ってみることにしました。

やりたいことはとりあえずWeb APIのシナリオテスト的なものを実施したかったので、RESTinstanceというプラグインを使うと記述が楽そうだったのでこちらも一緒に利用してみました。

そちらも絡めて軽くレポートさせて頂きます。

インストール手順

基本的に公式サイトにドキュメントへのリンクがまとまっているので、インストールからデモ動作まではこちらから各種ドキュメント見て頂くのが良いと思いますが、ざっと説明しておきます。

robotframework.org

github.com

今回はpipでインストールするのでpipは必須となります。

自分はDocker上のUbuntuコンテナに入れたので、以下をDockerfileに記述しておきました。

Robot Framework自体はプロセスではないのですが、ローカルマシンにあれこれ入れたくないのとローカル用の開発環境もDokcerなので検証しやすかったので。

日本語使いたかったのでlanguage-packも入れています。

  • Dockerfile
FROM ubuntu:18.04
MAINTAINER kcf.kono

ENV DEBIAN_FRONTEND=noninteractive

# install packages
RUN apt-get update
RUN apt-get install -y git python3-pip

# install additional packages
RUN apt-get install -y tzdata locales language-pack-ja-base language-pack-ja

# setting
RUN locale-gen ja_JP.UTF-8

RUN mkdir /sync

ENV LANG=ja_JP.UTF-8

# install robot framework
RUN pip3 install docutils robotframework

RUN pip3 install --upgrade RESTinstance

Robot FrameworkとRESTinstanceのインストール部分はこちらになります。

RUN pip3 install docutils robotframework

RUN pip3 install --upgrade RESTinstance

とりあえず動かしてみる

QuickStartGideがあるので、そちらをGitHubから持ってきて動作させてみます。

$ git clone https://github.com/robotframework/QuickStartGuide.git

QuickStart.rstというreStructuredText形式のファイルがあって読んでみると、直接そのファイルを指定して実行することが出来る様です。

ただ、拡張子が.robot以外のファイルは非推奨と警告でるので、自分でテストケース作る場合はrobotファイルを作ってそちらに記述してます。

$ robot QuickStart.rst

[ WARN ] Automatically parsing other than '*.robot' files is deprecated. Convert '/sync/QuickStartGuide/QuickStart.rst' to '*.robot' format or use '--extension' to explicitly configure which files to parse.
==============================================================================
QuickStart
==============================================================================
User can create an account and log in                                 | PASS |
------------------------------------------------------------------------------
User cannot log in with bad password                                  | PASS |
------------------------------------------------------------------------------
User can change password                                              | PASS |
------------------------------------------------------------------------------
Invalid password                                                      | PASS |
------------------------------------------------------------------------------
User status is stored in database                                     | PASS |
------------------------------------------------------------------------------
QuickStart                                                            | PASS |
5 critical tests, 5 passed, 0 failed
5 tests total, 5 passed, 0 failed
==============================================================================
Output:  /sync/QuickStartGuide/output.xml
Log:     /sync/QuickStartGuide/log.html
Report:  /sync/QuickStartGuide/report.html

実行後にはxmlとhtmlが出力され、htmlファイルを開くと結果レポートが表示されます。

結果レポートのスクリーンショットこちらのEXAMPLE 2に載っているので参考になるかと思います。

テストケースを記述してみる

まずは簡単なテストケースを記述してみることにします。

今回はWeb APIのテストを行いたいので、RESTinstanceを利用してヘルスチェック用APIへのテストを実行してみようと思います。

ヘルスチェック用APIはHTTPステータス 200を返すだけのものとなります。

*** Settings ***
Library       REST    https://example.com

*** Test Cases ***
リクエストを送るとHTTP STATUS 200が返ってくる
    GET         /health_check
    Integer     response status     200

Robot FrameworkはPythonで出来ていて、テストケースの記述方法もPythonに習ってテーブル形式で記述します。

「*** Settings ***」テーブルにはインポートするライブラリを記述します。

今回はRESTinstanceを利用するのでRESTと指定しています。

RESTの後に接続するURLを指定が出来、アクション実行時にURLを省略することが出来ます。

指定しない場合はアクセスの都度指定することになります。

「***Test Cases ***」テーブルに実際のアクションを記述します。

行頭にインデント入れていない部分がテストケースのタイトルになります。

Python3使っているからか普通に日本語は使えました。

アクションは行頭にインデント入れて字下げした箇所に記述します。

GET [PATH]で、[PATH]に対してGETリクエストして

Integer [検証対象] [期待値] でHTTPステータスが200で返ってくることを検証しています。

実行すると以下の様になります。

==============================================================================
Health Check :: RESTinstanceのサンプル用にヘルスチェック用APIのリクエストを...
==============================================================================
[ WARN ] Response body content is not JSON. Content-Type is: text/plain; charset=utf-8
リクエストを送るとHTTP STATUS 200が返ってくる                         | PASS |
------------------------------------------------------------------------------
Health Check :: RESTinstanceのサンプル用にヘルスチェック用APIのリ...  | PASS |
1 critical test, 1 passed, 0 failed
1 test total, 1 passed, 0 failed
==============================================================================
Output:  /sync/tests/output.xml
Log:     /sync/tests/log.html
Report:  /sync/tests/report.html

ヘルスチェックのResponse Bodyが空文字列なので、Warning出ていますがとりあえずテストをパスしました。

RESTinstanceの良いところはHTTPリクエストのテストケースをシンプルに記述出来るところで、同じHTTPリクエスト系のライブラリのrobotframework-requestsを使うと以下の様に若干めんどくさくなります。

*** Settings ***
Documentation   robotframework-requestsのサンプル用にヘルスチェック用APIのリクエストを記述
Library         RequestsLibrary

*** Test Cases ***
リクエストを送るとHTTP STATUS 200が返ってくる
    Create Session                  sample                  https://example.com     verify=True
    ${resp}=                        Get Request             sample                  /health_check
    Should Be Equal As Strings      ${resp.status_code}     200

もう少し凝ったテストをしてみる

実際のシナリオテストとなるともう少し凝ったことをするケースがあるので、以下をやってみました。

  1. 認証用APIからトークンを取得
  2. 取得したトークンをヘッダにセット
  3. データ一覧取得用のAPIをリクエス
  4. レスポンスデータのJSONの特定のカラムを検証

これをテストケースとして記述してみます。

*** Settings ***
Library     REST

*** Variables ***
${api_url}       https://example.com
${auth_url}      https://auth.example.com
${auth_api_key}  api_key_1
${auth_header}   {"x-api-key": "${auth_api_key}"}
${auth_json}     {"id": 1 , "credential_key": "qazxswedcvfr"}

*** Test Cases ***
ログインしてデータ一覧取得
    Set headers             ${auth_header}
    &{auth_response}        POST                                               ${auth_url}/loginauth     ${auth_json}
    Output                  ${auth_response.body.token}
    Set headers             {"x-auth-token": "${auth_response.body.token}"}
    &{response}             GET                                                ${api_url}/resources
    Integer                 response status                                    200
    String                  ${response.body[0].name}                           "テストデータ"

上記のテストケースですが、新たに「*** Variables ***」というテーブルを追加しています。

こちらでは上記の様に変数を定義することが出来ます。

また、「*** Test Cases ***」テーブルの中でも結果を変数に代入することが可能です。

これでリクエストの結果を利用して他のリクエストを実行することなどが出来ます。

変数の宣言方法は以下があるようです。

RESTinstanceのサンプルから引用します。

    ${json}=      {"foo": "bar" }   # JSON object, represented as Python str
    &{dict}=      foo=bar           # Python dict, corresponds to JSON object
    ${array}=     ["foo", "bar"]    # JSON array, represented as Python str
    @{list}=      foo   bar         # Python list, corresponds to JSON array

${VARIABLE}で定義するとスカラ変数(String)となりますが、&{VARIABLE}で定義すると辞書変数(Object)になるのでJSON形式のレスポンスに${auth_response.body.token}のようにアクセスすることも出来ます。

RESTinstanceの場合は$自体がresponse bodyと等価の様なので、以下の様に記述することも可能です。

...省略
*** Test Cases ***
ログインしてデータ一覧取得
    Set headers             ${auth_header}
    &{auth_res}             POST                                               ${auth_url}/loginauth     ${auth_json}
    Output                  $.token
    Set headers             {"x-auth-token": "${auth_res.body.token}"}
    &{response}             GET                                                ${api_url}/resources
    Integer                 response status                                    200
    String                  $[0].name                                          "テストデータ"

外部ファイル読み込み

「*** Variables ***」テーブルの内容を別ファイルにし、以下のように外部ファイルをincludeすることも出来ます。

  • common.robot
*** Variables ***
${api_url}       https://example.com
${auth_url}      https://auth.example.com
${auth_api_key}  api_key_1
${auth_header}   {"x-api-key": "${auth_api_key}"}
${auth_json}     {"id": 1 , "credential_key": "qazxswedcvfr"}
  • sample.robot
*** Settings ***
Library     REST
Resource     common.robot
...省略

上記のように「*** Settings ***」テーブルでResource [PATH]とすることで外部ファイルを読み込む事ができるので、テストファイル毎に重複する様な場合に効率的です。

環境変数

環境変数も取り込むことが可能です。

リポジトリに上げたくない様な機密情報はOSの環境変数に設定して、参照することでテストケースへの記述を回避することが出来ます。

環境変数は%{VARIABLE}で参照出来ます。

設定例としては以下になります。

$ export AUTH_API_KEY=api_key_1

$ cat common.robot

*** Variables ***
${api_url}       https://example.com
${auth_url}      https://auth.example.com
${auth_api_key}  %{AUTH_API_KEY}
${auth_header}   {"x-api-key": "${auth_api_key}"}
${auth_json}     {"id": 1 , "credential_key": "qazxswedcvfr"}

変数についてはこちらにドキュメントがまとまっています。

日本語翻訳版があるのが英語の得意でない自分にはありがたいですね。

まとめ

今回は簡単なWeb APIのシナリオテストを作って実施してみました。

使ってみた感想としては以下になります。

良かったところ

  • DSLが直感的で分かりやすい
  • 機能が豊富なため細かい制御が可能
  • 出来るといいなが大抵用意されている
  • ドキュメントが充実している
  • RESTinstanceは記述を楽にしてくれる

良くなかったところ

  • テーブルのインデント調整がめんどくさい (面合わせなくてもいいのですが合わせないと見にくいので。フォーマッターで解決出来そうですが。)
  • RESTinstanceのスター数が少ないし、バージョンも2018/12/20の時点ではまだ1.0.0rc4

正直、今回くらいの利用ではあまり困ったところもなく、Web APIのテストをやる程度でしたら大分楽に記述出来ました。

Selenium使ってフロントエンド含めたテストをやると大変かもしれませんが、今回そこはスコープ外なので考慮していません。

もう少し本格的にプロダクトチーム内でRobot Frameworkの導入をしてみようと思いますので、また新たな発見などあったら紹介させて頂こうと思います。