私は、いくつかの新しいモジュールを実装してユニットテストする必要があるプロジェクトに取り組んでいます。私はかなり明確なアーキテクチャを念頭に置いていたので、メインのクラスとメソッドをすぐに書き留めてから、単体テストの作成を開始しました。
テストを書いている間、次のような元のコードにかなりの修正を加える必要がありました。
- それらをテストするためにプライベートメソッドをパブリックにする
- プライベート変数にアクセスするためのメソッドを追加する
- ユニットテスト内でコードを実行するときに使用するモックオブジェクトを挿入するためのメソッドを追加します。
どういうわけか私はこれらが私たちが何か間違ったことをしている症状であると感じています、例えば
- 最初の設計が間違っていた(一部の機能は最初から公開されているはずでした)、
- コードは、単体テストとのインターフェース用に適切に設計されていません(おそらく、かなりの数のクラスが既に設計されているときに単体テストの設計を開始したという事実が原因です)。
- ユニットテストを間違った方法で実装している(たとえば、ユニットテストはAPIのパブリックメソッドを直接テスト/アドレスするだけであり、プライベートメソッドをアドレスするべきではない)。
- 上記の3つのポイントの混合、およびおそらく私が考えていなかったいくつかの追加の問題。
単体テストの経験はあるものの、第一人者にはほど遠いので、これらの問題についてのあなたの考えを読んでいただければ非常に興味があります。
上記の一般的な質問に加えて、より具体的で技術的な質問があります。
質問1.クラスAのプライベートメソッドmを直接テストし、それをテストするためにパブリックにすることは理にかなっていますか?または、mを呼び出す他のパブリックメソッドを対象とする単体テストによってmが間接的にテストされると想定する必要がありますか?
質問2.クラスAのインスタンスにクラスB(複合集約)のインスタンスが含まれている場合、AをテストするためにBを模擬することは理にかなっていますか?私の最初のアイデアは、BインスタンスはAインスタンスの一部であるため、Bをモック化しないことですが、それからこれについて疑い始めました。Bをモックすることに対する私の議論は、1と同じです:Bはプライベートwrt Aであり、その実装にのみ使用されます。しかし、これらの問題は設計上の欠陥を示している可能性があります。おそらく、複合集約ではなく、AからBへの単純な関連付けを使用する必要があります。
質問3.上記の例で、Bをモックすることにした場合、どのようにBインスタンスをAに注入しますか?ここに私たちが持っていたいくつかのアイデアがあります:
- AコンストラクターでBインスタンスを作成する代わりに、Aインスタンスの引数としてBインスタンスを注入します。
- BFactoryインターフェースを引数としてAコンストラクターに渡し、Aがファクトリーを使用してプライベートBインスタンスを作成できるようにします。
- AにプライベートなBFactoryシングルトンを使用します。シングルトンを設定するには、静的メソッドA :: setBFactory()を使用します。Aが設定されている場合(テストシナリオ)、ファクトリシングルトンを使用するBインスタンスを作成する場合、シングルトンが設定されていない場合(実稼働コードシナリオ)、Bを直接作成します。
最初の2つの代替案は私にはきれいに見えますが、Aコンストラクターのシグニチャーを変更する必要があります。APIを変更してテストしやすくすることは、私には不便に思えますが、これは一般的な方法ですか?
3番目の方法には、コンストラクターのシグニチャーを変更する必要がない(APIへの変更の影響が少ない)利点がありますが、テストを開始する前に静的メソッドsetBFactory()を呼び出す必要があります。これはIMOエラーが発生しやすい(テストが適切に機能するためのメソッド呼び出しへの暗黙の依存関係)。どちらを選べばいいのかわかりません。