カスタムオブジェクトがあると仮定しますStudent:
public class Student{
public int _id;
public String name;
public int age;
public float score;
}
そして、クラスWindowはStudentの情報を表示するために使用されます:
public class Window{
public void showInfo(Student student);
}
それはかなり正常に見えますが、関数を呼び出すには実際のStudentオブジェクトが必要であるため、Windowを個別にテストするのは簡単ではありません。そのため、showInfoを変更してStudentオブジェクトを直接受け入れないようにします。
public void showInfo(int _id, String name, int age, float score);
テストが簡単になるようにWindow個別に:
showInfo(123, "abc", 45, 6.7);
しかし、変更されたバージョンには別の問題があることがわかりました。
学生の変更(例:新しいプロパティの追加)では、showInfoのmethod-signatureを変更する必要があります
Studentに多くのプロパティがある場合、Studentのメソッドシグネチャは非常に長くなります。
したがって、カスタムオブジェクトをパラメーターとして使用するか、オブジェクトの各プロパティをパラメーターとして受け入れると、どちらがより保守しやすくなりますか?
カスタムオブジェクトを使用して関連するパラメータをグループ化することは、実際には推奨されるパターンです。リファクタリングとして、それは Introduce Parameter Object と呼ばれます。
あなたの問題は別の場所にあります。まず、ジェネリックWindow
はStudentについて何も知らないはずです。代わりに、StudentWindow
のみを表示することを認識しているStudents
が必要です。次に、Student
にStudentWindow
のテストを大幅に複雑にするような複雑なロジックが含まれていない限り、Student
をテストしてStudentWindow
をテストすることはまったく問題ありません。 ] _。そのロジックがある場合は、Student
をインターフェイスにしてモック化することをお勧めします。
あなたはそうだと言う
関数を呼び出すには実際のStudentオブジェクトが必要なので、個別にテストするのは簡単ではありません。
ただし、ウィンドウに渡す生徒オブジェクトを作成するだけです。
showInfo(new Student(123,"abc",45,6.7));
呼び出すのはそれほど複雑ではないようです。
簡単に言えば:
編集:
@ Tom.Bowen89のように、showInfoメソッドをテストすることはそれほど複雑ではありません。
showInfo(new Student(8812372,"Peter Parker",16,8.9));
Code CompleteのSteve McConnellがまさにこの問題に対処し、プロパティを使用する代わりにメソッドにオブジェクトを渡すことの利点と欠点について説明しました。
私が本にアクセスしてから1年以上経過しているため、詳細の一部が間違っている場合は、私を許してください。
彼は、オブジェクトを使用せず、メソッドに絶対に必要なプロパティのみを送信するほうがよいという結論に達しました。メソッドは、操作の一部として使用するプロパティ以外のオブジェクトについて何も知る必要はありません。また、時間の経過とともにオブジェクトが変更されると、オブジェクトを使用するメソッドに意図しない結果が生じる可能性があります。
また、多くの異なる引数を受け入れるメソッドになってしまう場合は、そのメソッドが多すぎることを示しているため、より小さなメソッドに分解する必要があることを示しています。
ただし、場合によっては、実際には多くのパラメーターが必要になることがあります。彼が与える例は、多くの異なるアドレスプロパティを使用して完全なアドレスを構築するメソッドです(ただし、これは、文字列配列を使用することで回避できます)。
オブジェクト全体を渡すと、テストの作成と読み取りがはるかに簡単になります。
public class AStudentView {
@Test
public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
StudentView view = aStudentView();
view.show(aStudent().withAFailingGrade().build());
Assert.that(view, displaysFailingGradeWarning());
}
private Matcher<StudentView> displaysFailingGradeWarning() {
...
}
}
比較のために、
view.show(aStudent().withAFailingGrade().build());
値を個別に渡すと、次のように行が記述されます。
showAStudentWithAFailingGrade(view);
実際のメソッド呼び出しがどこかに埋め込まれている場合
private showAStudentWithAFailingGrade(StudentView view) {
int someId = .....
String someName = .....
int someAge = .....
// why have been I peeking and poking values I don't care about
decimal aFailingGrade = .....
view.show(someId, someName, someAge, aFailingGrade);
}
つまり、実際のメソッド呼び出しをテストに含めることができないということは、APIが悪いことを示しています。
良い答えはたくさんありますが、別の解決策が見つかる可能性があるいくつかの提案を次に示します。
あなたの例は、ウィンドウ(明らかにビューレベルのオブジェクト)に渡される生徒(明らかにモデルオブジェクト)を示しています。中間のControllerまたはPresenterオブジェクトは、まだ持っていない場合に役立ち、モデルからユーザーインターフェイスを分離できます。コントローラー/プレゼンターは、UIテストの代わりに使用できるインターフェースを提供し、インターフェースを使用してモデルオブジェクトとビューオブジェクトを参照し、テストの両方から分離できるようにする必要があります。これらを作成またはロードする抽象的な方法を提供する必要がある場合があります(ファクトリオブジェクト、リポジトリオブジェクトなど)。
モデルオブジェクトの関連部分をデータ転送オブジェクトに転送することは、モデルが複雑になりすぎた場合にインターフェースをとるための便利なアプローチです。
生徒がインターフェース分離原則に違反している可能性があります。その場合は、操作しやすい複数のインターフェイスに分割すると効果的です。
遅延読み込みを使用すると、大きなオブジェクトのグラフを簡単に作成できます。
あなたは意味のあるもの、いくつかのアイデアを渡す必要があります:
テストが簡単です。オブジェクトを編集する必要がある場合、最もリファクタリングが必要なものは何ですか?この関数を他の目的で再利用することは役に立ちますか?この機能を使用するためにこの機能を提供するために最低限必要な情報は何ですか? (このコードを分割することで、このコードを再利用できるようになる可能性があります)、この関数を作成するための設計の穴に陥り、すべてをボトルネックにしてこのオブジェクトを排他的に使用することに注意してください。
これらのプログラミングルールはすべて、正しい方向に考えるためのガイドにすぎません。コードビーストを作成しないでください。わからない場合、続行する必要がある場合は、方向/自分の提案または提案をここで選択してください。「ああ、私はこれを作成したはずです」方法 '-おそらく、戻ってかなり簡単にリファクタリングできます。 (たとえば、Teacherクラスがある場合、それはStudentと同じプロパティセットを必要とし、Personフォームのオブジェクトを受け入れるように関数を変更します)
メインのオブジェクトを渡しておくのが一番です。コードの記述方法によって、この関数が何をしているのかを簡単に説明できるからです。
これに関する一般的なルートの1つは、2つのプロセスの間にインターフェースを挿入することです。
public class Student {
public int id;
public String name;
public int age;
public float score;
}
interface HasInfo {
public String getInfo();
}
public class StudentInfo implements HasInfo {
final Student student;
public StudentInfo(Student student) {
this.student = student;
}
@Override
public String getInfo() {
return student.name;
}
}
public class Window {
public void showInfo(HasInfo info) {
}
}
これは時々少し面倒になりますが、内部クラスを使用する場合は、Java)で少し整然としています。
interface HasInfo {
public String getInfo();
}
public class Student {
public int id;
public String name;
public int age;
public float score;
public HasInfo getInfo() {
return new HasInfo () {
@Override
public String getInfo() {
return name;
}
};
}
}
次に、偽のWindow
オブジェクトを指定するだけで、HasInfo
クラスをテストできます。
これは Decorator Pattern の例だと思います。
追加
コードの単純さが原因で混乱が生じているようです。テクニックをよりよく示す別の例を次に示します。
interface Drawable {
public void Draw(Pane pane);
}
/**
* Student knows nothing about Window or Drawable.
*/
public class Student {
public int id;
public String name;
public int age;
public float score;
}
/**
* DrawsStudents knows about both Students and Drawable (but not Window)
*/
public class DrawsStudents implements Drawable {
private final Student subject;
public DrawsStudents(Student subject) {
this.subject = subject;
}
@Override
public void Draw(Pane pane) {
// Draw a Student on a Pane
}
}
/**
* Window only knows about Drawables.
*/
public class Window {
public void showInfo(Drawable info) {
}
}
これは実際にはまともな質問です。ここでの本当の問題は、一般的な用語「オブジェクト」の使用です。これは少しあいまいな場合があります。
一般に、古典的なOOP言語では、「オブジェクト」という用語は「クラスインスタンス」を意味するようになりました。クラスインスタンスはかなり重い場合があります-パブリックプロパティとプライベートプロパティ(およびその間のプロパティ)、メソッド、継承、依存関係など。いくつかのプロパティを単に渡すために、そのようなものを実際に使用する必要はないでしょう。
この場合、いくつかのプリミティブを単に保持するコンテナーとしてオブジェクトを使用しています。 C++では、これらのようなオブジェクトはstructs
として知られていました(C#などの言語にも存在します)。構造体は、実際には、あなたが話す用途に正確に合わせて設計されました。それらは、論理的な関係があったときに、関連するオブジェクトとプリミティブをグループ化しました。
ただし、現代の言語では、構造体とクラスの間に実際の違いはありませんコードを記述しているときなので、オブジェクトを使用しても問題ありません。 (ただし、背後では、注意が必要ないくつかの違いがあります。たとえば、構造体は参照型ではなく値型です。)基本的に、オブジェクトを単純に保つ限り、それは簡単です手動でテストします。しかし、最近の言語とツールを使用すると、これをかなり軽減できます(インターフェース、モックフレームワーク、依存性注入などを介して)。