設計原則「関心の分離」からPage Object Modelを学ぼう

-- Views

December 06, 25

スライド概要

https://testautomationresearch.connpass.com/event/361747/

profile-image

大阪のテスターです

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

設計原則「関心の分離」から Page Object Modelを学ぼう ソフトウェアテスト自動化カンファレンス2025 2025.12.6 Short Session(15min) やまずん Dirty Tester バキバキQA

2.

Page Object Model を採用している現場でよくみる シナリオ 2

3.

敏腕のテストリーダー Webやし、テスト 自動化やから、と りあえずPage Object Modelやっ ときゃええやろ ページをクラスに まとめたらええん やろ? 3

4.

力を得たテストエンジニア 生成AIでコードが わからなくても、 テスト自動化がで きます! 「Page Object Model」にしてく ださいって分離し て書いてくれまし た! 4

7.

結果起こりうること 何をテストしているかわからない たくさんのファイルを修正する 修正しても修正しても終わらない 7

11.

私たちは設計原則を理解する必要がある ⚫ 生成AIで「誰でも自動テストコードが書ける」ようになりました ね ⚫ その分、変なテストコードが増えてきた ⚫ テストコードのあるべき姿について考えることが多くなりました やまずんだって 本当に理解して いんのか? あるべき姿、それは普遍的な 「設計原則」にヒントがあると思いました 11

12.

この発表の対象 ⚫ 生成AIでコードを書くようになった人たち ⚫ 生成AIが出してくれた分離されたコードをよくわか らずにAcceptしている人たち ランプの精も対 象だよね ⚫ それをレビューして違和感を持っているけどうまく 言語化できていない人たち 12

13.

この発表のゴール ⚫設計原則があると知っている し、学ぼうと思ってる ⚫AIが出してきたテストコード に対して、批判的にレビュー ができる 13

14.

やまずんとは ⚫ バキバキQA/Dirty Tester ⚫ 外資系SaaSのQAエンジニア ⚫ 大阪のテスター ⚫ JSTQB TAEを保有せしもの ⚫ 所属(コミュニティ) ⚫ testingOsaka ファウンダー ⚫ スクラム祭り 実行委員 ⚫ JaSST nanoお世話係軍団 ⚫ Tokyo Test Festの実行委員(予定) ⚫ バキバキQAチャンネル ⚫ まちがいさがし。 14

15.

アドベントカレンダーでこういうのも書いています 15

16.

この発表の注意 ⚫ やまずんは自分のことしか代表しません ⚫ 所属する団体や世の中のQA・テスターの見解を代表するものではありません ⚫ Page Object Model、長いので今後はPOM(ポム)と略します ⚫ サンプルコードを記載していますが、全てのベストプラクティスを網羅的 に実現しているわけではありません ⚫ 理解を促進させるために、あえて省略している部分もあります ⚫ 生成AIが生成するコードについて批判的な見解を示していますが、2025年 12月時点の見解であることにご留意ください ⚫ (資料を後で見る人向け)スライドに記載された内容が全てではなく、口 頭で補足した内容もあります 16

17.

設計原則 17

18.

設計原則・設計とは 原則(principle)は,「総合的かつ基本的な規範(law),教義 (doctrine),または前提(assumption)」である. ソフトウェア設計原則は,数多くの様々なソフトウェア設計手法 および概念に対する基礎となる,主観念(Key notions)である. 用語の定義って むずかしいねえ SWEBOK V3.0 p.25 この場で理解しておくこと ⚫ 設計は、ソフトウェアによって提供するソリューションの青写真を考えたり提供したりする ⚫ 設計原則は、設計を考える際に基礎となる考え方 ⚫ 自動テストは言わずもがな、「ソフトウェア」(システム)である 18

19.

本質的な原則:関心の分離 関心(concern)は,「ソフトウェア設計に関与すると考えて形成 される思考領域」(中略) 関心を分離することによって,(中略)複雑性をマネージするた めの手段を手にすることができるようになる 右上はゴマの領 域だよ SWEBOK V3.0 p.26 この場で理解しておくこと ⚫ 関心は、考えたり扱う範囲 ⚫ 分離によって、関心を考えたり扱うものを「できるだけ小さくする」ことができる ⚫ 多くの設計技法のパターンは「関心の分離」を目指している ⚫ POMも例外ではない 19

20.

単一責務の原則とは モジュールを変更する理由はたったひとつであるべきである。 モジュールはたったひとつのアクターに対して責任を負うべきである。 Clean architecture : 達人に学ぶソフトウェアの構造と設計 p.81-82 この場で理解しておくこと ⚫ ごまが変更され たのもひとつの 理由によるもの なんだ 単一責務の原則は、「コードの変更」という場面でのコードの凝集性について述べたもの ⚫ よくある誤解:「モジュールでやることはひとつであるべき」 ⚫ 単一責務の原則は関心の分離を実現したものですが、同じものとして語られることもあります ⚫ アクターは、変更要求の源泉くらいで捉えたらいい(この場では一旦ね) ⚫ POMにおける単一責務の原則は、「テスト対象の実装による変更」と、「自動テストに対する 要求の変更」で責務を分けたという理解でOK 20

21.

自動テストへの適用 21

22.

今回の自動化のコンテキスト ⚫ Webサイトのテスト自動化 ⚫ 主張は関係ないがサンプルコードは以下 ⚫ 言語はPythonっぽいもの Pythonが一番読 む量少ないから ねえ ⚫ フレームワークはPlaywrightぽいもの 22

23.

自動テストにおける典型的な保守性の課題 仕様変更に対する対応 このくらい僕 だって知ってる よ テスト自体の理解が困難 運用した際のメンテナンスコストの増大 23

24.

Page Object Model(POM)とは Within your web app’s UI, there are areas where your tests interact with. A Page Object only models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix needs only to be applied in one place. 日本語でOK Selenium Dev. Test Practices: Encouraged: Page Object Models. この場で理解しておくこと ⚫ WebのUIテストは「操作する」部分があり、これらの領域をオブジェクトとして分離する(関心の分離) ⚫ これによって重複するコード量が減り、UIが変更されても修正の必要は一箇所でよくなる(単一責務) ⚫ 上記をパターンとして提供したのがページオブジェクトモデル 24

25.

Web自動テストにおける関心の分離 ⚫ テストコードの関心事(What: 何を検証するか) ⚫ テストケース ⚫ ビジネスロジック 僕もランプと妖 精の関心の分離 してるよ ⚫ ページの関心事(How: どのように操作するか) ⚫ UI要素 ⚫ UI操作の詳細 25

26.

POMの利点 ⚫ 単一責任の原則の実現 ⚫ テスト対象の変更により、テストのロジックに変更を与 えなくなる ⚫ 変更を加えるコードが単一で済む 僕もランプにす ることによって 利点があるんだ けど、ここでは 話せないよ ⚫ 可読性と再利用性の向上 ⚫ 詳細を見なくても、何しているのかがわかる ⚫ (そうなってなければ名前付けが間違ってる) 26

27.

E2Eテストを関心の分離すべきかどうか判断する基準 ⚫ 関心の分離を判断するときは「単一責務の原則」を 参考にするといい感じです ⚫ 同じ理由で変更される → 同じ場所に置く そこまで考えて やってる暇ない んだよねえ ⚫ 異なる理由で変更される → 分離する ⚫ 品質特性のトレードオフを鑑みた上で「分離しな い」のもありです ⚫ 「性能効率性のために結合する」など 27

28.

二つの関心ごとが混在している例 def test_ログインのテスト(page: Page): page.goto('https://example.com/login') page.fill('#email-input', '[email protected]') page.fill('#password-input', 'password123') page.click('button[type="submit"]') page.wait_for_selector('.dashboard') current_url = page.url expect(current_url).to_contain('/dashboard') welcome_message = page.text_content('.welcome-msg') expect(welcome_message).to_contain('ようこそ') これから提示さ れるコードは本 当に動くかわか らないらしいよ ⚫ 読みにくい: 何をテストしているのかが、どうやってテストしているかの詳細に埋 もれている ⚫ 重複が発生: 他のテストでログインが必要な場合、同じコードを繰り返し書く ⚫ 変更に弱い: UIが変更されたら、すべてのテストケースを修正する必要がある 28

29.
[beta]
POMを適用したらこんな感じ
いわゆるページオブジェクト

いわゆるテストコード

class LoginPage:
# ページ上の要素の場所(セレクター)を定義
URL = 'https://example.com/login'
EMAIL_INPUT = '#email-input'
PASSWORD_INPUT = '#password-input'
SUBMIT_BUTTON = 'button[type="submit"]'
DASHBOARD_SELECTOR = '.dashboard'
WELCOME_MESSAGE = '.welcome-msg'

from .login_page import LoginPage

def __init__(self, page: Page):
# Playwrightの'page'オブジェクトを受け取る
self.page = page

def test_ユーザーログイン後にメッセージが表示されること(page: Page):
login_page = LoginPage(page)
login_page.navigate()
login_page.login('[email protected]', 'password123')
current_url = login_page.get_current_url()
expect(current_url).to_contain('/dashboard')
welcome_message = login_page.get_welcome_message()
expect(welcome_message).to_contain('ようこそ')

長くなってむし
ろ見づらくなっ
てるようにみえ
るよぉ

def navigate(self):
self.page.goto(self.URL)
def login(self, email: str, password: str):
self.page.fill(self.EMAIL_INPUT, email)
self.page.fill(self.PASSWORD_INPUT, password)
self.page.click(self.SUBMIT_BUTTON)
self.page.wait_for_selector(self.DASHBOARD_SELECTOR)
def get_welcome_message(self) -> str:
return self.page.text_content(self.WELCOME_MESSAGE)
def get_current_url(self) -> str:
return self.page.url

29

30.

今ままでに見た おそらくLLMで出力された E2Eテストコードたち 30

31.

Page Objectの中にアサーションがある class LoginPage: # セレクターを定義 EMAIL_INPUT = '#email-input' PASSWORD_INPUT = '#password-input' SUBMIT_BUTTON = 'button[type="submit"]' def __init__(self, page: Page): self.page = page def login_and_verify(self, email: str, password: str): # ページ操作 self.page.fill(self.EMAIL_INPUT, email) self.page.fill(self.PASSWORD_INPUT, password) self.page.click(self.SUBMIT_BUTTON) ランプの中にご まがいる # ログイン後のURLを取得 current_url = self.page.url # Page Object内でテストの検証(アサーション)を実行 expect(current_url).to_contain('/dashboard’) ⚫ Page Objectの中にアサーションがある ⚫ アサーションはテストコードに寄せた方がいい(という説がある) ⚫ みんなもどっちがいいか考えてみよう ⚫ アサーションはどっちにあるべきか、テストとはなんなのか 31

32.
[beta]
巨大クラス
class HomePage:
def __init__(self, page: Page):
self.page = page
def click_logo(self):
# 実際のPlaywright操作: self.page.click(self.HEADER_LOGO)
print("DEBUG: ヘッダーのロゴをクリックしました。")
pass # 実装は省略
def search_in_header(self, query: str):
# 実際のPlaywright操作: self.page.fill('header input[name="q"]', query)
print(f"DEBUG: ヘッダーで「{query}」を検索しました。")
pass # 実装は省略

僕はちゃんと手
書きで書かれて
いるよぉ

def click_footer_link(self, link_name: str):
# 実際のPlaywright操作: self.page.click(f'footer a:has-text("{link_name}")')
print(f"DEBUG: フッターのリンク「{link_name}」をクリックしました。")
pass # 実装は省略
def click_product_card(self, product_id: int):
# 実際のPlaywright操作: self.page.click(f'.product-card[dataid="{product_id}"]')
print(f"DEBUG: 製品ID: {product_id} のカードをクリックしました。")
pass # 実装は省略

⚫

1つのページをひとつの巨大クラスで実装したパターン

⚫

1つのページ=1つのクラスにする必要はないです

⚫

この場合、「パネルオブジェクト」として分離してもOKです

※Nano Banana Proで生成

32

33.
[beta]
Page Objectの中でデータ生成
class RegistrationPage:
def __init__(self, page: Page):
self.page = page

def _generate_random_email(self) -> str:
timestamp = int(time.time() * 1000)
return f"testuser_{timestamp}@example.com"
def register_with_random_user(self):
email = self._generate_random_email() # ページオブジェクト内でデータを生成
password = 'Test123!'

(あとで考え
る)

# ページ操作
self.page.fill(self.EMAIL_INPUT, email)
self.page.fill(self.PASSWORD_INPUT, password)
self.page.click(self.REGISTER_BUTTON)

⚫ Page Objectの中にデータ生成を持たせているパターン
⚫ テストコードのセットアップで行ったり、ヘルパー関数などで別途定義するのが望ましい
⚫ ただ、「操作に関するデータ生成」としてPage Objectにあるべきという論もある
⚫ 何が正しいか考えてみよう
33

34.

密結合 class HomePage: # セレクターや操作メソッドがあると仮定 def __init__(self, page: Page): self.page = page def navigate_to_dashboard(self): self.page.wait_for_selector(self.DASHBOARD_SELECTOR) pass class LoginPage: # セレクターや操作メソッドがあると仮定 ごまとランプは 密結合してると もいえるね def __init__(self, page: Page): self.page = page self.home_page = HomePage(page) # 直接他のPage Objectをインスタンス化 def _perform_login_actions(self, email: str, password: str): #ログインするアクションが記述されている def login_and_navigate(self, email: str, password: str): self._perform_login_actions(email, password) self.home_page.navigate_to_dashboard() # 他のPage Objectを直接操作 ⚫ LoginPageの関心はログインページで閉じるべき ⚫ ログイン後のページ遷移という関心ごとも含まれている ⚫ ページ間の遷移はテストコード側で制御するのがセオリー 34

35.

知っておこうポチョムキン理解 ⚫ 生成AIにおいて、「表面的には理解しているように見 えるが、実際の応用では間違う現象」 ⚫ 参考:ハルシネーション(事実誤認)より深刻なAIの「わ かったふり」を暴く:MITなどが発見したLLMの“ポチョム ポチョムキンっ てなんだかかわ いいよねえ キン理解”とは ⚫ 「Page Object Model」「関心の分離」「単一責務」 という言葉が何かをAIに聞いたらいい感じに言語化し てくれます。 ⚫ ただ、出力されるコードはそうだとは限らない 35

36.

他にもあるポチョムキンPOMの例 ⚫ 過度なDRY ⚫ DAMPなテストコードがいいという説がある ⚫ 同じ要素セレクタが点在 なんでこんな コードが出てく るかごまもわか らないよ ⚫ テストコードからPage Objectにセレクタを渡してい る 36

37.

E2Eテストコードのレビューでよく使う観点 ⚫ このテストコードは意図や関心ごとが明確に伝わってくるか?(DAMPの 原則) ⚫ テストの意図に対して、正しく反映したコードになっているか? ⚫ テストの意図に対してテストコードの作用が限定的な場合、どちらかを修正す る必要がある レビュー観点っ て言葉があるよ ねえ ⚫ 多くの場合はテストの意図を示すテストケース名を変える必要がある ⚫ 修正が発生した場合、影響範囲が限定的になる構造になっているか? ⚫ ツールが提示しているベストプラクティスやチームが設定した設計方針に 合致しているか? ⚫ していない場合、これらは見える形で文書化されているか これらの観点は、私のレビューアとしての役割・目的が合致しているから使っているだけです 皆さんの現場では自分の役割に応じて自分で考えてみてください! 37

38.

テスト自動化エンジニアとして 38

39.

今回紹介したアンチパターン 1. Page Objectのなかのアサーション 2. 巨大クラス 3. データ生成をPageに持たせる いろいろあるん だねえ 4. 密結合したPageObject 39

40.

紹介したアンチパターン 注意 これらはあくまでSeleniumなどが提示した 1. ベストプラクティスを参考にした場合に言える”アンチパターン”です Page Objectのなかのアサーション これらがコンテキストによらず適用できるアンチパターンかは未だに議論されています 2. 巨大クラス だからこそ、より原理的な「設計原則」を理解してほしい 3. データ生成をPageに持たせる 4. 密結合した異なるPageObject r/QualityAssurance: Page Object Model best practices 40

41.

そもそもPOMだけじゃない設計パターン ⚫ Robotパターン ⚫ 画面単位や特定のワークフロー単位で設計される ⚫ Screenplayパターン いろいろあるん だねえ ⚫ ユーザーのタスクや目標達成に焦点をおく ⚫ ビジネス用語をテストシナリオに取り入れ、より高度な 抽象化を行う 41

42.

テスト自動化エンジニアに言いたかったこと ⚫ 「AIが実装したコードをAcceptする」なら誰でもできます ⚫ POMといったデザインパターンは自動テストにおける「自動 化アプローチ」や「スクリプティング技法」のひとつです ⚫ 絶対的な正解ではない 偉そうに ⚫ テストはコンテキスト次第というのを忘れてはいけない ⚫ さらに言えば、プロダクトだってその場のコンテキストに合 わせて意思決定しているよね 42

43.

テスト自動化エンジニアに言いたかったこと ⚫ 「AIが実装したコードをAcceptする」なら誰でもできます 「コンピューターがテスト実行する何かを作る」 ⚫ POMといったデザインパターンは自動テストにおける「自動 「誰かが言っていたベストプラクティスを模倣する」 化アプローチ」や「スクリプティング技法」のひとつです で満足するのではなく 偉そうに 「自動テスト」というシステムを作る ⚫ テストはコンテキスト次第というのを忘れてはいけない 「自動テストエンジニア」として 本質的な課題解決に取り組もう ⚫ さらに言えば、プロダクトだってその場のコンテキストに合 そういう人になりたいぜ わせて意思決定しています ⚫ ADRとかがその例です 43

44.

参考文献 いつもありがとね〜 やまずんの資料を見 るのもいいけど、参 考文献も読んでね イラスト:タスマニアデビ男 • IEEE Computer Society (松本 吉弘 訳). (2014). ソフトウェアエンジニアリング 基礎知識体系 : SWEBOK V3.0. オーム社. • Martin, Robert C. (角 征典, 高木 正弘 訳). (2018). Clean architecture : 達人に 学ぶソフトウェアの構造と設計. ドワンゴ, KADOKAWA • ソフトウェアテスト徹底指南書〜開発の高品質と高スピードを両立させる実践 アプローチ、井芹洋輝、2025年、技術評論社 • Selenium Dev. (2025/10/30). Test Practices: Encouraged: Page Object Models. https://www.selenium.dev/documentation/test_practices/encouraged/page _object_models/ (最終アクセス日: 2025年11月23日) • Ledge.ai. (2025/7/8).ハルシネーション(事実誤認)より深刻なAIの「わかっ たふり」を暴く:MITなどが発見したLLMの“ポチョムキン理解”と は.https://ledge.ai/articles/potemkin_understanding_llm (最終アクセス日: 2025年11月23日) • Reddit. r/QualityAssurance: Page Object Model best practices (スレッド). https://www.reddit.com/r/QualityAssurance/comments/1ou4qr6/page_obje ct_model_best_practices/ (最終アクセス日: 2025年11月23日) 44