DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

機械学習を使ってUI自動テストをサイト間で再利用する

SWETの薦田(@toshiya-komoda)です。 今回は第3回目の記事で言及させていただいた機械学習とUIテストに関して実験的に進めている技術開発について紹介させていただこうと思います。 この記事で紹介している内容の実装はGitHubにアップロードしていますので、もし興味がある方はこちらも覗いてみていただければと思います。

こちらはTensorFlow Advent Calender 2017第7日目の記事にもなっています。機械学習の実装の中でKerasを用いてます。

とりあえずデモ

最初に以下のデモ動画をご覧いただきたいです。会員登録フォームに対する自動テストのデモです。各入力欄に適切な情報を入力しつつ、パスワード欄にだけ'weak'という不正なパスワード文字列を入力して、バリデーションで弾かれることを確認するテストです。デモでは入力欄に値を埋める部分を、Chrome Extensionを用いて自動化しています。動画は2倍速にしています。

youtu.be

一見何の変哲もないテストの動画です。しかし、このデモの中では「ページ中の入力欄の意味を機械学習を用いて判別し、適切な値を入力する」ことを行っています。言い換えると、各サイト固有のDOM Elementを扱うロジックが存在せず、入力欄へ正しくデータを埋めるロジックがサイトに依存せず流用可能となっています。

以下、この技術の詳細についてUIテスト開発の課題のおさらいも含めて、順に説明させていただきます。

UIテストにおけるDOM操作とその課題

まずは、そもそものUIテスト開発における課題のおさらいです。

一般的なUIテストケースのテスト実装は、

  • あらかじめテスト対象サイトのHTMLにテストで使うためのIDを各UI Elementに埋め込んでおく
  • テストコード側で埋め込んだIDを用いて、テストロジックを記述する

というフローになります。この結果として、

  • テスト実装者がテスト対象ページのDOMの詳細を理解しなければならない
  • テスト対象ページのDOMの変更で容易にテストが壊れる
  • テストロジックがテスト対象ページのDOMに密結合するため、テストコードの再利用性が乏しい

などの問題が発生します。一般的なウェブアプリケーションにとって、UIの変更頻度が比較的高いことを考えるとこれらのUIテストコードの特性が、テストの開発コスト・運用コストを増大させてしまうことは想像できるかと思います。

UIテストコードとテスト対象のDOMを疎結合にする

では、どういったことができると嬉しいのかについて簡単な例で考えてみます。今、サイトAの会員登録ページと、サイトBの会員登録ページがあるとします。2つのサイトで同じテストケース「会員登録ページのフォームを埋めて、次の画面に進む」を自動化したいとします。サイトA、サイトBでもちろんフロントエンドのコードは異なるため、現状ではそれぞれのページにカスタマイズされたUIテストコードを個別に書くしかありません。

f:id:swet-blog:20171205165042p:plain

ここで、もし個々のサイトをUIテスト実装で必要な程度にうまく抽象化してくれるレイヤが存在したとしたらどうでしょう?

この状況を表現したのが以下の図になります。

f:id:swet-blog:20171205165100p:plain

同じテストケースを自動化する場合のみとはいえ、サイトA・サイトBに対して、抽象レイヤを挟むことでテストコードを再利用できます。また、この抽象レイヤがUIの細かい詳細を吸収してくれるのであれば、細かいUIの変更によってテストが壊れる問題も緩和できると期待できます。

ここで、具体的にどのような操作をどの程度の粒度で抽象化すればよいか、という問題はテストケースに依存します。このため、どのように抽象化を行うかを一般的に議論することは難しい問題といえますが、冒頭のデモの例では「該当ページ中の入力欄が入力を求めている値の意味」を抽象化した例といえます。

もちろん、このようなアプローチではサイト固有の仕様をテストすることは難しく、UI自動テストの全てのニーズを捉えることは難しいでしょう。しかし、限定的なテストケースに限られるとしても、UIテストの導入・メンテナンスコストを大きく削減できることからこのようなアプローチが有効であるケースはあると考えています。

機械学習を用いてDOM Elementの意味を推定する

それでは、UIテスト設計における抽象化の問題をどのように機械学習の問題に置き換えたのかについて説明していきます。 冒頭のデモは以下の論文で述べられている手法をベースにしつつ、細かいところを自分たちで試行錯誤しつつ作成したデモです。

J. W. Lin, F. Wang and P. Chu, "Using Semantic Similarity in Crawling-Based Web Application Testing," 2017 IEEE International Conference on Software Testing, Verification and Validation (ICST), Tokyo, 2017, pp. 138-148.
doi: 10.1109/ICST.2017.20

URL: http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7927970&isnumber=7927908

この論文の内容をかいつまんで述べると、

  • DOM Elementを構成するHTMLの文字列から当該Elementの意味あるいは役割を高い精度で推定できる
    • メールアドレスの入力欄、会員規約の同意チェックなどの意味や役割
  • 機械学習でいうところの多値分類問題として問題を定式化し、解くことで実現される
  • 具体的には、自然言語に対して開発されてきた機械学習の手法を、よしなにHTMLに対して適用してやるとうまくいく

というものです。HTMLの文字列を見てその意味やページ上での役割を推定するということは、まさに前項で説明したDOMの抽象化レイヤの役割そのものです。

DOM Element分類問題の定式化

さて、前述の先行研究をベースにしつつ、DOM Elementの分類問題を以下のように定式化しました。

登場人物は以下のようになります。

登場人物 意味
ウェブページ 同じ機能を提供するテストページの集合。 複数のサイトにまたがって存在している。
ElementのHTML テストケース内で操作対象となるDOM ElementのHTML文字列。分類問題の入力値となる。
トピックラベル テスト対象としてElementの意味を表すラベル。分類問題の出力値となる。
トピックラベル集合 ありうるトピックラベル全体の有限集合。 抽象化したいテストケースごとに定義されます。

ここでDOM Element HTMLを入力として、トピックラベルを出力とする多値分類器が作れれば良いことになります。 定式化の具体例を用いた説明を記事の最後に補足として付記していますので、詳細に興味のある方はそちらも御覧ください。

教師データの半自動的な収集

さて、前節で教師ありの分類問題としての定式化ができました。次は実際に意味のある教師データを集めることが必要です。要するに様々なウェブサイトから、テストで操作したいDOM Elementに対応するHTML文字列を抽出して、これらに適切なトピックラベルをつけていくという作業を行う必要があります。

この問題を解決するために、以下のようなアプローチを取りました。

  • 最初に小さなサンプル数で良いので手動で教師データを作る。
  • この教師データを用いてトピック分類器をつくる。
    • この時点では、推定精度は高くない。
  • このトピック分類器を用いて、半自動的に教師データを収集するスクリプトを作る。
  • 半自動的に作られた教師データの正誤判定のみ人間が行う。

このような収集の半自動化を行うことで、素直に手動で教師データを作成する場合に比べて約10倍の速度で学習データを集められるようになりました。

判定精度の評価実験

最後にトピックラベルの推定精度を評価してみます。冒頭のデモで用いている「会員登録ページにおいて入力フォームに値を入力する」というユースケースに絞って実験を行っています。

教師データ

コーパスとなるウェブサイトは、Google検索に「会員登録」と入力して出てきた200サイト分の会員登録ページを用いています。会員情報を実際に入力するページへの導線はサイトごとに異なるので、ここは手動で入力ページまでいってデータを集めました。

以降の評価では学習データの中で、同一のトピックラベルを持つDOM Elementが10個以上存在するもののみを用いて評価を行っています。同じトピックラベルを持つデータが少なすぎるDOM Elementに対して、意味のある学習を行うことはそもそも難しいからです。合計で2610個のDOM Elementを用いて学習を行い、652個のDOM Elementを用いてモデルのバリデーションを行っています。

用いた教師データはトレーニングスクリプトと合わせて、冒頭のGitHub上で公開しています。 なお、冒頭のデモ中ではテスト対象ページの教師データを取り除いた上で学習させたモデルを用いています。

分類器

解くべき問題は自然言語処理を用いた多値分類問題です。比較的扱いが簡単だった以下の2つの分類器を用いました。

LSIを用いた次元圧縮 + ロジスティック回帰(以下、LSI)

  • ライブラリにgensimsklearnを用いて実装しました。
  • 圧縮後の次元数は500です。

単純なニューラルネットワーク(以下、NN)

  • ライブラリにkerasを用いて実装しました。
  • 全結合層を1つ重ねた場合(NN1)と2つ重ねた場合(NN2)で評価しています。
  • 学習時のエポック数はNN1、NN2ともに決め打ちで800epochs分学習させています。
    • 目分量ですが800epochs程度でだいたい学習が収束したように見えていました

今回用いたニューラルネットワークはもっともシンプルといって良い単純なネットワークかと思いますが、kerasのmodel.summary()の出力を記載しておきます。

全結合1層 (NN1)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 18)                54882     
=================================================================
Total params: 54,882
Trainable params: 54,882
Non-trainable params: 0

全結合2層 (NN2)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 100)               304900    
_________________________________________________________________
dense_2 (Dense)              (None, 18)                1818      
=================================================================
Total params: 306,718
Trainable params: 306,718
Non-trainable params: 0

推定精度

結果は以下のようになりました。 テストセットをランダムに5回ずつ変えて平均を計測しています。

分類器 Accuracy
LSI 90.8 %
NN1 92.5 %
NN2 92.7 %

今、トピックラベルの取りうる値は18通りあり、ランダムに予測した場合の正答率は5.6%程度となることを考えると悪くない精度が出ていると考えられます。

TensorBoardで表示したニューラルネットワークの学習曲線は以下のようになっていました。

NN1(全結合1層)の学習過程(TensorBoard)

f:id:swet-blog:20171206221559p:plain

NN2(全結合2層)の学習過程(TensorBoard)

f:id:swet-blog:20171206220823p:plain

全結合2層の学習では過学習が起きてしまっているように見えるなど、ニューラルネットワークに関してはもう少しチューニングの余地がありそうですが今回は深追いしていません。

最も精度の良かった全結合2層の場合に、具体的にどのような場合に推定が失敗しているかを見てみましょう。

失敗例: input tagのHTML文字列だけでは情報が不足しているケース

DOM HTML <input id="kiad2" name="kiad2" type="text" maxlength="80" value="">
単語列 ['kiad', '2', 'text', 'kiad', '2']
推定値 userName
正解 address

このケースではサイトの変数名の付け方に正解を予測するための情報がほとんどなく、人間が見たとしてもこのHTMLが住所の入力欄であることを判定するのは難しいといえます。例えば、入力欄のHTMLだけでなく関連するDOM要素(labelなど)を入力として与えるなど入力の定式化自体を考え直さないといけないと考えられます。

失敗例: 分類器の精度が向上すれば良くなるかもしれないケース

DOM HTML <input type="text" name="d" id="d" placeholder="オナマエ" class="sizeL" style="transition: outline 0.55s linear; outline: none; outline-offset: -2px;">
単語列 ['d', 'text', 'd', 'オナマエ']
推定値 mailSubscriptionCheck
正解 userName

今回は教師データ中に含まれる単語のみから辞書を作成し、単語列をベクトルに変換しています。このため、辞書内の単語数は3000強しかありません。従って、このケースで現れている「オナマエ」という日本語の文字列が「名前」と同じ意味であることを判定することができなかったと考えられます。例えばWikipediaなど一般的なコーパスから作成した単語の分散表現を組み合わせることで精度を改善できる可能性があると考えています。

その他、予測すべきトピックラベルが教師データ中にあまり存在せず、教師データが足りていないように見えるケースもありました。この点については、教師データをさらに増やすことで精度を向上させられる可能性があると考えています。

課題・将来展望

今回のデモおよび推定精度の評価では

  • 会員登録ページの入力フォームを埋めるというテストケース。
  • HTMLをレイアウト情報として利用するウェブページがテスト対象。

という2つの条件を仮定しています。一方で実用的にはより広い範囲のユースケースでの適用可能性を検討する必要があります。

まず、より複雑なテストケースに拡張できるかどうかを検討する必要があります。例えば、

  • 複数のページにまたがるテストケースを扱う
  • シングルページアプリケーションのようにより複雑な構造を持つページを扱う
  • テストの成功・失敗を判定するロジックを学習させる

といった課題が挙げられます。

また、この手法自体の適用範囲はHTMLに限定されるものではありません。このため、HTMLとは異なるフォーマット(XMLなど)を用いてレイアウト情報を管理しているモバイル・アプリケーションの世界への適用も試してみたい課題となります。

まとめ

今回は、機械学習を用いてUIテストコードをサイトをまたいで再利用する技術について紹介させていただきました。課題欄でも述べた通り、まだまだ検討すべき課題が多くある状態です。SWETではこうした技術的チャレンジを楽しめる方を随時募集しています。

参考文献

補足: 分類問題の具体例による説明

仮想的で簡単なフォームの例で考えると、以下のようになります。

ウェブページ(コーパス)

# サイトA
<form>
  メールアドレス: <br>
  <input type="email" name="user_email" placeholder="please input your email"> <br>
  パスワード: <br>
  <input type="password" name="user_pass" placeholder="please input your password"><br>
  <input type="submit" id="confirm_button" value="確認画面へ">
</form>
# サイトB
<form>
  メールアドレス: <br>
  <input type="email" name="email_address"><br>
  パスワード: <br>
  <input type="password" name="password"><br>
  パスワード(確認用): <br>
  <input type="password" name="password_confirmation"><br>
  規約に同意する <br>
  <input type="radio" name="service_term_confirmation"><br>
  <input type="submit" value="登録">
</form>

トピックラベル集合。このリストは抽象化したいテスト対象ページに合わせて、手動で作成する必要があります。

"email_input"
"password_input"
"password_confirm_input"
"service_term_input"
"form_submit_button"

ElementのHTML。

# サイトA
1. <input type="email" name="user_email" placeholder="please input your email">
2. <input type="password" name="user_pass" placeholder="please input your password">
3. <input type="submit" id="confirm_button" value="確認画面へ">
# サイトB
4. <input type="email" name="email_address">
5. <input type="password" name="password">
6. <input type="password" name="password_confirmation">
7. <input type="radio" name="service_term_confirmation">
8. <input type="submit" value="登録">

トピックラベルの列。この値がいわゆる正解データ、教師データと呼ばれるものになります。各値はトピックラベル集合の要素のどれか1つが入ります。また、番号が対応するElementのHTMLにひも付きます。

# サイトA
1. "email"
2. "password"
3. "form_submit"
# サイトB
4. "email"
5. "password"
6. "password_confirmation"
7. "service_term"
8. "form_submit"

HTML文字列の単語列への分解

さらにElementのHTMLは、HTMLパーサを用いて意味のある単語のみを抽出した単語列として扱います。この後、日本語が含まれている場合は形態素解析も行います。先の例のElementのHTMLの列は以下のように変換されます。

ElementのHTMLの単語列

1. ["email", "user", "email", "please", "input", "your", "email"]
2. ["password", "user", "pass", "please", "input", "your", "password"]
3. ["submit", "confirm", "button" "確認", "画面", "へ"]
4. ["email", "email", "address"]
5. ["password", "password"]
6. ["password", "password", "confirmation"]
7. ["radio", "service", "term", "confirmation"]
8. ["submit", "登録"]

この形までくれば、あとは完全に自然言語処理の世界の問題として扱うことができます。

ラベル付けについての補足

この手法ではラベル付けに任意性が存在します。ユースケースごとにモデルを学習することを想定しているので当然といえば当然です。これは最終的にどこまでテストをしたいのかに依存して変わるものです。

今回は会員登録フォームの入力欄について、

  1. 純粋に入力される値の意味だけで分類した場合 (粗い分類)
  2. 意味に加えて入力フォーマットの情報も含めて分類した場合 (細かい分類)

の2種類のラベル付けに対して評価を行っています。これは、例えば以下のように3つの欄に分かれた電話番号の入力欄が与えられたときに

1. <input type="text" id="tel1" name="tel1" value="" autocomplete="off"> # xxx-yyyy-zzzz形式のxxx
2. <input type="text" id="tel2" name="tel2" value="" autocomplete="off"> # xxx-yyyy-zzzz形式のyyyy
3. <input type="text" id="tel3" name="tel3" value="" autocomplete="off"> # xxx-yyyy-zzzz形式のzzzz

対応するトピックラベルを

1. 'telephone_number'
2. 'telephone_number'
3. 'telephone_number'

とするか、

1. 'telephone_number_first'
2. 'telephone_number_middle'
3. 'telephone_number_last'

とするかでトピックラベルの付け方を変えるということです。後者のラベル付けの方は分類がより詳細なため、分類問題としては難しくなります。

前者のような粗いラベル付けでも、テスト実装時の工夫でテストを実装することは可能です。例えば、'telephone_number'と推定されたElementの数を数えて値を分割して入力するなどが考えられます。

本文中の評価では、冒頭のデモを作成するのに粗いラベル付けで十分だったため粗い方のラベル付けを用いた場合の精度を評価として示しています。 参考までですが、この節で説明した後者の細かいラベル付の場合の推定精度をいかに示します。

分類器 Accuracy (テストセットをランダムに10回変えて平均を計測)
LSI 78.2 %
NN 82.7 %

精度は前者の粗いラベル付けの場合と比べて10%程度低くなり、今回用いたものよりもさらに洗練された分類器が必要になる事がわかっています。