Seleniumを適用してWebテストを行う
10334 ワード
Seleniumを使用してWebテストを行うと、次のような問題が発生することがよくあります. name、id、xpathなどのページ要素を大量に使用します.機能の変更、UIの再構築、インタラクティブな改善は、これらの要素に影響し、Seleniumテストが非常に脆弱になります. あまりにも詳細なページ操作は行動の意図を体現しにくく、しばらくするとテストの本来の目的を本当に把握することが難しくなり、Seleniumテストのメンテナンスが難しくなります. 特定のデータの値の取得に依存し、個別のデータが正当でない場合、テストは失敗するが、このような失敗は機能の欠如を識別することができず、Seleniumテストが脆弱になり、メンテナンスが困難になる.
これらの点から直接派生した結果は、新しいテストを絶えず追加し、既存のテストを再構築し、利用することは極めて少ない.実はこれも普通で、ユニットテストのテストが書きすぎて、このような問題もあります.ただし、Seleniumの実行速度は遅く(相対ユニットテスト)、テストが増えるにつれて、実行時間は耐えられないほど増加します.意図不明でメンテナンスが困難なSeleniumテストのセットで、構築(Build)のたびに40分か2時間も殺すことができ、2時間もパソコンの前に座って450個のSeleniumテストが通過するのを待っていた悲惨な経験がありました.そのため、Seleniumテストを合理的かつ効果的に計画することは、特に切実で重要です.現在の比較的有効な方法は、大きく言えば、分野ベースのWebテスト(Domain Based Web Testing)と呼ぶことができ、具体的にはPage Object Patternである.
Page Object Patternには、Driver、Page、Navigator、Shortcutの4つの基本概念があります.Driverは、Selenium、例えばWatir、例えばHttpUnitなどの真の実装メカニズムをテストします.それらはどのように本当に1つのWeb行為を実行するかを知っていて、通常Click、Select、Typeなどのような具体的な行為を表す方法を含む.Pageは、id、name、class、xpathなどの実装の詳細を理解し、ユーザーがどのような操作を行うことができるかを記述するページの構造を理解する特定のページのパッケージです.NavigatorはURLを代表し、ページ操作を経ない直接ジャンプを表す.最後にShortcutはhelperメソッドで、具体的なニーズに応じて決める必要があります.次に、ログインページをテストするという超簡単な例を見てみましょう.
個別のログインページを使用してログインすると、ログイン操作がLoginPageというpage objectにカプセル化される可能性があります.
login_asはビジネスの意味を持つページ動作です.login_Asメソッドでは,page objectがid,xpath,nameなどの情報によるログイン操作を担当する.テストでは、このpage objectを使用することができます.
しかし、Rubyを使用する以上、ruby sugarをいくつか使用しましょう.onメソッドを定義してページ操作を表現する環境を定義します.
その後、page objectのクラス名定数とblock記述を使用して、特定のページで操作できます.
行動方法に加えて、登録ページのウェルカムワードを取得する方法など、page objectでページ情報を取得する方法を定義する必要があります.
このようなテストは、より生き生きと表現することができます.
すべてのページをPage Objectでカプセル化すると、テストとページ構造の結合が効果的に分離されます.テストではlogin_などのasとadd_product_to_cartのようなビジネス行為は、id、nameなどの具体的で変化しやすいページ要素に頼る必要はありません.これらのページ要素が変化すると、対応するpage objectを変更するだけでよいが、既存のテストではあまり変更する必要はない.
行動だけがテストにならないので,行動結果を判断し,いくつかの断言を行う必要がある.上の例を簡単に振り返ると、ログインに成功したとどう判断するかという重要な問題が解決されていないことがわかります.本当にログインページにあることをどうやって知ることができますか?次のコードを呼び出すとどうなりますか?
そのため、page objectにいくつかの断言的な方法を追加する必要があります.少なくとも、各ページには、本当にこのページに達したかどうかを判断する方法があるはずです.このページにいなければ、ビジネス行為はできません.次の方法でLoginPageを変更します.
visibleで?方法では、URLアドレス、特定のUI構造、または要素などの特定のページ要素を判断することによって、実際にページ上に存在するかどうかを判断することができる.私たちが現在表現しているテストの基本構造はonメソッドによって完成されています.私たちはonメソッドに断言を追加して、本当にあるページにあるかどうかを判断し、このページにいなければビジネス操作を行いません.
この方法は神秘的にpageオブジェクトに戻り、ここでは比較的巧みなテクニックです.実際、私たちはpageを利用したいだけです!=nilという事実は、ページのフローを断言します.例えば、次のコードは、ログインに成功したページのフロープロセスを説明します.
この基本的な断言に加えて、カートページでカートが空いているかどうかを判断する断言を定義することができます.
注意しなければならないのは、page objectにTest::Unit::Assertionモジュールを導入したが、断言方法にassert*メソッドは使用されていないことだ.なぜなら、概念的にはpage objectはテストではないからです.いくつかの本当の断言を含んで、1つは概念が混乱して、2つはpage objectをいくつかのシーンに対するtest helperに変えやすくて、後でテストするメンテナンスに不利で、そのため私達は往々にして断言方法を1つの普通の戻り値がbooleanの方法に実現する傾向があります.
テスト意図の表現は、動作の説明だけでなく、次の2つのコードのようなテストデータもあります.
テストは同じものですが、2番目のテストは、登録されたユーザーを使用してログインし、ウェルカムページにアクセスする必要があるというテストの意図をよりよく示しています.このテストを見ると、ユーザー名やパスワードが具体的に何なのか、どのようなテストケースを表現しているのかに関心がありません.これはDataFixtureで実現できます.
ここでは、ユーザーAは登録済みのユーザーであり、ユーザーBは登録されていないユーザーであるというテストケースと具体的なデータに対応しています.ある日、ログインユーザー名をメールボックスに変更する必要がある場合は、対応するテストを変更することなく、DataFixtureモジュールを変更するだけでいいです.
もちろん、より複雑なテストでは、DataFixtureは実際のデータベースまたはRails Fixtureを使用してこのような対応を完了することができますが、全体的な目的は、テストとテストデータの有効性の結合を分離することです.
インタフェース要素と同様に、URLも変化しやすく、意図を表現しにくい要素であるため、Navigatorを使用してテストとデカップリングすることができます.具体的な方法はTest Dataと似ていますが、ここでは説明しません.次の例を示します.
前に私たちはすでに良い基礎を持っていて、Seleniumテストと様々な脆弱で意図不明な要素を分離しました.では、最後にshortcutはケーキの上で最もきれいなクリームにすぎません.きれいな文法を持つhelperを定義します.
次に別のmagicメソッドがあります.
以前のテストは次のように書き換えることができます.
これは結論的なshortcut記述であり、よりbehaviourの書き方もできます.
最後に、テストは検収条件のようなものになります.
とにかくショットカットは良し悪しに関係なく、想像力だけにかかわるもので、ルビーDSLを存分に振りまいましょう!
Seleniumは人を愛し憎むもので、Seleniumを誤って使用すると、敏捷なチーム全体の開発リズムに災難的な影響を与える.しかし、Seleniumを正しく使用する原則もかなり簡単です.脆弱で変化しやすいページ要素とテストを分離することによって、ページの変化がテストに大きな影響を及ぼさないようにします. は、テストデータの意図を明確に指定し、テスト用に具体的なデータを使用しない. 可能な限り、テストの意図を明確に表現し、テストを理解しやすくする.
もちろん、これらの基本原則に従う以外に、page objectや他のdomain based web testing技術を使用するのは良い選択です.これらは、Seleniumテストの規模をより容易に制御し、カバー率と実行効率をよりよくバランスさせ、高品質のWebプロジェクトをより効果的に提供するのに役立ちます.
この文書には、私がここ数週間、Seleniumテストを再構築したときに採用した実際の技術について説明しています.Nick DrewがDriver、Page、Nagivator、Shortcutの階層関係を明確に区別してくれたことに感謝します.それらは私の実践全体の礎を構成しています.Chris Leishmanに感謝して、彼とペアを組んでプログラミングする過程で、彼は私にRuby DSLを鍛えてくれた.またMark RyallとAbhiは、プロジェクトにTest Data Fixtureを導入したのは初めてで、すべての人の仕事が簡単になりました.
作者紹介:
徐昊、ThoughtWorksコンサルタントと敏捷な過程監督.
BJUG
(Beijing Java User Group)と
AgileChina
主な創始者の一人.RSSer(Ruby,Smalltalk & Scheme).現在,主にコンパイル理論の研究とDSL(Domain Specified Language)の実際のプロジェクトにおける応用の普及に力を入れている.彼のブログのアドレスは:
http://www.blogjava.net/raimundox
.
これらの点から直接派生した結果は、新しいテストを絶えず追加し、既存のテストを再構築し、利用することは極めて少ない.実はこれも普通で、ユニットテストのテストが書きすぎて、このような問題もあります.ただし、Seleniumの実行速度は遅く(相対ユニットテスト)、テストが増えるにつれて、実行時間は耐えられないほど増加します.意図不明でメンテナンスが困難なSeleniumテストのセットで、構築(Build)のたびに40分か2時間も殺すことができ、2時間もパソコンの前に座って450個のSeleniumテストが通過するのを待っていた悲惨な経験がありました.そのため、Seleniumテストを合理的かつ効果的に計画することは、特に切実で重要です.現在の比較的有効な方法は、大きく言えば、分野ベースのWebテスト(Domain Based Web Testing)と呼ぶことができ、具体的にはPage Object Patternである.
Page Object Patternには、Driver、Page、Navigator、Shortcutの4つの基本概念があります.Driverは、Selenium、例えばWatir、例えばHttpUnitなどの真の実装メカニズムをテストします.それらはどのように本当に1つのWeb行為を実行するかを知っていて、通常Click、Select、Typeなどのような具体的な行為を表す方法を含む.Pageは、id、name、class、xpathなどの実装の詳細を理解し、ユーザーがどのような操作を行うことができるかを記述するページの構造を理解する特定のページのパッケージです.NavigatorはURLを代表し、ページ操作を経ない直接ジャンプを表す.最後にShortcutはhelperメソッドで、具体的なニーズに応じて決める必要があります.次に、ログインページをテストするという超簡単な例を見てみましょう.
1. Page Object
個別のログインページを使用してログインすると、ログイン操作がLoginPageというpage objectにカプセル化される可能性があります.
class LoginPage
def initialize driver
@driver = driver
end
def login_as user
@driver.type 'id=...', user[:name]
@driver.type 'xpath=...', user[:password]
@driver.click 'name=...'
@driver.wait_for_page_to_load
end
end
login_asはビジネスの意味を持つページ動作です.login_Asメソッドでは,page objectがid,xpath,nameなどの情報によるログイン操作を担当する.テストでは、このpage objectを使用することができます.
page = LoginPage.new $selenium
page.login_as :name => 'xxx', :password => 'xxx'
しかし、Rubyを使用する以上、ruby sugarをいくつか使用しましょう.onメソッドを定義してページ操作を表現する環境を定義します.
def on page_type, &block
page = page_type.new $selenium
page.instance_eval &block if block_given?
end
その後、page objectのクラス名定数とblock記述を使用して、特定のページで操作できます.
on LoginPage do
login_as :name => 'xxx', :password => 'xxx'
end
行動方法に加えて、登録ページのウェルカムワードを取得する方法など、page objectでページ情報を取得する方法を定義する必要があります.
def welcome_message
@driver.get_text 'xpath=...'
end
このようなテストは、より生き生きと表現することができます.
on LoginPage do
assert_equal 'Welcome!', welcome_message
login_as :name => 'xxx', :password => 'xxx'
end
すべてのページをPage Objectでカプセル化すると、テストとページ構造の結合が効果的に分離されます.テストではlogin_などのasとadd_product_to_cartのようなビジネス行為は、id、nameなどの具体的で変化しやすいページ要素に頼る必要はありません.これらのページ要素が変化すると、対応するpage objectを変更するだけでよいが、既存のテストではあまり変更する必要はない.
2. Assertation
行動だけがテストにならないので,行動結果を判断し,いくつかの断言を行う必要がある.上の例を簡単に振り返ると、ログインに成功したとどう判断するかという重要な問題が解決されていないことがわかります.本当にログインページにあることをどうやって知ることができますか?次のコードを呼び出すとどうなりますか?
$selenium.open url_of_any_page_but_not_login
on LoginPage {...}
そのため、page objectにいくつかの断言的な方法を追加する必要があります.少なくとも、各ページには、本当にこのページに達したかどうかを判断する方法があるはずです.このページにいなければ、ビジネス行為はできません.次の方法でLoginPageを変更します.
LoginPage.class_eval do
include Test::Unit::Asseration
def visible?
@driver.is_text_present(...) && @driver.get_location == ...
end
end
visibleで?方法では、URLアドレス、特定のUI構造、または要素などの特定のページ要素を判断することによって、実際にページ上に存在するかどうかを判断することができる.私たちが現在表現しているテストの基本構造はonメソッドによって完成されています.私たちはonメソッドに断言を追加して、本当にあるページにあるかどうかを判断し、このページにいなければビジネス操作を行いません.
def on page_type, &block
page = page_type.new $selenium
assert page.visible?, "not on #{page_type}"
page.instance_eval &block if block_given?
page
end
この方法は神秘的にpageオブジェクトに戻り、ここでは比較的巧みなテクニックです.実際、私たちはpageを利用したいだけです!=nilという事実は、ページのフローを断言します.例えば、次のコードは、ログインに成功したページのフロープロセスを説明します.
on LoginPage do
assert_equal 'Welcome!', welcome_message
login_as :name => 'xxx', :password => 'xxx'
end
assert on WelcomeRegisteredUserPage
この基本的な断言に加えて、カートページでカートが空いているかどうかを判断する断言を定義することができます.
def cart_empty?
@driver.get_text('xpath=...') == 'Shopping Cart(0)'
end
注意しなければならないのは、page objectにTest::Unit::Assertionモジュールを導入したが、断言方法にassert*メソッドは使用されていないことだ.なぜなら、概念的にはpage objectはテストではないからです.いくつかの本当の断言を含んで、1つは概念が混乱して、2つはpage objectをいくつかのシーンに対するtest helperに変えやすくて、後でテストするメンテナンスに不利で、そのため私達は往々にして断言方法を1つの普通の戻り値がbooleanの方法に実現する傾向があります.
3. Test Data
テスト意図の表現は、動作の説明だけでなく、次の2つのコードのようなテストデータもあります.
on LoginPage do
login_as :name => 'userA', :password => 'password'
end
assert on WelcomeRegisteredUserPage
registered_user = {:name => 'userA', :password => 'password'}
on LoginPage do
login_as registered_user
end
assert on WelcomeRegisteredUserPage
テストは同じものですが、2番目のテストは、登録されたユーザーを使用してログインし、ウェルカムページにアクセスする必要があるというテストの意図をよりよく示しています.このテストを見ると、ユーザー名やパスワードが具体的に何なのか、どのようなテストケースを表現しているのかに関心がありません.これはDataFixtureで実現できます.
module DataFixture
USER_A = {:name => 'userA', :password => 'password'}
USER_B = {:name => 'userB', :password => 'password'}
def get_user identifier
case identifier
when :registered then return USER_A
when :not_registered then return USER_B
end
end
end
ここでは、ユーザーAは登録済みのユーザーであり、ユーザーBは登録されていないユーザーであるというテストケースと具体的なデータに対応しています.ある日、ログインユーザー名をメールボックスに変更する必要がある場合は、対応するテストを変更することなく、DataFixtureモジュールを変更するだけでいいです.
include DataFixtureDat
user = get_user :registered
on LoginPage do
login_as user
end
assert on WelcomeRegisteredUserPage
もちろん、より複雑なテストでは、DataFixtureは実際のデータベースまたはRails Fixtureを使用してこのような対応を完了することができますが、全体的な目的は、テストとテストデータの有効性の結合を分離することです.
def get_user identifier
case identifier
when :registered then return User.find '....'
end
end
4.Navigator
インタフェース要素と同様に、URLも変化しやすく、意図を表現しにくい要素であるため、Navigatorを使用してテストとデカップリングすることができます.具体的な方法はTest Dataと似ていますが、ここでは説明しません.次の例を示します.
navigate_to detail_page_for @product
on ProductDetailPage do
....
end
5. Shortcut
前に私たちはすでに良い基礎を持っていて、Seleniumテストと様々な脆弱で意図不明な要素を分離しました.では、最後にshortcutはケーキの上で最もきれいなクリームにすぎません.きれいな文法を持つhelperを定義します.
def should_login_successfully user
on LoginPage do
assert_equal 'Welcome!', welcome_message
login_as user
end
assert on WelcomeRegisteredUserPage
end
次に別のmagicメソッドがあります.
def given identifer
words = identifier.to_s.split '_'
eval "get_#{words.last} :#{words[0..-2].join '_'}"
end
以前のテストは次のように書き換えることができます.
def test_should_xxxx
should_login_successfully given :registered_user
end
これは結論的なshortcut記述であり、よりbehaviourの書き方もできます.
def login_on page_type
on page_type do
assert_equal 'Welcome!', welcome_message
login_as @user
end
end
def login_successfully
on WelcomeRegisteredUserPage
end
def given identifer
words = identifier.to_s.split '_'
eval "@#{words.last} = get_#{words.last} :#{words[0..-2].join '_'}"
end
最後に、テストは検収条件のようなものになります.
def test_should_xxx
given :registered_user
login_on LoginPage
assert login_successfully
end
とにかくショットカットは良し悪しに関係なく、想像力だけにかかわるもので、ルビーDSLを存分に振りまいましょう!
結論
Seleniumは人を愛し憎むもので、Seleniumを誤って使用すると、敏捷なチーム全体の開発リズムに災難的な影響を与える.しかし、Seleniumを正しく使用する原則もかなり簡単です.
もちろん、これらの基本原則に従う以外に、page objectや他のdomain based web testing技術を使用するのは良い選択です.これらは、Seleniumテストの規模をより容易に制御し、カバー率と実行効率をよりよくバランスさせ、高品質のWebプロジェクトをより効果的に提供するのに役立ちます.
この文書には、私がここ数週間、Seleniumテストを再構築したときに採用した実際の技術について説明しています.Nick DrewがDriver、Page、Nagivator、Shortcutの階層関係を明確に区別してくれたことに感謝します.それらは私の実践全体の礎を構成しています.Chris Leishmanに感謝して、彼とペアを組んでプログラミングする過程で、彼は私にRuby DSLを鍛えてくれた.またMark RyallとAbhiは、プロジェクトにTest Data Fixtureを導入したのは初めてで、すべての人の仕事が簡単になりました.
作者紹介:
徐昊、ThoughtWorksコンサルタントと敏捷な過程監督.
BJUG
(Beijing Java User Group)と
AgileChina
主な創始者の一人.RSSer(Ruby,Smalltalk & Scheme).現在,主にコンパイル理論の研究とDSL(Domain Specified Language)の実際のプロジェクトにおける応用の普及に力を入れている.彼のブログのアドレスは:
http://www.blogjava.net/raimundox
.