DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

UIテストの最前線: SeleniumConf Berlin 2017参加レポート

SWETグループの薦田です(@toshiya_komoda)。10月9、10日にドイツ・ベルリンで開催されたSelenium Conferenceに参加してきました。私もLightning Talksで、機械学習とUIテストに関する発表をさせていただきました(スライド)。

こちらの内容については、別の記事で書かせていただくこととし、この記事ではカンファレンスで聴講し、特に気になったトークについてレポートさせていただこうと思います。

ウェブ・アプリケーションにおけるUIテストの最前線の状況が少しでも伝われば幸いです。

f:id:swet-blog:20171018220430j:plain

Seleniumについておさらい

Selenium WebDriverは、ウェブ・ブラウザをプログラムから操作するためのソフトウェアツールです。OSSとして開発されており、主な用途としてウェブ・アプリケーションのテスト自動化に用いられています。現バージョンは3.6.0(2017年10月24日現在)、プロジェクト開始から10年以上の歴史を持ちます。

Selenium内部ではブラウザを操作するためにWebDriverと呼ばれるAPIが用いられています。主要なブラウザベンダが、自社ブラウザ向けのWebDriverの実装を提供しています(Official Selenium Blog: Selenium 3.0: Out Now)。 Seleniumはブラウザベンダが公式に認めているブラウザ・テストツールと言って言い過ぎではないでしょう。また、W3Cにて仕様標準化の作業が進められています(WebDriverのCR)。

Selenium Conference

Selenium Conferenceはプロジェクト公式のミートアップイベントで、プロジェクトのリーダーであるステアリング・コミッティも参加するイベントです。7年の歴史があり、2011年から2015年は年に1度、2016年からは年に2度ずつ開催されています。 前回のカンファレンスは、2017年4月にアメリカ オースティンで開催されており、SWETでも参加レポートを公開しています。 また、過去のトークがYouTube上で視聴できるようになっているので、興味のある方は眺めてみると雰囲気が伝わるかと思います。 今回のトークもすでにアップロードされています。

それでは、特に気になったトークを3つ紹介させていただきます。

Selenium State of the Union, To Infinity and Beyond Selenium

ステアリング・コミッティの一人であるSimon Stewart氏による基調講演です。 基調講演の中で、Seleniumが普及しプロダクトが成熟していく中で現在のSeleniumに求められているものは、 より分かりやすいドキュメンテーションと分かりやすいAPIだ、と語りました。 これらを実現するために、現役でテストを書いているエンジニアにこそ、OSSであるSeleniumにコントリビュートしてほしいとのことでした。

また気になる話題として、Firefoxのバージョンアップに伴い2017年8月にサポートが切れてしまった Selenium IDEの動向についても言及がありました。 現在、applitoolsの開発者が中心となって、Selenium IDEを公式に作り直しているとのことで期待して待っていて欲しいとのことでした(開発中のSelenium IDEのレポジトリ)。

Keep Your Test Lean

GithubのQAエンジニアによるUIテストにおけるベストプラクティスの話です。 UIテスト自動化が効果的に機能するための要件として、

  • Valuable: 重要で実行頻度の高いテストケースが自動化されている
  • Reliable: UIテストの実行結果を安定させるための仕組みが備わっている
  • Fast: テスト実行時間が短い時間に収まっている

という3点を挙げ、UIテストスイートがこれらを満たすために必要な戦略と工夫について説明されています。

UIテストをやみくもに作成してしまった結果、メンテナンスコストばかり大きくなりメリットが感じられなくなった、という話は日本でもよく聞く話です。 SWETでもこれまでにUIテストを運用していく中で、全く同じような運用上の課題を経験しています(資料)。

特にUIテストをテスト担当者に属人化させず、システム開発者から目に見えるものとするための工夫が興味深いものでした。具体的には、

  • UIテストはシステムの開発言語と同じ言語で記述する
  • テスト対象システムと同じコードベースで管理する
  • テストコードベースの運用ルールをテスト対象システムと同じにする
  • テスト対象UIパーツにカスタムデータ属性を埋め込んでおく

といった工夫が紹介されていました。最後のテスト対象のUIパーツにカスタムデータ属性を埋め込むとは、例えば以下のHTMLにおけるdata-qaがこれに相当します。 このタグがついたUIパーツは、テストで利用されているということが開発者にひと目で分かります。これにより、意図せずテストを壊す変更が入ってしまうことを防止できるというわけです。

# コードは動画中のスライドより引用
<l-popup as="popup" class="has-menu tall-option has-icon cap-option">
  <ul class="popup-menu narrow-menu" data-qa='add-candidate-menu'>
    <li class="option">
      <a on-click="addManually()">
        <view is="icon-edit"></view>
        Add manually
      </a>
    </li>

また、非同期処理が原因でテストが落ちた場合の再実行の仕組みを整える、並列化やヘッドレスブラウザを利用して高速化を行う、といったテスト実行環境の足回りの改善の重要性にも触れています。

UIテスト導入を検討している方に、ぜひ一度視聴してほしい内容でした。

Readable. Stable. Maintainable. E2E Testing @ Facebook

Facebookでテストフレームワーク・テストインフラを担当しているエンジニアの発表です。

Seleniumをそのまま用いたUIテストコードの可読性が低いことは、よく指摘されるSeleniumの問題点ですが、Facebookの中で開発しているテストフレームワークがこの問題をいかに解決しているかについて、具体例を用いて説明しています。

f:id:swet-blog:20171020201722p:plain (写真は動画中より抜粋)

例えば、上記のようなフィード欄のUIテストを考えてみましょう。以下の例では、 Seleniumをそのまま使って書かれたコードが、可読性の低いテストコードになる様子を分かりやすく説明しています。

# コードは動画中のスライドより引用

var feed
// feedがロードされるのが非同期なので、これに対応するためのwhileループ
while (feed == nil) {
  feed = try? driver.findElement(by: id("feed"))
}

// id を直接指定したWebElementの取得
let story = element.findElementBy(by: id("permalink"))[2]

// たまに一回目のクリックがうまくいかないのでretryしている
story.click()
story.click()

// 理由は分からないが、sleepを追加するとテストが動作するので入れられたsleep
sleep(5000)

// id を直接したUIパーツのセレクト
let postText = driver.findElement(by: id("post_text"))

コメントに記載している通り、UIテストにまつわるバッドノウハウがそこかしこに登場しており、本質的にテストしたい内容が、表現されている実装とは言えないものとなってしまっています。 FacebookにおけるUIテストフレームワークの開発の動機の一つは、この問題を解決し、可読性が高くメンテナンスが容易なUIテストコードを実現することだったそうです。

FacebookのUIテストフレームワークにおける問題解決のポイントは、テスト対象画面上のUIパーツの構造とそれに紐づくアクションをクラスとして定義する仕組みを提供していることです。

例えば、前述のフィードのUIは以下のようなクラスとして定義されると説明されています。

# コードは動画中のスライドより引用

// フィードに対するUI定義クラス。
// 子要素と、それに対するアクションを定義していく
class FeedStory: Component {
  let definition = 
           root(storyLocator, "story")
           .withDescendant(
             profilePhotoLocator,
             "profile photo"
           ) // 画像アイコン
           .withDescendant(
             permalinkLocator,
             "permalink"
           ) // その下にフィード詳細へのパーマリンク
           .withDescendant(
             contentLocator,
             "post content"
           ) // その下に投稿内容
           .withDescendant(
              StoryFooter
           ) // その下にフッタ
             // 下部で定義されたStoryFooterクラスを使いまわすことができる

  // UIパーツ内の要素を取得する関数
  getPostText() -> String {
    return getTextOf("post content")
  }

  // UIパーツに対するアクションを定義した関数。
  // 返り値はPageObject Patternの考え方に従い、UIオブジェクトが返される
  openProfile() -> UserProfile {
    return click("profile photo", andGoTo: UserProfile)
  }
  ...
}

// Like, Commentを含むフッターの定義。StoryFeedクラスと同様にUIを定義していく
class StoryFooter {
  // Like, Commentを含むFooterコンポーネントのUI定義クラス
}

このようなUIパーツのクラスを利用することで、 例えば「フィードを投稿して、この投稿したフィードの詳細ページを開き、これにコメントを付ける」といった複雑なシナリオテストも、以下のように簡略に書けるようになるとのことです。

waitFor(LoginScreen)
  .loginAs(user)
  .selectComposer()
  .open()
  .enterText("Test Post")
  .post()
  .selectStoryWithText("Test Post")
  .openPermalink()
  .postComment("Test Comment")

冒頭のバッドノウハウだらけのテストコードと比較して、テストコードの可読性が大きく向上していることが伺えます。

はじめにテスト対象のUIの仕様をクラスとして定義してしまうことで、テストロジックのメンテナンスコストを下げるというアイディアは、UIテストのデザインパターンとしてよく知られたPageObjectパターンの考え方をフレームワークとして一歩推し進めたかたちとなっており、興味深いアプローチだと感じました。

また、トークの中ではテストコードの可読性に関する話題だけでなく、One Worldと呼ばれるモバイルテスト基盤の話など、UIテストに関する多くの技術トピックに触れています。

UIテストの技術的な側面に興味のある方には是非視聴していただきたい内容でした。

終わりに

講演以外で印象に残っていることは、ZaleniumというOSSソフトウェアの開発者と直接会って話ができたことです。 Zaleniumは、Seleniumを用いた複数のUIテストを並列実行することができるテスト基盤を提供するソフトウェアです。 SWETでもZaleniumを利用しており、機能拡張のPRを送っていました。 普段は会えないOSS開発者と直接会って話ができることは海外カンファレンス参加の一つの魅力だと感じました。

Selenium Conferenceは年2回、来年はインドとアメリカでの開催です。 来年4月には第1回目のAppium Conferenceも開催されます。 DeNA Testing Blogでは今後もテスト自動化の最前線の動向について、継続して情報発信していく予定です。

参考資料