内部コンポーネントの単体テスト


14

クラス/モジュール/パッケージ/などの内部/プライベートコンポーネントをどの程度単体テストしますか?それらをすべてテストしますか、それとも外部とのインターフェースをテストするだけですか?これらの内部の例は、プライベートメソッドです。

例として、1つの中央プロシージャから呼び出されるいくつかの内部プロシージャ(関数/メソッド)を持つ再帰降下パーサーを想像してください。外の世界への唯一のインターフェイスは中央の手順であり、文字列を受け取り、解析された情報を返します。他のプロシージャは文字列のさまざまな部分を解析し、中央プロシージャまたは他のプロシージャから呼び出されます。

当然、外部文字列をサンプル文字列で呼び出し、手で解析した出力と比較して、外部インターフェイスをテストする必要があります。しかし、他の手順はどうですか?それらを個別にテストして、サブストリングが正しく解析されることを確認しますか?

私はいくつかの議論を考えることができます:

長所

  1. より多くのテストが常に優れており、これはコードカバレッジの向上に役立ちます
  2. 一部の内部コンポーネントは、外部インターフェイスに入力を与えることにより、特定の入力(エッジケースなど)を与えるのが難しい場合があります。
  3. より明確なテスト。内部コンポーネントに(修正された)バグがある場合、そのコンポーネントのテストケースは、バグがその特定のコンポーネントにあったことを明確にします

短所

  1. リファクタリングは非常に苦痛で時間がかかります。何かを変更するには、外部インターフェースのユーザーが影響を受けていない場合でも、ユニットテストを書き直す必要があります
  2. 一部の言語およびテストフレームワークでは許可されていません

あなたの意見は?


以下の可能性のある重複または大幅な重複:Programmers.stackexchange.com/questions/10832/…–
azheglov

回答:


8

ケース:「モジュール」(広義には、パブリックインターフェイスと、場合によってはプライベートな内部部分を持つもの)には、複雑で複雑なロジックが含まれています。モジュールインターフェイスのみをテストすることは、モジュールの内部構造に関連する一種の統合テストです。したがって、エラーが見つかった場合、そのようなテストでは、障害の原因となっている正確な内部部品/コンポーネントを特定できません。

解決策:複雑な内部パーツをモジュール自体に変換し、単体テストを行い(複雑すぎる場合はこれらの手順を繰り返します)、元のモジュールにインポートします。これで、uniit-test(動作が正しいことを確認し、エラーを修正する)を簡単に行うためのモジュールのセットが完成しました。それだけです。

注意:

  • 「サブモジュール」が新しい/変更された契約を履行するのに十分なサービスを提供しない限り、モジュールの契約を変更するときに、モジュールの(以前の)「サブモジュール」のテストで何も変更する必要はありません。

  • 不必要に公開されるものはありません。つまり、モジュールの契約が保持され、カプセル化が維持されます。

[更新]

オブジェクトの内部インターフェイス(プライベートにインポートされたモジュール/パッケージではないメンバーを意味する)をオブジェクトのパブリックインターフェイス経由で入力するだけで適切な状態にするのが難しい場合に、スマートな内部ロジックをテストするには:

  • 友だち(C ++の用語)またはパッケージ(Java)が実際に内部から状態を設定し、必要に応じて動作をテストするためのいくつかのテストコードを持っているだけです。

    • これにより、カプセル化が再び解除されることはなく、テスト目的で内部に簡単に直接アクセスできます。テストを「ブラックボックス」として実行し、リリースビルドでコンパイルするだけです。

リストのレイアウトが少し壊れているようです;(
mlvljr

1
いい答えです。.NETでは、[assembly: InternalsVisibleTo("MyUnitTestAssembly")]属性を使用してAssemblyInfo.cs内部をテストできます。不正行為のように感じます。
誰も

@rmx何かが必要な基準をすべて満たすと、実際のチートと共通点がある場合でも、チートは発生しません。しかし、モジュール間/モジュール内アクセスのトピックは、現代の主流言語では実際に少し工夫されています。
mlvljr

2

FSMベースのコードへのアプローチは、従来から使用されているアプローチとは少し異なります。ここで説明するハードウェアテスト(通常はFSM)に非常によく似ています。

つまり、特定の出力を生成するだけでなく、特定の「不良」出力を生成するときに、障害の性質によって障害のあるコンポーネントを特定できるテスト入力シーケンス(またはテスト入力シーケンスのセット)を作成します。このアプローチは非常にスケーラブルであり、テスト設計に費やす時間が長いほど、テストは良くなります。

この種のテストは、いわゆる「機能テスト」に近いものですが、実装に少し手を加えるたびにテストを変更する必要がなくなります。


2

まあ-それは依存します:-)。BDD(Behaviour Driven Development)またはATDD(Acceptance Test Driven Development)アプローチに従っている場合、パブリックインターフェイスのテストは問題ありません(さまざまな入力で徹底的にテストする限り)。実際に重要です。

ただし、特定の時間枠内または特定のbigO曲線(nlognなど)に沿ってそのアルゴリズムの一部を実行する場合は、個々の部分のテストが重要です。これを従来のTDD /ユニットテストアプローチと呼ぶ人もいます。

すべてと同様に、YMMV


1

例えば、機能的な意味を持つ複数の部分に分割しParseQuotedString()ParseExpression()ParseStatement()ParseFile()及びそれらをすべて公開します。構文が大きく変化して、これらが無関係になる可能性はどのくらいありますか?


1
このアプローチにより、カプセル化が弱くなり、インターフェースが大きくなり、使用/理解が難しくなります。
サラ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.