Slide 1

Slide 1 text

階層化自動テストで 開発に機動力を 同人ソフトサークル Project ICKX 若葉 章 @effy_staffs (EFFY開発チーム)

Slide 2

Slide 2 text

自己紹介 • 若葉 章(わかば あきら) • 2010年~ 同人ソフトサークル project ickx • プロデューサー / 営業 / インフラ / サーバサイド... / ゲームの実開発以外全部 • 本職はスタッフエンジニアっぽいこともするphpエンジニア • php続けてもうすぐ25年 php3の頃からお世話になっています • 最近の趣味は自転車とコーヒーとアマチュア無線 • tyrell iveで京阪や徳島から高松まで走ったり、icom ic-705やID-52で移動運用試したり • ボトルゲージ用のアルミステーやドリッパーホルダの自作を始めましたもうだめだ • Nintendo Switch で『VERTICAL STRIKE ENDLESS CHALLENGE』販売中 • Qiitaで『phpで高速に・省メモリ・確実に日本語csvを扱う方法』公開中 https://qiita.com/wakabadou/items/84b48ca12f25fb2fb69c 「composer require fw3/streams」をよろしくね。 「composer require tacddd/tacddd」もよろしくね。 「composer require bypassflow/crypt 」もよろしくね。

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

宣伝 PHPerKaigi2021で発表した「PHPでCSVを 安心して扱うために」にて紹介した、SJIS CSVを直接扱えるライブラリ “fw3/streams “ 最新版にてZIPファイル内のCSVファイルを ”そのまま直接”扱えるようになりました。 ぜひ使ってみてね。

Slide 6

Slide 6 text

こんな感じ。 CSVの設定はふつう NoRewindIterator万歳!!

Slide 7

Slide 7 text

宣伝 最近、bypassflowと名付けたPHP8.4以降向けの ライブラリ開発に着手しました。 現在はcryptのみリリースしています。 同じ条件でも毎回違う結果が出るのに適正に検証できるハッシュ 同じ条件でも毎回違うバイナリが出力される暗号化処理 来月あたりに強力なオブジェクトコレクションを リリースする予定なのでお楽しみに。

Slide 8

Slide 8 text

資料1 PHPカンファレンス関西2024やPHPerKaigi2024で 発表した”「“品質”が高いコード」って何?”を 読むとより理解が深まります

Slide 9

Slide 9 text

資料2 PHPカンファレンス新潟2025で発表した ”「兵法」から見る"質とスピード"”を 読むとより理解が深まります

Slide 10

Slide 10 text

用語・原則

Slide 11

Slide 11 text

“テスト”

Slide 12

Slide 12 text

• リスクベースのアプローチの下で静的および動的技法を 用いソフトウェアが仕様や要求事項を満たしていることを 検証(Verification)妥当性確認(Validation)する 一連のプロセス • 有限のテストケースによる実行や解析を通じて欠陥を検出 し、品質情報を提供するもの ISO/IEC/IEEE 29119-1:2022で定義される ソフトウェアテストでは

Slide 13

Slide 13 text

“品質”

Slide 14

Slide 14 text

“品質”とは “対象”に“本来備わっている特性”の 集まりが“要求事項”を満たす“程度” ISO 9000 (JIS Q 9000:2015)

Slide 15

Slide 15 text

ISO 25000で定義される “ソフトウェアの品質”では 明示された条件下で使用するとき、 明示的ニーズ又は暗黙のニーズを満たす ためのソフトウェア製品の能力 ISO/IEC 25000:2014 (JIS X 25000:2017)

Slide 16

Slide 16 text

つまり“品質”とは “要求を満たしていること”

Slide 17

Slide 17 text

“高品質”

Slide 18

Slide 18 text

“高品質”とは • “定められた要求事項”に対して“要求を満たしている”こと です。 • 超過でも未満でもなりません。 • めっぽう忘れられがちですが、 未満だけでなく、超過もダメです。

Slide 19

Slide 19 text

“高品質” を達成するには?

Slide 20

Slide 20 text

レイヤごとに要求を 明確化しそれを達成する

Slide 21

Slide 21 text

単一責任原則

Slide 22

Slide 22 text

モジュール、クラスまたは関数は、 単一の機能について責任を持ち、 その機能をカプセル化するべきである

Slide 23

Slide 23 text

開放閉鎖原則

Slide 24

Slide 24 text

ソフトウェア要素は、 拡張に対しては開いており、 修正に対しては閉じているべきである

Slide 25

Slide 25 text

テストにおける 単一責任原則

Slide 26

Slide 26 text

テストは単一のスコープについて 責任を持ち、その検証・妥当性確認を カプセル化すべきである

Slide 27

Slide 27 text

テストにおける 開放閉鎖原則

Slide 28

Slide 28 text

テスト内容は 階層の追加に対しては開いており、 修正に対しては閉じているべきである

Slide 29

Slide 29 text

階層化自動テストで 開発に機動力を

Slide 30

Slide 30 text

Webサイトを対象とした 今までのテスト階層

Slide 31

Slide 31 text

• セッション・永続化を伴う複数URLへの一環した操作 • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストに対するレスポンス • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力

Slide 32

Slide 32 text

• セッション・永続化を伴う複数URLへの一環した操作 • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストに対するレスポンス • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 E2Eで カバー xUnitで カバー

Slide 33

Slide 33 text

E2Eのカバー範囲が巨大すぎて 出力に関係する変更があった場合 膨大な修正が生まれる訳です

Slide 34

Slide 34 text

そもそもこれ “開放閉鎖原則” に即していないんですよね 修正のたびに別階層に影響でる訳だし

Slide 35

Slide 35 text

これがあるので 「E2Eは必ず破綻する」 などと言われてきたわけです

Slide 36

Slide 36 text

テストを 境界づけられたコンテキスト として整理します

Slide 37

Slide 37 text

そこで

Slide 38

Slide 38 text

テストの関心事を テストで見るべきもので分割する

Slide 39

Slide 39 text

• 状態 • 特定条件における出力内容を状態と定義します • テストとして関心事は特定条件における出力内容の妥当性 • テスト開始条件は固定され、結果には冪等性を要求します • そのため、前後URLなどのフローに関する内容は関知しません • フロー • 関連する複数の状態を横断するものをフローと定義します • テストとしての関心事は適切にフローを流せるか • その際のチェックポイントとして一部の状態をテストすることも ありますが、チェックポイント以外の状態は関知しません

Slide 40

Slide 40 text

• フローとして見るべきもの • セッション・永続化を伴う複数URLへの一環した操作 • リクエスト受け付けから出力までの処理コールツリー • 状態として見るべきもの • 単一URLリクエストに対するレスポンス • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力

Slide 41

Slide 41 text

• フローとして見るべきもの • セッション・永続化を伴う複数URLへの一環した操作 • リクエスト受け付けから出力までの処理コールツリー • 状態として見るべきもの • 単一URLリクエストに対するレスポンス • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 E2Eで カバー xUnitで カバー

Slide 42

Slide 42 text

それは今まで通りのE2Eと xUnitでカバーしていた事では?

Slide 43

Slide 43 text

では別の関心事で整理してみます

Slide 44

Slide 44 text

テストの関心事を ”完結できる単位”で分割する

Slide 45

Slide 45 text

• 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 • 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス • 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力

Slide 46

Slide 46 text

フローとして見るべきもの 状態として見るべきもの 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 マトリクスにするとこんな感じ

Slide 47

Slide 47 text

フローとして見るべきもの 状態は強く意識せず、フローだけをテスト 状態として見るべきもの フローは意識せず、状態だけをテスト 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 関心事を”テストで見るべきもの”にした場合

Slide 48

Slide 48 text

フローとして見るべきもの 状態は強く意識せず、フローだけをテスト 状態として見るべきもの フローは意識せず、状態だけをテスト 複数リクエストの集約が必要 E2E以外では実現困難 • セッション・永続化を伴う複数URLへの一環した操作 単一リクエストで完結できる 状況によりE2E、xUnit使い分け • 単一URLリクエストに対するレスポンス 変数値の確認で完結できる xUnitのみで完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 関心事を”テストを完結できる単位”にした場合

Slide 49

Slide 49 text

どこに注目する必要があって どのようにすればいいか を考えやすくなりました

Slide 50

Slide 50 text

また、テストを実施する手段を 適材適所しやすくなりました

Slide 51

Slide 51 text

完結できる単位に注目すると テスト階層が見えてきます

Slide 52

Slide 52 text

テスト階層に期待される責務も 明確になってきます

Slide 53

Slide 53 text

• 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 • 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス • 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 フローが妥当に流れること

Slide 54

Slide 54 text

• 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 • 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス • 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 状態が妥当であること

Slide 55

Slide 55 text

• 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 • 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス • 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 フローが妥当に流れること

Slide 56

Slide 56 text

• 複数リクエストの集約が必要 • セッション・永続化を伴う複数URLへの一環した操作 • 単一リクエストで完結できる • 単一URLリクエストに対するレスポンス • 変数値の確認で完結できる • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 状態が妥当であること

Slide 57

Slide 57 text

状況が整理された所で テスト階層の詳細を 見ていきましょう

Slide 58

Slide 58 text

セッション・永続化を伴う 複数URLへの一環した操作

Slide 59

Slide 59 text

• E2Eを適用せざるを得ない階層 • リクエストパラメータの管理や描画内容の検証 を考えると、E2E以外での対応は非現実的 • なんでもできるから、なんでもやってしまう そのせいで地獄のメンテが発生します • そう思い込まれがちですね

Slide 60

Slide 60 text

このテスト階層に期待される責務は?

Slide 61

Slide 61 text

フローが妥当に流れること

Slide 62

Slide 62 text

• URLをまたぐフローにのみ関心を持ちます • URLごとの詳細な状態は他テスト階層で 保証されます • 結果、フロー変更の影響を局限できます • MPAならとてもシンプルですね • 入力 => 確認 => 完了 => 結果確認 の流れだけ • 入力で入れた内容が確認で出力され • セッションに入った内容が完了で反映され • 反映された内容が結果画面で確認できること

Slide 63

Slide 63 text

ここでの肝は各ページでの細かい内容の 検証・妥当性確認を行わないことです

Slide 64

Slide 64 text

見るべきは妥当に遷移できたかどうかと 遷移結果としてユニークな状態のみです

Slide 65

Slide 65 text

E2Eテストが爆発しがちだったのは、 期待される責務である フローが流れること 以上のことを詰め込んでいたから

Slide 66

Slide 66 text

フローが流れるというのは ビジネス上重要な価値を持つ処理 が流れている事も指します

Slide 67

Slide 67 text

フローのみに関心を持てれば ビジネス上優先すべきテスト対象なのか で優先順位付けを行いやすくなるわけです

Slide 68

Slide 68 text

優先順位付けを行いやすくなるということは 判断の迷いが弱くなり意思決定も速くできるため 開発プロセスのリズムがよくなり テンポを速めることにつながります

Slide 69

Slide 69 text

では各URLごとの 詳細な状態の確認は どうする?

Slide 70

Slide 70 text

適切な責務を 担う階層に 委譲しよう!

Slide 71

Slide 71 text

単一URLリクエストに 対するレスポンス

Slide 72

Slide 72 text

• 一般にE2Eが適用される階層 • 負担感が爆増するのは大抵の場合ここのせい • 全体を通すE2Eで巻き取れてしまうため 単体で実行する意味がない • そう思い込まれがちですね

Slide 73

Slide 73 text

このテスト階層に期待される責務は?

Slide 74

Slide 74 text

入力検証、出力・表示内容が ブラウザ描画時点の内容として 妥当であること

Slide 75

Slide 75 text

• そのURLで完結する表示状態にのみ関心を持ちます • フローや変数の状態は他テスト階層で保証されます • 結果、表示内容変更の影響を局限できます • 特定のHTTPリクエストに対して、妥当なレスポンスで あるかを見ることで冪等性のあるテストをします • ページ遷移に伴う外乱がなく、純粋な リクエスト・レスポンスに関心を集中できます

Slide 76

Slide 76 text

単一URLリクエストにおける 変数割り当て

Slide 77

Slide 77 text

• 一般にE2Eが適用される階層 • 負担感が爆増するのは大抵の場合ここのせい • レンダリングするんだからE2Eでなければだめ • そう思い込まれがちですね

Slide 78

Slide 78 text

このテスト階層に期待される責務は?

Slide 79

Slide 79 text

出力前段階として 割り当てられている 変数が妥当であること

Slide 80

Slide 80 text

• 描画に利用する変数の状態のみ関心を持ちます • フローやブラウザでの描画、変数生成手段は 他テスト階層で保証されます • 結果、アサイン内容変更の影響を局限できます

Slide 81

Slide 81 text

レンダリングする内容、 どうやって渡してますか?

Slide 82

Slide 82 text

一般的なフレームワークを利用している場合、 Renderインスタンスに直接変数アサインか View Modelオブジェクト経由が多いです

Slide 83

Slide 83 text

そう、この時点で変数なんですよ つまりxUnit適用できるな?

Slide 84

Slide 84 text

今までE2Eに対応を強制していた 検証内容をより早い段階へ委譲させる ことが出来ました

Slide 85

Slide 85 text

ストレージ以外の大がかりな実行準備を 必要としないため、比較的軽率に実行 出来るのも利点です

Slide 86

Slide 86 text

また、その気になればEC CUBE2などの 旧いプロダクトであったとしても 適用できる余地があります というかやった あれはRender内でexitしてるのでそのあたりの つじつま合わせが大変ですが

Slide 87

Slide 87 text

単一URLリクエストに おける検証処理

Slide 88

Slide 88 text

• 一般にE2Eが適用される階層 • 他の階層で吸収されたり、されなかったりと 曖昧になりがち • フォーム入力とブラウザ出力なんだから E2Eでなければだめ • そう思い込まれがちですね

Slide 89

Slide 89 text

このテスト階層に期待される責務は?

Slide 90

Slide 90 text

HTTPリクエストを入力値として 値の文字列表現が 受入仕様に対して妥当であること

Slide 91

Slide 91 text

• 入力値の文字列表現が受入仕様に対して妥当か どうかのみ関心を持ちます • フローやブラウザでの描画、変数生成・割当 手段は他テスト階層で保証されます • 結果、受入仕様変更の影響を局限できます

Slide 92

Slide 92 text

ただし…

Slide 93

Slide 93 text

• 入力値検証処理のみを切りはがせる環境である 必要があります • SymfonyならTypeTestCaseで検証できるが 単一URL単位でのリクエストとして表現する のが手間

Slide 94

Slide 94 text

かつて私が提供した環境では RequestModelという概念を用意し そのリクエストが成立するために必要なこと 全てが妥当であることを保証していました

Slide 95

Slide 95 text

リクエスト受け付けから出力までの 処理コールツリー

Slide 96

Slide 96 text

• 他のテストが結果として巻き取っている階層 • 滅法バグが出る原因の一つ • 特に引数・返り値の変数型周り • インターフェースなり値オブジェクトなりで 意味安全をガチガチに固めてあればPHPStan などで静的解析としてテストが可能に

Slide 97

Slide 97 text

このテスト階層に期待される責務は?

Slide 98

Slide 98 text

特定コード地点からの呼び出し フローが妥当に流れること

Slide 99

Slide 99 text

フローが妥当に流れること

Slide 100

Slide 100 text

• マーチンの開放/閉鎖原則をうまく適用できていると、 インターフェースの呼び出しツリーをフロー、 具象の関心事を状態として定義する事ができます • URLリクエストに対する関心事、具象の関心事は 他テスト階層で保証されます • 結果、呼び出しツリーの変更の影響を局限できます

Slide 101

Slide 101 text

クラス・関数に対する入力と出力

Slide 102

Slide 102 text

• 一般にxUnitが適用される階層 • ここについては議論の余地がありませんね • テクニックとしてはJIG(治具)と見立て 作業中のリグレッションテスト機能として 割り切ると扱いやすくなります

Slide 103

Slide 103 text

このテスト階層に期待される責務は?

Slide 104

Slide 104 text

クラス・関数への入力が 出力時に状態として 妥当であること

Slide 105

Slide 105 text

• プログラミングインターフェースへの 入力から得られる出力状態にのみ関心を持ちます • 呼び出しフローは他テスト階層で保証されます • 結果、クラス・関数の仕様変更影響を局限できます • 使われ方の特性上、限定条件に特化した検証を 実施しやすいため、適切に作りこむと”追跡が面倒”な 不具合を抑止しやすくなります

Slide 106

Slide 106 text

ここまでのテスト階層にない テスト対象が発見された場合は?

Slide 107

Slide 107 text

コンテキストが 増えただけなので 適宜増やせば宜しい

Slide 108

Slide 108 text

逆もまたしかりで 例えばリクエスト検証処理は 実装都合で事実上実現不能ならば 減らしてしまえばいい

Slide 109

Slide 109 text

ただし、テストプロセスに対して 要求される事項を誤って削らないよう 注意が必要です

Slide 110

Slide 110 text

結びに

Slide 111

Slide 111 text

階層化自動テストとは • 境界付けられたコンテキストに応じてテストを分割する • テストの関心事を見るべきものと完結できる単位で分割する • テストで見るべきものをフローと状態に分割する • 階層テストはそれぞれ独立して機能する • 階層テストはプロセスに要求されるコンテキストの有無で増減する • ツールの謳う使い方に囚われない • 上記を満たし、実施される自動テストが階層化自動テストである

Slide 112

Slide 112 text

階層化自動テストを 導入するメリット

Slide 113

Slide 113 text

• テスト範囲と責務の明確化 • 修正・メンテコストの低減 • 開発プロセスのリズム・テンポ向上

Slide 114

Slide 114 text

階層化自動テストを導入することで テスト対象が明確になるため 高機能なテストを低負担で 実現しやすくなります

Slide 115

Slide 115 text

• セッション・永続化を伴う複数URLへの一環した操作 • 単一URLリクエストに対するレスポンス • リクエスト受け付けから出力までの処理コールツリー • 単一URLリクエストにおける検証処理 • 単一URLリクエストにおける変数割り当て • クラス、関数に対する入力と出力 画面遷移 のみ担当 画面描画 のみ担当 プログラムコール ツリーのみ担当 HTTP入力検証 のみ担当 アクションが生成 する変数のみ担当 処理の入出力 のみ担当

Slide 116

Slide 116 text

各工程において実施すべきテストを 適切に局限できるため 各工程で必要とされるテストを より早く完了させる事が出来ます

Slide 117

Slide 117 text

またいつそのテストが実施されるのか 工程単位でここまでは保証済みが 自明となるため 業務プロセスのリズムを 取りやすくなります

Slide 118

Slide 118 text

• セ ッ シ ョ ン ・ 永 続 化 を 伴 う 複 数 U R L へ の 一 環 し た 操 作 • 単 一 U R L リ ク エ ス ト に 対 す る レ ス ポ ン ス • リ ク エ ス ト 受 け 付 け か ら 出 力 ま で の 処 理 コ ー ル ツ リ ー • 単 一 U R L リ ク エ ス ト に お け る 検 証 処 理 • 単 一 U R L リ ク エ ス ト に お け る 変 数 割 り 当 て • ク ラ ス 、 関 数 に 対 す る 入 力 と 出 力 リリース前 受け入れ前 PR前 コミット 単位 作業 単位 作業 単位 上書き 保存単位 テストプロセスフロー

Slide 119

Slide 119 text

おしまい

Slide 120

Slide 120 text

appendix データ設計

Slide 121

Slide 121 text

自動テストを設計するにあたり 甚大な影響を与えるのがデータです

Slide 122

Slide 122 text

理想を言えば全てモックで完結できる 簡潔なデータ設計になっていると良いのですが そんな都合の良い状況はあまりありません

Slide 123

Slide 123 text

現実としてはストレージを利用して テストした方が速いし正確とかあるある

Slide 124

Slide 124 text

現実に負けてストレージに依存するテストを 設計する場合に問題になるのが 初期化に莫大な時間を 要求するマスタデータ

Slide 125

Slide 125 text

ECサイトならば 不正住所情報

Slide 126

Slide 126 text

これに対する対処として DBを分割する選択肢があります

Slide 127

Slide 127 text

MySQLなら同一接続先内で 別データベースを作るだけで対応できます 仮にリレーションが必要だった場合 DB名.テーブル名で指定する事が出来ます

Slide 128

Slide 128 text

PostgreSQLやSQLiteの場合は… 流石にリレーションきびしい…

Slide 129

Slide 129 text

¥e