簡単な例でもう少し要約してみましょう:
大規模なアプリケーション、たとえばフィード、ニュース、アカウント管理、さまざまな機能を備えたユーザーポータルを構築しています。
開発中、テストを容易にするため、またはおそらく通信しているAPIが信頼できないために、モックデータを実装する必要があると判断しました。
モックデータの使用に対応するためにコアアプリケーションに変更を加える必要がある場合、またはアプリケーションが純粋であり、その使用方法にとらわれない場合、代わりにモックデータに強制的に方法を見つける必要がある場合アプリケーションに自分自身を挿入しますか?
私は、アプリケーションが外部がそれをどのように使用またはテストしたいかに関心を持つべきではなく、モック化されているかどうかをチェックする条件付きステートメントをアプリケーションに追加することは単に悪い習慣であるだけでなく、ひどい習慣であるという考え方を持っています。これは、アプリケーションにモックデータを取り込むのがいかに困難であっても、決して楽しませてはならない完全な結合です。
現在の割り当てでは、使用されているデータのコンテキストに応じて異なる処理を実行する、コードベース全体にわたって文字通り何百もの参照が表示されています。この種のものは私を泣かせたいと思うが、多分私はそれをすべて間違っており、これには正当な利益があるのだろうか?
なぜこの密結合が良いことになるのかについての議論は何ですか?
アプリケーション自体にモックデータの使用をしっかりと組み込んだときの一般的な考えと経験は何ですか?
アプリケーションがブラックボックスであることが理想です。テストでは、さまざまな入力を与え、結果を観察します。
実際にはトレードオフがあります。いくつかのテストは書くのに本当に高価です。いくつかのテストは低い値を持っています。テストが100%になることは決してないので、「十分に良い」場合はビジネス上の決定でもあります。模造品や特別な条件が導入されるたびに、テストの価値が低下し、盲点が作成されます。
ただし、いたるところにチェックがある場合、それはアプリケーションの設計がテストに適しておらず、リファクタリングする必要があることを示しています。リファクタリングの目標は、モジュールの境界とインターフェース、および境界を越えたときにデータ転送オブジェクトを導入することです。したがって、依存関係は1つの場所で対処できます。
現在の割り当てでは、使用されているデータのコンテキストに応じて異なる処理を実行する、コードベース全体にわたって文字通り何百もの参照が表示されています。
それはおそらく1つの無垢のif
から始まった遺産ですが、アプリケーションが大きくなるにつれて、いくつかの場所で状態を報告する必要がありました。これは技術的な負債であり、この方法で簡単にエラーが発生する可能性があります。条件を更新する必要がありますが、場所を忘れるとすべてが崩れます。
なぜこの密結合が良いことになるのかについての議論は何ですか?
すばやく簡単に実行できました。
それは完全な結合であり、アプリケーションにモックデータを取り込むのがいかに困難であっても、楽しまれることさえありません。
実際の生活はこれよりも複雑です。迅速で汚れていると、長く複雑なクリーンなソリューションよりも価値が高まることがあります。ただし、常に長所と短所を重み付けする必要があります。
ここで、モックデータを分離したい場合は、実装とモックデータ以外にも、いくつかのリファクタリングと分離を行う必要があります。責任が多すぎる実装がある場合があります。理想的には、データの取得方法に関係なく、データを操作するエンティティが必要です。
これを実現する1つの方法は、依存性注入(DI)を使用することです。目標は、データが提供されるエントリポイントを削減および制御することです。
データを操作するコンポーネントC
があるとします。データを保持するデータサービスS
に依存しているとしましょう(そして、おそらくそれらをフェッチして適切な構造に保存します)。 DIを実現する一般的な方法は、コンストラクションインジェクションを使用することです。
ComponentC(IDataServiceS service) { ... }
いつものように、具体的な実装に依存しないでください。そのため、IDataService
はインターフェイスであり、実装はプロファイルに応じて変更されます。
あなたの場合、IDataService
の具体的な実装がそれぞれ異なる3つの異なるプロファイルを持つことができます。いくつかの例:
ProdDataService implements IDataService
// Production implementation (fetch in database, through network...)
FakeDataService implements IDataService
// Fake to be used in development. Fetch data from a local file,
// those data closely match production data and allow to use the
// application like in production.
MockDataService implements IDataService
// Mock to be used in test. Mock Edge case data.
具体的な実装を注入する方法は、主にスタックに依存しています。
取得できるのは、データの単一のエントリポイント(少なくともコンポーネントごと)です。コンポーネント内に複数の条件がなく、データを取得/解析/保存/構築する責任はサービスに任されています。コンポーネントはどのような状態もありません。
モックデータの使用に対応するためにコアアプリケーションに変更を加える必要がある場合、またはアプリケーションが純粋であり、それがどのように使用されているかにとらわれず、代わりにモックデータに注入する方法を強制する必要がある場合アプリケーションにそれ自体?
「純粋で不可知論的」が正解です。それが正しい表現かどうかはわかりませんが、意図した意味(つまり、データが本物かどうかわからない)は正しいものです。
テストのポイントは、実際の条件をテストすることです。テスト/実際のデータに基づく分岐動作がある場合、本質的にどのテストでも「実際のデータ」ブランチの動作をエラーチェックできず、テストの目的が損なわれます。
[..]モック化されているかどうかをチェックする条件付きステートメントをアプリケーションに追加するのは悪い習慣ではなく、ひどい習慣であるという考え方です。
あなたの発言に完全に同意します。そうは言っても、レガシーコードが最新のプラクティス(したがって「レガシー」)で最新でないことは合理的であり、状況を引き起こす可能性があります理解可能ですが、それは考慮すべきであるという意味ではありませんいい練習。
現在の割り当てでは、使用されているデータのコンテキストに応じて異なる処理を実行する、コードベース全体にわたって文字通り何百もの参照が表示されています。この種のものは私を泣かせたいと思うが、多分私はそれをすべて間違っており、これには正当な利益があるのだろうか?
ここでの唯一の実際の使用例は、特定の動作でキルスイッチが必要な場合です。 SRPは、そのような動作をコードベース全体に適用するべきではないと規定していますが、それがレガシーコードベースであることを無視してみましょう。
ただし、killスイッチはデータモックとは異なります。アプリケーションの実際の動作をテストする目的に従って、データ入力(実際の場合も模擬の場合も)はまったく同じパスに従う必要があります。
なぜこの密結合が良いことになるのかについての議論は何ですか?
もう1つの考慮事項は、非常に短い寿命のアプリケーションの場合です。コンソールアプリケーション私は短期間で目的を達成するためにすぐに強打します。適切なコーディングは実際には必要ありません。
しかし、適切なコーディングの必要性は、コードベースのサイズと複雑さの両方、およびコードベースへの長期的な依存(および保守性)に応じて急速に拡大します。
アプリケーション自体にモックデータの使用をしっかりと組み込んだときの一般的な考えと経験は何ですか?
しないでください。
モックデータの使用に対応するためにコアアプリケーションに変更を加える必要がある場合、またはアプリケーションが純粋であり、その使用方法にとらわれず、モックデータに強制的に方法を見つける必要がある場合アプリケーションに自分自身を挿入しますか?
正直なところ、これはデザインが悪いように思えます。または、完全に悪くない場合でも、少なくとも有機的に成長して混乱します。
アプリケーションのコアは完全にデータにとらわれないものである必要があるというあなたの見解に完全に同意します。テストデータまたはモックデータに対応するために本番コードを変更していることに気付いた場合は、コードを再編成する必要があります。
理想的には、データをマスクするために、コンストラクターまたはインターフェースを介して依存関係を注入することにより、テストに必要なものだけを注入できるようにする必要があります。
Javaを使用している場合は、メソッドでの@VisibleForTesting
などのアノテーションの使用を検討して、たとえ目的が限定されていても、確実にマークされるようにする必要があります。
開発中、テストを容易にするため、またはおそらく通信しているAPIが信頼できないために、モックデータを実装する必要があると判断しました。
データのモック自体には、次のようないくつかの利点があります。(1)(注入可能な)依存関係をモックすることで、クラスを個別にテストできます(そして、コアアプリケーションではなく、ユニットテストでこれらのモックを記述します); (2)次に、モックを変更して特定の値を返すことにより、テストのカバレッジを制御できます。これにより、ユニットテストが以前にカバーされていないシナリオに分類されます。 (3)テストを自動化できるため、テストの実行がはるかに速くなります。
これについての興味深い読み: https://blog.cleancoder.com/uncle-bob/2014/05/10/WhenToMock.html
...モックデータの使用に対応するためにコアアプリケーションに変更を加える必要がある場合、またはアプリケーションが純粋であり、その使用方法にとらわれず、モックデータを強制的に検索する必要がある場合アプリケーションに自分自身を注入する方法?
ボブおじさんによる「クリーンアーキテクチャ」をご覧ください:( https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html ) 。
ブログの投稿で説明されているように、クリーンアーキテクチャの特徴は次のとおりです。
結論:説明された記事と概念に基づいて(そして実際のプロジェクトでの私の経験に基づいて)yourアプリケーションは純粋であり、その使用方法にとらわれないままであり、DBなどはコアアプリに「プラグイン可能」である必要があります。
私の答えは他のいくつかの質問と似ていますが、少し違う方法で議論を進めたいと思います。
アプリケーションは、ユーザーが直接操作するプログラムという意味で、通常、実際の作業をすべて実行するいくつかのAPI(サービス、ライブラリなど)の薄いラッパーです。これらのAPIサーフェスは、テストに集中する必要がある場所です。テストは、実装の詳細ではなく、文書化されたAPIサーフェスのパブリック動作に基づいている必要があります。パブリックAPIの動作が文書化されていない場合、または公開されている実装の詳細が多すぎる場合は、適切なテストを書き始める前に、これらの問題に対処する必要があります。
これは双方向です。アプリケーションをAPIの実装の詳細に結合したり、APIを操作対象のデータの実装の詳細に結合したりしないでください。これに対処する効果的な方法は、OOインターフェイスを具象型と区別する言語では、メソッドパラメータと戻り値の型は一般にインターフェイスでなければならないという原則です。データがインターフェイス型として表される場合、データ自体を明確に定義されたAPIを持つと考えることができます。
さて、あなたの質問の主要部分に:実装の詳細は使用法に依存するべきですか?はい、たぶん。文書化されたパブリック動作の制約内で、つまりテストを中断することなく、通常の入力を最適化したり、別の方法でパフォーマンスを改善できる特殊なケースのテストと分岐を確実に行うことができます。しかし、これらすべてはAPIの利用者から隠されるべきです。これらの最適化を使用した実装は、(テストを作成する必要がある)Edgeの場合でも、それらを使用しない実装とまったく同じ結果を生成する必要があります。
そして、最適化を記述して維持するコストがパフォーマンスの向上に見合うかどうか、慎重に検討する必要があります。パフォーマンスに夢中になる開発者が多すぎて、プログラムが一度行うことから数ミリ秒を削るために巧妙なハックを書くのに何日も費やすほどです。このような最適化は、目立つことは言うまでもなく、ほとんど測定できません。一般的に、パフォーマンスの違いは目立つだけでなく、パフォーマンスと保守性のトレードオフを検討する前に、実際にパフォーマンスに問題があるはずです。
多くの場合、システムの管理者ユーザーは、システムの使用方法を学習する(または新しい機能を試す)一環として、モックデータを自分で作成する必要があります。
したがって、この目的のためだけにユーザーテスト用にデータカテゴリを作成する必要があります。
どのような場合でも、システムを最初からマルチテナントに対応できるように設計することは通常は有用だと思います。そのため、同じシステムの本番データから完全に分離された「テスト」カテゴリを(他の個別のカテゴリとともに)持つことができます。システム内のデータ)。
私の意見では、これは最も実用的なアプローチです。
短いバージョン:アプリケーションは完全に不可知論である必要があり、異なる動作が必要な場合は、動作もデータである可能性があることを認識し、適切に設定します。
ロングバージョン:JavaScriptを使用しているようですが、設定クラスがありません。構成するための最も簡単な言語/環境ではありませんが、それは確かに可能です。
基本的に、テスト/開発のデータと環境があるか、本番環境で開発しています。したがって、別のURLを取得することは機能です。問題は、URLを決定する方法と場所が異なることです。構成クラスがなければ、その決定はランダムな場所で行われます。
表示されている問題は、データにデータを通知させるのではなく、コードを使用してデータを決定することによって発生します。プログラムの実行中にエンドポイントを計算するのではなく、エンドポイントを取得する必要があります。
それがどのようになるかに関しては、それは1回限りのように見えるので忍び寄り、正しく実行するために必要となる数十または2行ではなく、1行のコードを記述する方が簡単です。それは1つの価値と1つの決定のようであり、それを柔軟にするための追加の努力が必要であるか、または価値があるとは思われません。
少し哲学的に、そして著者を引用するために、「人々は彼らの目標を達成するために、彼らができる限りの努力をしない」ことが好きです。あなたはそれを変えることはできません、あなたができることは彼らの目標を変えることです。
一般に、アプリケーションは、データが正しい形式である限り、データの出所を気にする必要はありません。偽のデータをテストに使用することは素晴らしいアイデアです。なぜなら、実際のデータストリームでめったにヒットしない可能性のあるコーナーケースをテストするための値を指示できるからです。理想的には、アプリケーションが実際に「認識」していなくても、模擬データをプラグインできます。つまり、コードのフラグではなく、構成の一部になります。たとえば、データストリームのURLを実際のソースからテストインスタンスに切り替えます。
これには、事前にある程度の計画を立てるか、後で変更する必要があります。設計していない場合は、実際のデータではなく偽のデータを要求するために、アプリケーション内のフローを制御するフラグを代わりに使用したくなるかもしれません。それが最小化されている限り、それはひどい考えではありません。テストモードで行う特殊なケースが多ければ多いほど、テストによる本番コードのテストが少なくなります。
フラグ値のチェックを繰り返すと、ポリモーフィズムが欠落しているというコードの匂いがすることがあります。これを識別してリファクタリングできる場合は、実行しているフラグチェックの数を減らすことができ、コンポーネントがテストモードとプロダクションモードでどのように異なるかをよりよく理解できます。