「Growing Object-Oriented Software」の抜粋を読み、具象クラスのモックが推奨されない理由をいくつか説明しました。
以下は、MusicCentreクラスのユニットテストのサンプルコードです。
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
そして彼の最初の説明:
このアプローチの問題は、オブジェクト間の関係を暗黙的に残すことです。モックオブジェクトを使用したテスト駆動開発の目的は、オブジェクト間の関係を発見することであることを今までに明らかにしたことを願っています。私がサブクラス化する場合、そのような関係を可視化するドメインコードには何もありません。オブジェクトのメソッドだけです。これにより、この関係をサポートするサービスが他の場所に関連しているかどうかを確認することが困難になり、次にクラスで作業するときにもう一度分析を行う必要があります。
彼が言ったとき、私は彼が何を意味するのか正確に理解できません:
これにより、この関係をサポートするサービスが他の場所に関連しているかどうかを確認することが困難になり、次にクラスで作業するときにもう一度分析を行う必要があります。
サービスがMusicCentre
と呼ばれるstartMediaAt
のメソッドに対応していることを理解しています。
彼は「他の場所」とはどういう意味ですか?
完全な抜粋はこちら: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html
これにより、この関係をサポートするサービスが他の場所に関連しているかどうかを確認することが困難になり、次にクラスで作業するときにもう一度分析を行う必要があります。
それについてよく考えた後、私はこの引用の可能な解釈を得ます:
引用された「サービス」は「スケジューリングの事実」に対応します。これは、「ScheduledDevice」という名前の付いた「フォーカスされた1つのロール」インターフェースで表すことも、インターフェースに依存しない具体的なメソッド実装で暗黙的に表すこともできます。
上記のサンプルでは、スケジューリングはCDPlayer
という名前のフル機能のオブジェクト全体で表されています。したがって、それでもMusicCentre
と「スケジューリングの事実」の間の暗黙的な関係が生じます。
したがって、具体的なクラスを挿入し、それらを高レベルのオブジェクトにモックする場合、これらのオブジェクトをテストする場合は、注入された各「コンクリート」オブジェクトを分析して、それらが非表示(暗黙的)であるためにモックする必要がある特定の関係があるかどうかを確認する必要があります。逆に、常にインターフェイス上でコーディングすることにより、開発者は高レベルのオブジェクトによって提供される関係の種類を直接把握できるため、単体テストを分離するためにモックする必要のある機能を検出できます。
その投稿の著者は、メンバークラスの使用よりもインターフェイスの使用を促進しています。
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.
彼が後で再発見することについて心配している関係は、MediaCentreクラスがすべてのCdPlayerオブジェクトを必要としないという事実です。彼の主張は、インタラクション(おそらく開始|停止のみに制限されている)を使用することで、相互作用が実際に何であるかを理解しやすくなるというものです。
「他の場所」とは、他のオブジェクトが同様に制限された関係を持っている可能性があることを意味し、完全なメンバーオブジェクトは必要ありません。
潜在的な機能をすべて爆発させると、この主張はより意味をなすようになります。
今、彼は「スタートとストップだけが必要だ」という主張はもっと理にかなっています。インターフェイスの代わりに具象メンバーオブジェクトを使用すると、reallyが何である必要があるかについて、将来の開発者にとって明確ではなくなります。 MediaCentreからCdPlayer内の他のすべての関数に対してユニットテストを実行することは、「ドントケア」状態に属しているため、テストの無駄です。この場合、Record
関数が機能していなかった場合は、必要ないため、本当に気にしません。しかし、将来のメンテナは、書かれているように、コードに基づいていることを必ずしも知っているとは限りません。
結局のところ、作者の前提は、必要なものだけを使用し、以前の必要なものを将来のメンテナに明らかにすることです。目標は、その後のメンテナンス中にコードのモジュールの再作成/再分析を最小限に抑えることです。
ここで私が意味するサービスは、CDPlayer.scheduleToStartAt()でした。それがMediaCentreと呼ばれるものであり、機能するために必要なコラボレーターです。 MediaCentreはテスト中のオブジェクトです。
実装クラスではなく、MediaCentreが依存するものだけを明示する場合、その依存関係の役割に名前を付けて、それについて話すことができるという考えです。 MediaCentreが知る必要があるのは、ScheduledDevicesと通信することだけです。システムの残りの部分が変更されても、機能が変更されない限り、MediaCentreを変更する必要はありません。
それは役に立ちますか?