この内容は私のポートフォリオサイトのブログ記事 The Limitations of TestDataFactory and Selector Pattern: Two Fundamental Problemsの日本語訳です。
TestDataFactoryとSelectorパターンの限界:2つの本質的な問題
🏭 TestDataFactoryについて
Salesforceでは、Apexテストにおいて明示的にテストデータを作成するために、TestDataFactory
の利用が推奨されています。
TestDataFactoryは、ユーティリティクラスでテストデータを一元管理することで、再利用性の向上やテストコードの可読性向上を目的とした設計です。
🔄 TestDataFactoryの2つの使い方
便利なTestDataFactoryですが、複雑なデータ構造を扱うテストになってくると、次のような2つの使い方に分かれます:
-
複雑なデータ構造そのものを生成するFactoryメソッドを提供する
例:Account、Opportunity、OpportunityLineItem、Quote、QuoteLineItem などを一括作成する -
必要最小限の構造だけ提供し、テストごとに肉付けしていく
例:AccountとOpportunityだけ生成し、その後のQuoteなどはテストクラス側で追加する
どちらの方式にも一長一短はありますが、共通する致命的な問題があります:
✅ あらゆるレコード操作がデータベースアクセスを伴う
- 親レコードを
insert
- 子レコードを親のIDを参照して
insert
- 孫レコードをさらに
insert
...
この繰り返しにより、テスト実行時にトリガー処理や不要なロジックが毎回実行され、次第にテストクラスの実行時間が長くなる傾向があります。
そして、テストの速度が劣化した開発現場では、やがてこんな最悪の選択が取られるようになります:
「もうまともなテストなんて書けない。75%のカバレッジだけ満たすための最低限のテストだけ書こう」
これこそが、TestDataFactoryの限界が表面化した姿です。
こうして書かれたApexクラスは、ろくに検証されていないか、過剰に重たいテストに依存している状態になってしまいます。
🎯 Selectorパターンについて
Salesforceがもうひとつ公式に推奨しているのが「Selectorパターン」です。
これは一言で言えば、「Repositoryパターンのうち、SELECT処理だけを抽出してまとめたデザイン」です。
オブジェクト単位で必要なSELECT文を集約することで、取得ロジックの再利用性を高めることを目的としています。
先ほど触れたTestDataFactoryの課題を踏まえると、次のように考えるのは自然でしょう:
このセレクタークラスをモックして、SELECT結果だけ差し替えられれば、テストはもっと速くなるんじゃないか?
ところが、このアプローチにも限界があります。
⚠️ Selectorパターンの構造的な問題
Selectorパターンは「オブジェクト中心の再利用」を目指しているものの、現実の開発では次のような事態が頻発します:
- 似ているけど微妙に異なるクエリがどんどん増えていく
- その差分を制御するフラグ引数が必要になる
その結果、セレクタークラスは以下のいずれかの問題に直面します:
- メソッドが爆発して複雑化
- フラグ地獄により条件分岐が読みづらくなる
🤔 Selectorのモックは現実的か?
実際にSelectorクラスをモックしようとすると、さらなる問題が発生します。
Interfaceベースのモックの限界
一般的には SelectorInterface
のようなインターフェースを定義し、それを MockSelector
クラスで実装します。
しかし、この設計には以下の課題があります:
- メソッドを追加するたびにインターフェースの修正が必要
- すべての実装クラスでそのメソッドに対応しなければならない
- 使わないメソッドにもダミー実装を書く必要がある
この結果、モックのための無駄なコードが膨らみ、メンテナンス性が悪化します。
virtual + overrideでの差し替えの限界
そこで、「virtualメソッドを使って必要な部分だけオーバーライドしよう」という選択肢も出てきます。
一見良さそうに見えますが、セレクタークラス自体の肥大化や複雑化、コンテキストの喪失といった本質的な問題は解消できません。
この方法では、根本的な構造の問題は残ったままです。
🕳️ 忘れがちな落とし穴:DML操作はどうする?
さらに忘れがちなのが、データ取得(SELECT)以外のDML操作(insert / update / delete)もテストでは時間と副作用の原因になるという点です。
-
insert
→ トリガーが発火 -
update
→ バリデーションルールやProcessBuilderが走る -
delete
→ 関連レコードの削除やフローが発動
つまり、SELECTだけをモックしても、テスト高速化には不十分なのです。
🚫 DRY原則を鵜呑みにしないで
SalesforceがSelectorパターンを推奨する理由のひとつに、「コードの重複を避けるため(DRY原則)」があります。
ただし、私はここに重要な落とし穴があると考えています。
AクラスとBクラスが同じクエリを使っているから、共通メソッドにまとめよう
ありがちなパターンですね。
しかし、AとBがまったく異なるコンテキストである場合、これは将来的な危険を孕みます。
たとえば、将来Aクラスの仕様だけが変わってクエリを修正したい場合、Bにも影響が及んでバグが発生することがあります。
DRY原則には「コンテキストが同じであること」が前提条件です。
この前提を無視して「似ているからまとめる」という設計は、変更に弱い脆いコードを生んでしまいます。
Salesforceの公式ドキュメントでもこの点はあまり強調されておらず、私は問題だと感じています。
📉 結論:Selectorパターンにも持続性はない
ここまでを整理すると、TestDataFactoryもSelectorパターンも、最初は便利に見えますが、
プロジェクトの成長とともに、保守性・パフォーマンス・拡張性の面で限界に直面します。
- FactoryはDMLとトリガー地獄で重くなる
- Selectorは肥大化と過剰な共通化で破綻する
💡 では、どうすればいいか?
答え:Query Delegation Pattern と Apex Eloquent
私がたどり着いた答えは、**「責務を分離し、モックしやすくする設計思想」**です。
✅ Query Delegation Pattern
- ドメイン層がクエリ構築を担当
- Repositoryは取得・保存といったI/Oのみに責任を持つ
この構成により、「A用のクエリ」と「B用のクエリ」がたとえ似ていてもドメイン単位で分離されたまま管理できます。
DRY原則も文脈を守りながら適用できるため、変更にも強い設計が可能です。
そしてこのQuery Delegation Patternを実現するために、私が設計・検証を重ねて作り上げたのが Apex Eloquent です。
🎭 最後に
TestDataFactoryとSelectorパターンは、Salesforceの多くのプロジェクトで使われており、公式にも推奨されています。
しかし、それが「持続可能である」かどうかは、また別の話です。
もし、あなたのプロジェクトでテスト設計やデータ取得戦略に限界を感じているなら、
Query Delegation PatternとApex Eloquentの導入を、ぜひ一度検討してみてください。