問題のクラスを分割する必要があります。
各クラスはいくつかの簡単なタスクを実行する必要があります。タスクが複雑すぎてテストできない場合、クラスが行うタスクは大きすぎます。
この設計の愚かさを無視する:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
更新
クラスのスタブメソッドの問題は、カプセル化に違反していることです。テストでは、オブジェクトの外部動作が仕様に一致するかどうかを確認する必要があります。オブジェクト内で何が起こっても、そのビジネスは何もありません。
FullNameがFirstNameとLastNameを使用するという事実は、実装の詳細です。クラス外では、それが真実であることを気にするべきではありません。オブジェクトをテストするためにパブリックメソッドをモックすることにより、そのオブジェクトが実装されていることを仮定しています。
将来のある時点で、その仮定は正しくなくなるかもしれません。おそらく、すべての名前ロジックは、Personが単に呼び出すNameオブジェクトに再配置されます。おそらくFullNameは、FirstNameとLastNameを呼び出すのではなく、メンバー変数first_nameとlast_nameに直接アクセスします。
2番目の質問は、なぜそうする必要があると感じるかです。結局、個人クラスは次のようにテストできます。
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
この例では、スタブの必要性を感じる必要はありません。あなたがそうするなら、あなたはスタブ幸せで、まあ...それを止めてください!とにかくメソッドの内容を制御できるので、そこでメソッドをモックするメリットはありません。
FullNameが使用するメソッドがモックされるのが理にかなっていると思われる唯一のケースは、なんとかFirstName()とLastName()が自明でない操作である場合です。たぶん、あなたはそれらのランダムな名前ジェネレータの1つを書いているか、FirstNameとLastNameが答えなどをデータベースに問い合わせています。しかし、それが起こっている場合は、オブジェクトがPersonクラスに属さない何かをしていることを示唆しています。
別の言い方をすれば、メソッドのモックとは、オブジェクトを取得して2つの部分に分割することです。片方がモックされ、もう片方がテストされています。あなたがしていることは、本質的にオブジェクトのアドホックな分割です。その場合は、オブジェクトを既に分割してください。
クラスが単純な場合、テスト中にクラスの一部をモックアウトする必要性を感じるべきではありません。クラスが複雑で、モックする必要があると感じる場合は、クラスをより単純な部分に分割する必要があります。
もう一度更新
私の見方では、オブジェクトには外部と内部の振る舞いがあります。外部の動作には、他のオブジェクトへの戻り値の呼び出しなどが含まれます。明らかに、そのカテゴリ内のすべてのものをテストする必要があります。(そうでなければ、何をテストしますか?)しかし、内部の動作は実際にテストされるべきではありません。
ここで、内部動作がテストされます。これは、外部動作の結果になるためです。しかし、私は、内部の動作を直接テストするのではなく、外部の動作を介して間接的にテストを作成します。
何かをテストしたい場合は、外部の動作になるように移動する必要があると考えています。そのため、何かをモックしたい場合は、オブジェクトを分割して、モックしたいものが問題のオブジェクトの外部動作に収まるようにする必要があると思います。
しかし、それはどのような違いをもたらしますか?FirstName()とLastName()が別のオブジェクトのメンバーである場合、FullName()の問題は本当に変わりますか?FirstNameをモックする必要があると判断した場合、LastNameは別のオブジェクトに存在するのに実際に役立ちますか?
モッキングアプローチを使用すると、オブジェクトに継ぎ目が作成されると思います。外部データソースと直接通信するFirstName()やLastName()などの関数があります。また、ないFullName()もあります。しかし、それらはすべて同じクラスに属しているため、明らかではありません。データソースに直接アクセスすることを想定していないものとそうでないものがあります。これらの2つのグループを分割するだけで、コードはより明確になります。
編集
一歩下がって質問してみましょう。テストするときにオブジェクトをモックするのはなぜですか?
- テストを一貫して実行する(実行ごとに変わるものへのアクセスを避ける)
- 高価なリソースへのアクセスを避けます(サードパーティのサービスなどにアクセスしないでください)
- テスト対象のシステムを簡素化する
- 考えられるすべてのシナリオ(つまり、障害のシミュレーションなど)をテストしやすくします。
- 他のコード部分の変更がこのテストを壊さないように、他のコード部分の詳細に依存することは避けてください。
さて、このシナリオには理由1〜4は当てはまらないと思います。fullnameをテストするときに外部ソースをモックすると、モッキングのこれらすべての理由が処理されます。処理されない唯一の要素は単純さですが、オブジェクトは十分に単純であるため、心配する必要はありません。
あなたの懸念は理由番号5だと思います。懸念は、将来のある時点でFirstNameとLastNameの実装を変更するとテストが中断されることです。将来、FirstNameとLastNameは別の場所またはソースから名前を取得する可能性があります。ただし、FullNameは常にになりますFirstName() + " " + LastName()
。そのため、FirstNameとLastNameをモックしてFullNameをテストする必要があります。
その場合、他のオブジェクトよりも変更される可能性が高い人物オブジェクトのサブセットがあります。オブジェクトの残りはこのサブセットを使用します。そのサブセットは現在、1つのソースを使用してデータを取得していますが、後日、まったく異なる方法でそのデータを取得する場合があります。しかし、私にとっては、そのサブセットは抜け出そうとする別個のオブジェクトのように思えます。
オブジェクトのメソッドをモックすると、オブジェクトを分割しているように思えます。しかし、あなたはアドホックな方法でそうしています。コードでは、Personオブジェクト内に2つの異なる部分があることを明確にしません。したがって、実際のコードでそのオブジェクトを単純に分割し、コードを読んで何が起こっているかが明確になるようにします。意味のあるオブジェクトの実際の分割を選択し、テストごとにオブジェクトを別々に分割しようとしないでください。
オブジェクトを分割することに反対するかもしれませんが、なぜですか?
編集
私は間違っていた。
個々のメソッドをモックしてアドホック分割を導入するのではなく、オブジェクトを分割する必要があります。ただし、オブジェクトを分割する1つの方法に集中しすぎました。ただし、オブジェクト指向はオブジェクトを分割する複数の方法を提供します。
私が提案するもの:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
多分それはあなたがずっとやっていたことです。しかし、各メソッドがどちらの側にあるのかを明確に描いているため、このメソッドにはモックメソッドで見た問題はないと思います。また、継承を使用することで、追加のラッパーオブジェクトを使用した場合に生じる厄介な問題を回避できます。
これは多少の複雑さをもたらしますが、いくつかのユーティリティ関数については、基礎となるサードパーティのソースをモックするだけでテストするでしょう。確かに、彼らは破壊の危険性が高まっていますが、再配置する価値はありません。分割する必要があるほど複雑なオブジェクトがある場合、このようなものは良い考えだと思います。