9. テストフレームワーク

ここまでの説明では、ただひとつのテストシナリオについてWebDriverを使用した実装方法を説明してきましたが、実際のシステムでは複数の画面があり各画面に対して異なる観点から様々なテストを実行する必要があります。
従って、複数のテストシナリオをどのように実行するのか、実行結果はどのように把握するのかなどを検討する必要があります。
テストフレームワークは、こうした問題に対して有用な機能を提供しています。

テストフレームワークは、古くはnodeunitから始まり、mochajasminejestなど非常に多くの種類がOSSで公開されています。どのテストフレームワークも「簡単で豊富な機能」をうたっていますが独自の記述ルールに従ってテストシナリオを記述する必要があり、その方法について学習が必要です。また、テストシナリオの記述方法に互換性はないので、一度採用したテストフレームワークを乗り換えることは難しく、よく吟味して選択する必要があります。


テストシナリオの作成方法で示した、WebDriver APIの直接使用、WebDriver APIをラップするクラスの使用、データ化したテストシナリオの3種類の方法のうちどの方法が適しているかは、テストシナリオを作成する人のスキルやテスト内容とテストフレームワークとの親和性を考慮して選択する必要があります。
また、どれか一つの方法に固定するのではなくテストシナリオの内容による使い分けも必要になると思います。従って、テストフレームワークはできるだけ柔軟にテストシナリオを記述できる種類を選択する方が良いと思います。

別の選択肢として、公開されているテストフレームワークを使用するのではなく、実行したいテスト内容やテスト環境の事情に合わせてオリジナルのテストフレームワークを作成することも検討する価値があります。
テストフレームワークは最低、複数のテストの連続実行、テストの合格失敗判定、テスト結果の集約とわかりやすい表示、の3つの機能を満たせば良いので、比較的容易に作成することができます。既存のテストフレームワークの機能がフィットしない場合には、無理に使い続けて個々のテストシナリオの作成や維持管理に時間を費やすよりも目的に合ったテストフレームワークを作成した方が効率的な自動テストを運用できることも多くあります。
また、テスト対象のアプリケーションとOSSで公開されているテストフレームワークの寿命を比べたとき、どちらが寿命が長そうなのかも検討する必要があります。例えば先に上げたnodeunitの寿命は約7年でした。
寿命を予測することは難しいのですが、自動テストの維持には手間とコストを要しますから、テストフレームワークの寿命で蓄積されたテストシナリオが実行できなくなることは大きな問題となる可能性があります。

mochaの使用例

mochaのGitHubサイトで配布されているSeleniumのサンプルに、GHOST Operatorを利用したテストシナリオを追加した使用例です。

このサンプルには、2種類のテストシナリオが含まれており、ひとつはユーザ登録の正常ケース、もう一つは電話番号の未入力ケースです。

import "@babel/polyfill"; import { Builder, Capabilities, Key } from "selenium-webdriver"; import {AppURL, DriverURL} from "../setting.js"; const {Runner} = require("../runner.js"); let runner = null; describe("User Registration", () => { beforeEach(async () => { const capabilities = new Capabilities() .set("scriptFilter",["\\.html"]) .setBrowserName("safari+G.O"); runner = new Runner(DriverURL, capabilities); }); afterEach(async () => { }); it("Normal case of user registration", async () => { await runner.run(AppURL, [ // アコーディオンメニューの選択 {action:"click", target:"</html/body/ul/li[4]/div",3000}, {action:"waitVisible", target:"#userreg", timeout:3000}, // ユーザ登録ページへ {action:"click", target:"#userreg"}, {action:"waitURL", url:/^http.*\/demo\/reg\.html$/, timeout:1000}, {action:"resetMousePosition"}, // ユーザ情報の入力 {action:"click", target:"#name1"}, {action:"delay", timeout:100}, {action:"sendKeys", key:["[HanjaMode On]yamada",Key.SPACE,Key.RETURN," tarou",Key.SPACE,Key.RETURN,"[HanjaMode Off]"]}, {action:"sendKeys", key:Key.TAB}, {action:"sendKeys", key:"Tarou.Yamada@nexaweb.com[Select All][Copy]"}, {action:"sendKeys", key:Key.TAB}, {action:"sendKeys", key:"[Paste]"}, {action:"sendKeys", key:Key.TAB}, {action:"sendKeys", key:"080-4321-8765"}, {action:"click", target:"</html/body/div[2]/form/div/div[5]/div/label[1]/input"}, {action:"click", target:"#job"}, {action:"delay", timeout:500}, {action:"click", target:"#job", offset:{x:250,y:-250}}, {action:"click", target:"</html/body/div[2]/form/div/div[7]/div[1]/label/input"}, {action:"click", target:"</html/body/div[2]/form/div/div[7]/div[3]/label/input"}, // 送信 {action:"click", target:"</html/body/div[2]/form/div/button"}, {action:"waitURL", url:/^http.*\/demo\/result\.html\?.*$/, timeout:1000}, ]); }); it("Case where no phone number is entered", async () => { await runner.run(AppURL, [ // アコーディオンメニューの選択 {action:"click", target:"</html/body/ul/li[4]/div",timeout:3000}, {action:"waitVisible", target:"#userreg", timeout:3000}, // ユーザ登録ページへ {action:"click", target:"#userreg"}, {action:"waitURL", url:/^http.*\/demo\/reg\.html$/, timeout:1000}, {action:"resetMousePosition"}, // ユーザ情報の入力 {action:"click", target:"#name1"}, {action:"delay", timeout:100}, {action:"sendKeys", key:["[HanjaMode On]yamada",Key.SPACE,Key.RETURN," tarou",Key.SPACE,Key.RETURN,"[HanjaMode Off]"]}, {action:"sendKeys", key:Key.TAB}, {action:"sendKeys", key:"Tarou.Yamada@nexaweb.com[Select All][Copy]"}, {action:"sendKeys", key:Key.TAB}, {action:"sendKeys", key:"[Paste]"}, {action:"sendKeys", key:Key.TAB}, {action:"click", target:"</html/body/div[2]/form/div/div[5]/div/label[1]/input"}, {action:"click", target:"#job"}, {action:"delay", timeout:500}, {action:"click", target:"#job", offset:{x:250,y:-250}}, {action:"click", target:"</html/body/div[2]/form/div/div[7]/div[1]/label/input"}, {action:"click", target:"</html/body/div[2]/form/div/div[7]/div[3]/label/input"}, // 送信 {action:"click", target:"</html/body/div[2]/form/div/button"}, {action:"delay", timeout:1000}, {action:"waitURL", url:/^http.*\/demo\/reg\.html$/, timeout:0}, ]); }); });

※ このサンプルの関連ファイルはSeleniumSamples.zipのsec9_mocha_sampleに収められています。

サンプルを実行してみる場合、zipファイルを展開し、sec9_mocha_sample内のsettings.jsで定義されているWebDriverのURLとデモアプリのURLを環境に合わせて修正してください。
また、GHOST OperatorのWebDriver Serverには、BrowserName=”safari+G.O”を指定したCapabilitiesでセッションを開始するように設定されています。こちらも環境に合わせて変更してください。

環境設定が終わったら以下のように依存モジュールをインストールします。

$ cd sec9_mocha_sample $ npm install > chromedriver@88.0.0 install C:\projects\mps\tutorial\sec9_mocha_sample\node_modules\chromedriver > node install.js Current existing ChromeDriver binary is unavailable, proceeding with download and extraction. Downloading from file: https://chromedriver.storage.googleapis.com/88.0.4324.96/chromedriver_win32.zip Saving to file: C:\usr\cygwin64\tmp\88.0.4324.96\chromedriver\chromedriver_win32.zip Received 1040K... Received 2080K... Received 3120K... 中略 added 513 packages from 418 contributors and audited 514 packages in 12.34s 41 packages are looking for funding run `npm fund` for details found 0 vulnerabilities

以下の操作でテストが実行されます。

  1. GHOST Operator WebDriver serverを起動

  2. 使用するiPad端末のMobileSafariから、GHOST Operator WebDriver serverのWebページを開く

  3. Webページに表示される構成情報から、利用する構成の[Select]をタップし待機状態とする

  4. mocha_sampleディレクトリでnpm testを実行する

正常に実行されると以下のような表示になります。

「should render a message on a Google search result」のテストは、Chrome WebDriverを使用してデスクトップPC上でChromeブラウザが起動して実行されます。

「Normal case of user resistoration」と「Case where no phone number is entered」のテストは、GHOST Operatorを利用してiPad実機上のMobile Safariで実行されます。