Software Design連載 2021年11月号 Robot FrameworkでE2Eテストを自動化する

最初に少しイベントの宣伝

こんにちは。金谷です。

Software Designに連載させていただいております「Pythonモダン化計画」は、前半の4回で、それぞれの局面に合ったテスト手法を用いることで変更容易性を確保する話をしてきました。 前半の4回すべてに出てきたツールにJenkinsさんがいて、何らかのかたちで自動化されています。

モノタロウにおけるモダン化計画に不可欠な存在のJenkinsさん。 なんとこのたび、Jenkins Day Japan 2021というイベントで、Jenkinsの活用事例を発表させていただくことになりました。

「モノタロウの開発・リリースサイクルを支えるJenkinsの活用事例」という内容で金谷が発表させていただきます。 詳細とお申込みは、下記のURLからご覧ください。

cloudbees.techmatrix.jp

では本題に入ります。 本記事の初出は、 Software Design2021年11月号「Pythonモダン化計画(第4回)」になります。 過去の連載記事は以下を参照ください。

はじめに

モノタロウ(MonotaRO)では事業者向けに間接資材を販売するECサイトを運用しています。 このECサイトはPythonによる内製で、最初期には小さなCGIアプリだったものを少しづつ機能追加、拡大してきました。 現在では、WSGIベースの独自フレームワークとflaskのハイブリッドでWebサーバ側のアプリケーションを実装しています。

ECサイトの実装に修正が発生したときは、「修正結果が期待通りでリリース可能か」を検証する受け入れテストが必要になります。 その中の「サイトのUIを実際に操作したときの動作」を確認するE2E(End to End)テストは、以前は開発者やテストエンジニアによる手作業に大きく依存していました。 手動でのテストには時間も人手もかかります。 実際、見かけが単純な画面でも、商品の情報やユーザーのログインの有無、使っているブラウザの種類といった条件に応じて表示内容や挙動が変わり、それらをすべて網羅するのは大変な労力を要します。 テストシナリオを効率的に実施する専門のエンジニアのリソースにも限りがあることから、テストの規模を限定してリリースを早める(デグレーションのリスクをとる)か、安全のために開発の手を止めて手分けしてテストを実施するか、というトレードオフを強いられている状況でした。

そこでモノタロウでは、2016年ごろからRobot FrameworkというPythonベースのフレームワークを導入し、ECサイト開発時のE2Eテストを自動化しています。 今回は、このE2Eテスト自動化の取り組みを紹介します。

Robot Framework 採用の決め手

E2Eテスト自動化にあたり、最初に試験的に使ってみたのは、Geb+SpockというJavaベースのフレームワークでした。 このツールは、テストを書くのにプログラム言語的な記述が必要なこと、テストの実行にコンパイルを伴うことからテストエンジニアとの協調が難しく、本格的な導入には至りませんでした。

より軽量なフレームワークを探す中でフィットしたのが、もともとノキア社で開発され2008年にオープンソース化されたRobot Frameworkです。 Robot Frameworkがモノタロウのニーズにマッチしたのは以下のようなポイントでした。

  • 自然言語に近い記述でテストシナリオを書ける
  • 日本語でテストケースを定義できる
  • ブラウザの操作を自動化するSeleniumと連携するための拡張があり、主要なブラウザでのテストを自動化できる
  • ファイル操作やSSH経由でのコマンド実行、DB操作などの拡張機能も充実しているので、テストそのものの実装に専念できる
  • テスト手順をライブラリにしたり、複数のテストをまとめたりする機能があり、テストの大規模化に対応できる
  • テストシナリオを実行した結果をHTMLやXMLで書き出してレポートにでき、エビデンス(スクリーンショット)付きの実行結果を記録できる
  • XUnit互換の結果ファイルを得られるので、これを解析してテスト報告書を整形したり、CIと連携したりできる
  • システムの主力開発言語と同じPython製で、ドキュメントの和訳資料もあるので、導入のハードルが低い

自然言語風にE2Eテストのシナリオを書ける手軽さも手伝って、主要なページのテストケースは数か月で網羅できました。 それをJenkinsで自動実行する仕組みを仕掛け、半年ほどで主要な機能のテストについて、それぞれ数十パターンにわたるテストが揃いました。 結果、以前はリリースのたびにエンジニアがついて1時間ほどかけていたテストが数分で自動的に終わるようになりました。

トップページのE2Eテスト事例

実際にRobot Frameworkを使って、「モノタロウのトップページにChromeでアクセスしてスクリーンショットを取得する」というE2Eテストを書いてみましょう。 Python 3の環境で下記2つのライブラリをインストールしてください。 また、Chrome Webdriverもダウンロードして中身のchromedriverにPATHを通してください。

$ pip install robotframework robotframework-seleniumlibrary 

Robot FrameworkのSeleniumLibraryには、「Open Browser」や「Capture Page Screenshot」といった、ブラウザを制御するためのキーワードがあらかじめ定義されています。 たとえば、「ブラウザでトップページを表示する」という操作に対するテストケースはこう書けます。 キーワードと引数の間にスペースが2つ以上あることに注意してください。

Visit top page   # トップページを訪問  (シャープ文字以降はコメントです)
    Open Browser  about:  browser=chrome   # Chromeブラウザを起動
    Go To  http://localhost:8000/  # 指定URLに移動
    Title Should Be  Welcome  # ページタイトルを検証(「Welcome」は変数)
    Caputure Page Screenshot  toppage.png  # エビデンスを撮影(「toppage.png」は変数)
    Close all Browsers  # ブラウザを閉じる

1行めの「Visit top page」は、このテストケースの名前を表します。 2行め以降の「Open Browser」や「Go To」は、Robot Frameworkの組み込みの機能を呼び出すキーワードです。

テストケースごとに上から順番にキーワードを実行していき、その成功/失敗を記録して報告するというのが、Robot Frameworkの基本的な流れになります。 このように組み込みのキーワードを使ってテストケースを記述してもいいのですが、モノタロウでは自社サイトを前提とした専用のキーワードを定義することで、DOMの変更でテストシナリオをすべて書き換えることにならないようにしています。 その際、専用のキーワードを日本語で定義できるのがRobot Frameworkの利点です。 たとえば、以下のようなテストシナリオを用意することで、トップページのエビデンスを取得するE2Eテストが実現できます。

*** Settings ***
Documentation  トップページのエビデンスを取得するテスト
Library  SeleniumLibrary
Test Teardown  ブラウザーを閉じる  # 下記で定義したキーワード

*** Test Cases ***
トップページを表示しスクリーンショットを撮る
    ブラウザーを開いてトップページを表示する  # 下記で定義したキーワード
    トップページが表示されていること  # 下記で定義したキーワード
    スクリーンショットを撮る  # 下記で定義したキーワード

*** Keywords ***
ブラウザーを開いてトップページを表示する  # 新しいキーワードの定義
    Open Browser  https://www.monotaro.com/  browser=chrome

ブラウザーを閉じる  # 新しいキーワードの定義
    Close Browser

トップページが表示されていること  # 新しいキーワードの定義
    Page Should Contain Element  css=.headbtn

スクリーンショットを撮る  # 新しいキーワードの定義
    Capture Page Screenshot  filename=toppage-screenshot.png

テストシナリオを実行するには、robotコマンドを使います。 上記のテストシナリオをTopPageTest.robotというファイルに保存して、下記のようにコマンドを実行してみてください。 「Chromeが自動で立ち上がり、モノタロウのトップページにアクセスしてスクリーンショットを取得し、Chromeが自動で閉じる」という操作が自動で実行されて、スクリーンショットおよびテスト結果のレポートがlogsフォルダに出力されます。

$ robot -d logs TopPageTest.robot

logsフォルダに出力されるファイルのうち、report.htmlというファイルがテスト結果レポートです。 テスト結果レポートファイルを見ることで、下記の情報を得ることができます。

  • 成功/失敗したテストケース
  • 失敗したテストケースの失敗箇所(スクリーンショット付き)
  • 各キーワードの実行時間

f:id:atkanaya:20211110113033p:plain
Robot Frameworkのテスト結果レポート

f:id:atkanaya:20211110113141p:plain
Robot Frameworkdの実行結果ログ

下記に、Robot Frameworkを使ったE2Eテストの流れを要約します。

f:id:atkanaya:20211110113241p:plain
Robot Frameworkを使ったE2Eテストの流れ

ロケーターについて

キーワード「トップページが表示されていること」の定義に記述されている「Page Should Contain Element」は、SeleniumLibraryのキーワードです。 そのキーワードに付随する「css=.headbtn」はロケーターと呼ばれ、DOMの要素を特定するために使われます。 この例では、「CSSセレクターでheadbtnクラスが付与されたDOM」を特定しています。

モノタロウではロケーターとして主にCSSセレクターを使っていますが、id属性、name属性、XPath、jQueryなども利用できます。 たとえば「何番めの要素か」で特定したい場合にはXPathを使うと便利でしょう。 詳しくはSeleniumLibraryのドキュメントを参照してください。

ログイン処理のE2Eテスト事例

Robot Frameworkでは、%{ENV_NAME}という構文により、環境変数ENV_NAMEの値を取得できます。 これを使って、ログインIDとパスワードのようなクレデンシャルを渡せば、ログイン処理に対するE2Eテストが可能です。 参考までに、モノタロウのECサイトへのログイン処理をテストするためのテストシナリオは以下のようなものになります。

*** Test Cases ***
ログインする
    ブラウザーを開いてトップページを表示する
    ログインボタンをクリックする
    ログインする
    トップページが表示されていること
    ログイン済みであること

*** Keywords ***
ログインボタンをクリックする
    Click Element  css=.headbtn__btn--login

ログインする
    Input Text  name=userId  %{LOGIN_ID}
    Input Password  name=password  %{PASSWORD}
    Click Element  css=.Button--Secondary

ログイン済みであること
    Page Should Contain Element  css=.User_info

この例を試すには、roborコマンドを実行する前に、あらかじめ環境変数LOGIN_IDPASSWORDに(モノタロウのECサイトの)ログインIDとパスワードを設定しておく必要があります。

$ export LOGIN_ID=xxx
$ export PASSWORD=yyy

キーワードの一部をパラメータ化する

ここまでのキーワード定義の例は、すべて固定の操作を実行するものでした。 Robot Frameworkでは、キーワードの定義で${name}という構文を使ったパラメータも利用できます。 これにより、定義したキーワードを再利用できるようになります。 たとえば、下記のようにキーワードを定義したうえで「キーワード「軍手」で検索する」をテストケース内に記述すると、${keyword}の部分に「軍手」という文字列が入った状態で実行されます。

*** Keywords ***
キーワード「${keyword}」で検索する
    Input Text  id=keywords  ${keyword}

非同期なページの検証

Ajaxにより非同期的に読み込まれるコンテンツやSPA(Single Page Application)のページに対するE2Eテストは、「操作→待ち→検証」を明示的に記述することで自動化できます。 ただし、このとき単純にSleep 5sのように待つ方法は好ましくありません。 後から読み込まれるコンテンツが早く返ってきたときには無駄に待つことになりますし、読み込まれるコンテンツが遅く返ってきたときにはエラーになる場合があるからです。

SeleniumLibraryには、非同期で読み込まれるコンテンツに対するキーワードとして、下記のようなものが用意されています。 これらのキーワードは個別にタイムアウト値を設定できるので、待ちすぎる前にタイムアウトエラーにすることも可能です。

Wait Until Element Contains  # 指定の要素内に指定のテキストが含まれるまで待つ
Wait Until Element Is Visible  # 指定の要素が表示されるまで待つ
Wait Until Page Contains  # ページ内に指定のテキストが含まれるまで待つ
Wait Until Page Contains Element  # ページ内に指定の要素が含まれるまで待つ

シナリオ上の操作とページ上の実際の動作を分離する

1ファイルにキーワード定義とテストケースを書いていると、自作キーワードの再利用がしにくくなります。 特に画面遷移を伴うE2Eテストでは顕著に困ります。

モノタロウでは、こうした課題への対策として、テストシナリオと画面固有の操作とでファイルを分離しています。 これは、画面の修正を伴うUIの変更などでシナリオに影響が及ばないことを目的とした「Page Objectパターン」と呼ばれるSeleniumのアプローチを模倣したものです。 これにより、再利用性の向上やテストシナリオへの影響の抑制に加え、どこに何を書くべきかが明確になってコードの読み書きのしやすさも高まりました。

具体的には、以下のような構成で自作キーワードを定義したファイルを分離しています。

  • SeleniumLibraryやRobot Frameworkの標準ライブラリやキーワードにのみ依存する(つまい互いの依存関係がない)自作キーワードはpages以下のファイルに配置
  • pages以下の定義に依存する自作キーワードはscenarios以下のファイルに配置

たとえば、先に例として挙げたトップページのエビデンスを取得するテストTopPageTest.robotであれば、テストケース以外はpages以下のTopPage.robotに切り分けて、それぞれ以下のように配置します。

scenarios/TopPageTest.robot

*** Settings ***
Documentation  トップページのエビデンスを取得するテスト
Resource  ../pages/Common.robot
Resource  ../pages/TopPage.robot
Resource  ../pages/LoginPage.robot
Test Teardown  ブラウザーを閉じる

*** Test Cases ***
トップページを表示しスクリーンショットを撮る
    ブラウザーを開いてトップページを表示する
    # 以下省略

ログインするテスト
    # 以下省略

TopPage.robot

*** Settings ***
Documentation  トップページ
Library  SeleniumLibrary

*** Keywords ***
トップページが表示されていること
    Page Should Contain Element  css=.headbtn

ログインボタンをクリックする
    Click Element  css=.headbtn__btn--login

★配置するディレクトリ

scenarios/
  TopPageTest.robot
pages/
  Common.robot
  LoginPage.robot
  TopPage.robot

CIツールで実行する場合の注意

ここまでの説明では、robotコマンドを使って開発者が自分のコンピュータ上でテストを実行する例を紹介しました。 実際のモノタロウにおける開発では、Jenkins CIのデプロイパイプラインの最終ステップでrobotコマンドを呼んでE2Eテストを実行し、デプロイしたバージョンの主要な画面機能が正しく動作することを自動で検証しています。

Jenkinsと組み合わせるには、以下の対応をしておくのがおすすめです。

  • JenkinsのRobot Frameworkプラグインを入れる
  • ログインIDなどの認証情報は、Jenkinsの認証情報ストレージに入れておく
  • ブラウザーはHeadlessモードで起動する(Open Browser url browser=headlesschrome

課題

Robot Frameworkの導入でE2Eテストの実行は楽になりましたが、課題もまだまだあります。

1つめは、テスト時間のさらなる短縮です。 E2Eテストでは、サーバの応答や、ブラウザ側のUI更新待ちによって、シナリオの実行時間が長くなりがちです。沢山のシナリオを直列に実行すると、全体で数十分におよぶこともあります。 テスト時間を左右する要因は、テスト対象サーバのレスポンス速度、テスト実行ホストのスペック、テストケース内での待ちなど、さまざまです。 実行レポートには各キーワードの実行に要した時間も出力されるので、それを参考に時間がかかる要因を特定して改善していきます。 テストスイートやテストケース単位でRobot Frameworkを並列実行できるPabotという仕組みが有効なこともあります。

2つめの課題は、DOMの変更への耐性です。 本文ではRobot Frameworkのロケーターとしてcssを紹介しましたが、タグのclass属性がデザイン変更などで書き換えられてしまい、テストが壊れることがたまにあります。 本来はページの外観のためのclassにE2Eテストが勝手に相乗りしていることが問題といえるので、E2Eテストのロケーター用にHTML5の属性を別に定義するなどして回避するべきだと考えられます(実際にモノタロウではdata-e2eという属性を利用しています)。

おわりに

今回は、自然言語風にテストケースを書けるRobot Frameworkを導入することで、手動だったE2EテストをCIで自動化した事例を紹介しました。 手動のテストを自動化できると、同じ品質のテストを網羅的に繰り返し実行できるようになり、開発のスピードをゆるめずに不具合の発生を防げるようになります。 それに加えて、E2Eテストを含めたデプロイ後の動作確認の多くが自動化されたことでこんな効果も得られました。

  • デプロイで思わぬ場所に影響があることが検出できるようになった
  • PC版サイトやスマホ版サイトのどちらもE2Eテストできるようになった
  • ABテストのAパターンBパターンどちらもE2Eできるようになった
  • テスター自身がE2Eテストを積極的に追加できるようになった

さらに副次的な効果として、Robot FrameworkをE2Eテスト以外の作業の自動化1に応用する人も現れました。 「仕組みを用意することで、それを応用する人が現れる」ことはよくありますが、E2Eテストの自動化でそうした現象が見られたのは印象的でした。

モノタロウでは、ECサイト以外にも基幹系や大企業向けのサービスなどWeb UIを持ったシステムがあります。 現在はそれらの開発でも同じ仕組みでE2Eテストを運用しています。


  1. *** Test Cases ****** Tasks ***に変更すると、テストに限らない作業の自動化(いわゆるRPA)全般に適したレポート内容になる。