web-dev-qa-db-ja.com

単体テストのためだけにメソッドを公開することは悪い習慣ですか?

パブリックメソッドのクラスがあります。パブリックメソッドの目的で「支援」する他のメソッドがあります。 publicメソッドはテストする必要があります。ただし、プライベートメソッドの単体テストも行いたいと思います。

単体テストでプライベートメソッドを呼び出して個別にテストすることは不可能であるため、それらはパブリックでなければなりません。

ここでの問題は、OOP規約を尊重せずに純粋にユニットテストを実行できるようにするために作成されたと感じているコードが残っていることです。

純粋に「テスト容易性」のためにメソッドのアクセスを修正するのは正しいことですか?または、プライベートメソッドの機能を分離するために、構造全体を再考し、リファクタリングする必要があります(実際にはあまり機能せず、小さなヘルパーメソッドに似ていることもありますが)。

28
JᴀʏMᴇᴇ

はい、それは非常に悪い習慣です-あなたはあなたのツールにあなたに代わって設計決定をさせているのです。

ここでの主な問題は、個々のメソッドを1つの単位として扱うことです。これは通常、すべての単体テストの問題の原因です。メソッドが非常に複雑で多くのテストを必要とする場合を除いて、クラスをユニットとして扱う必要があります。 Martin Fowlerは、関連するクラスも1つの単位として(場合によっては)扱います。

その結果、クラスをインスタンス化してから、そのパブリックインターフェイスでメソッドを呼び出す必要があります。これにより、ドキュメントの例が提供され、全体が意図したとおりに機能することが保証されます。ここでは、パブリックメソッドを呼び出してプライベートメソッドをテストする必要があります。プライベートメソッドが呼び出されない場合でも、なぜコードに含まれているのですか。

31
gbjbaanb

「支援」するメソッドがある場合、単一の大きなメソッドが実際に多くのことを実行している可能性があります。小さいメソッドに分割し、これらのメソッドをパブリックインターフェイスを持つ個別のクラスに移動すると、大きなメソッドを持つクラスが1つのことだけを担当します(単一の責任の原則を参照)。メソッドを公開する必要があるため、この分離クラスへの移行により、テスト可能な構造が自動的に作成されます。

含まれている支出のリストを見て予算計算を行うBankAccountクラスを取ります。支出には、カテゴリ(スポーツ、食べ物、車など)、金額、日付があります。 BankCalcには、開始日と終了日、およびカテゴリを取得する「CalculateExpenditureFor」というメソッドがある場合があります。このメソッドは、日付とカテゴリに一致する支出のみを含むように支出をフィルタリングしてから、金額の合計に進みます。その要件を表現するために必要な 'ands'の量に注意してください!これは、メソッドが多すぎることを明確に示しています。

この方法は、実際には2つのことを行います。支出をフィルタリングし、金額を合計します。 BankAccountにExpenditureLedgerクラスが含まれていて、Expendituresが含まれているとします(「元帳」とは、金融取引のリストを含む本またはファイルであり、したがって名前です)。次に、日付とカテゴリでのフィルタリングを処理するExpenditureLedgerにFilterExpendituresメソッドを設定できます。作成された支出のリストは、銀行口座が金額を集計するために使用できます。

このシナリオでは、テストできるExpenditureLedgerのパブリックメソッドがあります(元帳のExpendituresにフィードして結果を確認します)。 ExpenditureLedgerから返された金額を合計するコードをテストできるように、(できればモック化された)ExpenditureLedgerを入力してBankAccountメソッドを確認することもできます。

9
JDT

メソッドを作成するpublic-はい、それは悪い習慣です。それらを作るinternal-それは依存します。

すべてのメソッドをパブリックにテストするのではなく、クラスを完全に再設計する代わりに、最も実用的な解決策は、メソッドを利害関係の「内部」にし、「InternalsVisibleTo」属性を使用してユニットテストがそれらにアクセスできるようにすることです。そもそも理想的なデザインにはならないかもしれませんが、時には物事を成し遂げる必要があるだけです。「内部」コードのテストは、テストをまったく行わないか、これらのプライベートメソッドをリファクタリングして誤って新しいバグを導入するよりも、多くの場合優れています。以前に記述されたユニットテストなしでクラスを分離します。

参照 実装の詳細を定義する方法?

6
Doc Brown

APIではなく内部実装の詳細のみを変更した場合は、ユニットテストを変更する必要はありません。変更後も古いテストが機能するという事実は、変更によって問題が解決されなかったことの証明です。コミットログを見て、テストコードの何も変更されていないことを確認し、安心することができます(テストが最初から良好である限り)。

ここで、実装を変更したときにテストを強制的に変更する設計を提案しています。あなたはその自信を破壊します。実装の変更をテストする過程で、本物のパブリックAPIテストに変更が加えられるリスクが常にあります。

オブジェクト階層が間違っていて、それらのメソッドが他のオブジェクトでパブリックである必要があるか、それらがオブジェクトの実装の詳細であり、それらをテストして時間を浪費している(そして、設計全体をより脆弱にするリスクがある)。契約をテストします。

重要な質問は、「なぜテストしたいですか?」です。これらのメソッドが適切な場所にあり、外部コードに関係がない場合は、コントロールフリークであるのをやめ、OCDを振り切り、システムを信頼する必要があります。それらをテストしないことが本当に問題である場合、現在のオブジェクト階層(またはAPI設計)が間違っています。

4
itsbruce