AppiumにおけるPOパターンの応用(1)——フレームワークの初期設計

  1. PageObject パターン

従来のテストケースの課題

1)UIの変更に耐えられず、変更ごとに多数のテストケースを修正する必要がある
2)大量の共通コード(driver, find, click)が存在する
3)ビジネスロジックのテストケースを明確に表現できない

PageObject パターンの原則

メソッドの役割

  • UIが提供する機能を共通メソッドとして表現する
  • メソッドは他のPageObjectを返すか、アサーションに使用するデータを返す
  • 同じ操作でも結果が異なる場合は異なるメソッドとしてモデル化する
  • メソッド内でアサーションを行わない

フィールドの役割

  • ページ内部の要素を外部に公開しない
  • UIのすべての要素をモデル化する必要はない

POパターンによるカプセル化の主要構成要素

  • Pageオブジェクト:ページの機能をカプセル化
  • Driverオブジェクト:Web、Android、iOS、APIなどへのアクセスを担当
  • テストケース:Pageオブジェクトを呼び出して業務ロジックを実行し、アサーションを行う
  • データ管理:設定ファイルとデータ駆動型テストのためのデータ
  • Utils:基本的なフレームワークの不足を補完する機能群

2. テストフレームワークの構築

POは設計思想であり、まずは全体像を描いてから詳細を埋めていく。企業微信アプリの実装を例に説明する。

1)pageパッケージを作成し、basepageモジュールを追加し、ベースクラスを作成する

basepage.py

from appium.webdriver.webdriver import WebDriver

class BasePage():
    def __init__(self, driver: WebDriver = None):
        self._driver = driver

2)appモジュールを作成し、AppクラスをBasePageから継承する

from testenter.page.basepage import BasePage
from testenter.page.main import Main

class App(BasePage):
    def start(self):
        return self
    
    def stop(self):
        pass
    
    def restart(self):
        pass
    
    def main(self):
        return Main(self._driver)

以下の点に注意:

  1. startメソッドがreturn selfを返す理由:この戻り値がない場合、mainメソッドを呼び出すことができない
  2. startメソッドをBasePageに移動してもよいが、今後のWeb対応を考慮して、別モジュールに分ける方が設計的に柔軟性がある
  3. driverを渡しているが、BasePageから継承されるdriverNoneなので重複は発生しない

3)Mainページに遷移する際、mainモジュールを作成する

from testenter.page.addresslistpage import AddresslistPage
from testenter.page.basepage import BasePage

class Main(BasePage):
    def goto_message(self):
        pass
    
    def goto_addresslist(self):
        return AddresslistPage(self._driver)
    
    def goto_workbench(self):
        pass
    
    def goto_myprofile(self):
        pass

ページ名が不明な場合は、adb shell dumpsys window | grep mCurrentで現在の画面名を取得できる。

4)addresslistpageモジュールを作成し、メンバー追加ボタンをクリックして「招待メンバー」ページへ遷移する。click_addmemberメソッドを定義する

from testenter.page.basepage import BasePage

class AddresslistPage(BasePage):
    def click_addmember(self):
        from testenter.page.memberinvitepage import MemberInvitePage
        return MemberInvitePage(self._driver)

5)memberinvitepage.pyを作成し、「手動入力」ボタンをクリックして連絡先追加ページへ遷移するclick_memualaddメソッドを定義する

from testenter.page.basepage import BasePage

class MemberInvitePage(BasePage):
    def click_memualadd(self):
        from testenter.page.contactaddpage import ContactAddPage
        return ContactAddPage(self._driver)

    def click_back(self):
        from testenter.page.addresslistpage import AddresslistPage
        return AddresslistPage(self._driver)

6)contactaddpage.py(連絡先追加ページ)を作成し、名前、電話番号、性別を入力して保存し、「メンバー追加」ページに戻るclick_saveメソッドを定義する

class ContactAddPage(BasePage):
    def input_name(self):
        return self

    def set_gender(self):
        return self

    def input_phone(self):
        return self

    def click_save(self):
        from testenter.page.memberinvitepage import MemberInvitePage
        return MemberInvitePage(self._driver)

7)ページ構造が整ったので、テストケースを作成する。test_caseパッケージを作成し、test_contact.pyに連絡先追加テストケースを記述する

from testenter.page.app import App

class TestAddContact():
    def setup(self):
        self.main = App().start().main()

    def test_addtact(self):
        self.main.goto_addresslist().click_addmember().click_memualadd(). \
            input_name().input_phone(). \
            set_gender().click_save().click_back()

⚠️⚠️❗️重要なポイント:なぜ一部のモジュールはメソッド内でのみインポートしているのか?最初はモジュールの先頭でインポートしていたがエラーが発生した。エラーの原因は循環インポートであった。

循環インポートとは、あるページが次のページをインポートし、その逆もまた同様に起こる状態である。Pythonはこのような循環参照に対応していない。

解決策としては、使用時にインポートを行う(ローカルインポート)ことで回避できる。これにより、初期化時の循環依存を避けることができる。

タグ: appium PageObjectパターン テストフレームワーク 自動テスト Python

6月6日 22:40 投稿