web-dev-qa-db-ja.com

内部実装をテストする必要がありますか、それとも公共の行動のみをテストする必要がありますか?

与えられたソフトウェア...

  • システムはいくつかのサブシステムで構成されています
  • 各サブシステムは、いくつかのコンポーネントで構成されています
  • 各コンポーネントは、多くのクラスを使用して実装されます

...各サブシステムまたはコンポーネントの自動テストを作成するのが好きです。

コンポーネントの内部クラスごとにテストを作成しません(ただし、各クラスがコンポーネントのパブリック機能に貢献しているため、コンポーネントのパブリックAPIを介して外部からテスト/テストできる場合を除きます)。

したがって、コンポーネントの実装をリファクタリングするとき(新しい機能の追加の一環として頻繁に行います)、既存の自動テストを変更する必要はありません。テストはコンポーネントのパブリックAPIとパブリックAPIにのみ依存するためです。通常、変更されるのではなく拡張されます。

このポリシーは、 リファクタリングテストコード のようなドキュメントとは対照的だと思います。

  • 「...ユニットテスト...」
  • 「...システム内のすべてのクラスのテストクラス...」
  • 「...テストコード/製品コードの比率...理想的には1:1の比率に近づくと考えられています...」

...私が同意しないと思う(または少なくとも練習しない)すべて。

私の質問は、私の方針に同意できない場合、その理由を説明していただけますか?この程度のテストでは不十分なシナリオは何ですか?

要約すれば:

  • パブリックインターフェイスはテスト(および再テスト)され、変更されることはめったにありません(追加されますが、変更されることはめったにありません)
  • 内部APIはパブリックAPIの背後に隠されており、パブリックAPIをテストするテストケースを書き直すことなく変更できます。

脚注:私の「テストケース」のいくつかは、実際にはデータとして実装されています。たとえば、UIのテストケースは、さまざまなユーザー入力と対応する予想されるシステム出力を含むデータファイルで構成されています。システムのテストとは、各データファイルを読み取り、システムへの入力を再生し、対応する期待される出力を取得することを表明するテストコードを用意することを意味します。

テストコードを変更する必要はめったにありませんが(パブリックAPIは通常、変更されるのではなく追加されるため)、既存のデータファイルを変更する必要がある場合があります(週に2回など)。これは、システム出力をより適切に変更した場合(つまり、新しい機能によって既存の出力が改善された場合)に発生する可能性があり、既存のテストが「失敗」する可能性があります(テストコードは出力が変更されていないことを表明しようとするだけなので)。これらのケースを処理するために、私は次のことを行います。

  • 自動テストスイートを再実行します。このフラグは、出力をアサートせず、代わりに新しい出力を新しいディレクトリにキャプチャするように指示する特別なランタイムフラグです。
  • ビジュアル差分ツールを使用して、どの出力データファイル(つまり、どのテストケース)が変更されたかを確認し、これらの変更が適切であり、新しい機能が与えられた場合に期待どおりであることを確認します
  • 新しい出力ファイルを新しいディレクトリからテストケースの実行元のディレクトリにコピーして、既存のテストを更新します(古いテストを上書きします)

脚注:「コンポーネント」とは、「1つのDLL」や「1つのアセンブリ」などを意味します。システムのアーキテクチャや配置図に表示されるのに十分な大きさで、多くの場合、数十または100のクラスを使用して実装されます。約1つまたは少数のインターフェイスのみで構成されるパブリックAPIを使用します...開発者の1つのチームに割り当てられる可能性のあるもの(別のコンポーネントが別のチームに割り当てられる場合)、したがって コンウェイの法則 比較的安定したパブリックAPIを持っています。


脚注:記事オブジェクト指向テスト:神話と現実は、

神話:ブラックボックステストで十分です。クラスのインターフェイスまたは仕様を使用してテストケースの設計を慎重に行うと、クラスが確実になります。完全に行使されました。ホワイトボックステスト(テストを設計するためのメソッドの実装を調べる)は、カプセル化の概念そのものに違反します。

現実:OO構造が重要、パートII。多くの研究は、ブラックボックステストスイートが耐え難いほどであると考えられていることを示しています開発者による徹底的なテストでは、テスト対象の実装でステートメントの3分の1から半分(パスまたは状態は言うまでもなく)のみを実行します。これには3つの理由があります。まず、選択した入力または状態は通常、通常のパスを実行しますが、実行しません。考えられるすべてのパス/状態を強制します。次に、ブラックボックステストだけでは驚きを明らかにすることはできません。テスト対象のシステムの指定された動作をすべてテストしたとします。不特定の動作がないことを確認するために、パーツがあるかどうかを知る必要があります。この情報を取得する唯一の方法は、コードインストルメンテーションによるものです。第3に、ソースコードを調べずに例外やエラー処理を実行することは、多くの場合困難です。

ホワイトボックス機能テストを行っていることを追加する必要があります。コードを(実装で)確認し、さまざまなコードブランチ(機能の実装の詳細)を実行するための機能テスト(パブリックAPIを駆動する)を作成します。

43
ChrisW

私の練習は、パブリックAPI/UIを介して内部をテストすることです。一部の内部コードに外部から到達できない場合は、それを削除するためにリファクタリングします。

18
mouviciel

答えは非常に簡単です。ソフトウェアQAの重要な部分である機能テストについて説明しています。内部実装のテストは単体テストです。これは、異なる目標を持つソフトウェアQAの別の部分です。だからこそ、人々はあなたのアプローチに反対していると感じているのです。

機能テストは、システムまたはサブシステムが想定どおりに機能することを検証するために重要です。顧客が目にするものはすべて、この方法でテストする必要があります。

ユニットテストは、今書いた10行のコードが想定どおりに機能することを確認するためにここにあります。これにより、コードの信頼性が高まります。

どちらも補完的です。既存のシステムで作業している場合、おそらく最初に作業するのは機能テストです。ただし、コードを追加したらすぐに、単体テストを行うこともお勧めします。

30
Philippe F

Lakos のコピーが目の前にないので、引用するのではなく、すべてのレベルでテストが重要である理由を説明するよりも、彼の方が優れていることを指摘します。

「公の行動」のみをテストする場合の問題は、そのようなテストではほとんど情報が得られないことです。 (コンパイラが多くのバグをキャッチするのと同じように)多くのバグをキャッチしますが、バグがどこにあるかはわかりません。不適切に実装されたユニットは、長期間にわたって適切な値を返し、条件が変化したときにそれを停止するのが一般的です。そのユニットが直接テストされていたとしたら、それがうまく実装されていなかったという事実はもっと早く明らかになったでしょう。

テストの粒度の最適なレベルは、ユニットレベルです。インターフェースを介して各ユニットのテストを提供します。これにより、各コンポーネントがどのように動作するかについての信念を検証および文書化できます。これにより、導入された新しい機能のみをテストすることで依存コードをテストでき、テストを短くして目標を達成できます。ボーナスとして、それは彼らがテストしているコードでテストを続けます。

別の言い方をすれば、公開されているすべてのクラスに公開されている動作があることに気付いている限り、公開されている動作のみをテストするのが正しいです。

9
darch

これまでのところ、この質問に対する素晴らしい回答がたくさんありますが、私自身のメモをいくつか追加したいと思います。序文として:私は、幅広い大規模なクライアントにテクノロジーソリューションを提供する大企業のコンサルタントです。私の経験では、ほとんどのソフトウェアショップよりもはるかに徹底的にテストする必要があるためです(おそらくAPI開発者を除いて)。品質を確保するために実行する手順の一部を次に示します。

  • 内部ユニットテスト:
    開発者は、作成するすべてのコードの単体テストを作成する必要があります(読み取り:すべてのメソッド)。単体テストは、正のテスト条件(メソッドは機能しますか?)と負のテスト条件(必須の引数の1つがnullの場合にメソッドはArgumentNullExceptionをスローしますか?)をカバーする必要があります。通常、CruiseControl.netなどのツールを使用して、これらのテストをビルドプロセスに組み込みます。
  • システムテスト/アセンブリテスト:
    このステップは別のステップと呼ばれることもありますが、これはパブリック機能のテストを開始するときです。個々のユニットがすべて期待どおりに機能することがわかったら、外部機能も期待どおりに機能することを知りたいと思います。目標はシステム全体が正常に機能するかどうかを判断することであるため、これは機能検証の一形態です。これには統合ポイントが含まれていないことに注意してください。システムテストでは、実際のインターフェイスではなくモックアップインターフェイスを使用して、出力を制御し、その周りにテストケースを構築できるようにする必要があります。
  • システム統合テスト:
    プロセスのこの段階で、統合ポイントをシステムに接続する必要があります。たとえば、クレジットカード処理システムを使用している場合は、この段階でライブシステムを組み込んで、それがまだ機能することを確認する必要があります。システム/アセンブリテストと同様のテストを実行する必要があります。
  • 機能検証テスト:
    機能検証とは、システムを実行しているユーザー、またはAPIを使用して期待どおりに機能することを検証するユーザーのことです。請求システムを構築した場合、これは、テストスクリプトをエンドツーエンドで実行して、すべてが設計どおりに機能することを確認する段階です。これは、あなたが仕事をしたかどうかを教えてくれるので、明らかにプロセスの重要な段階です。
  • 認証テスト:
    ここでは、実際のユーザーをシステムの前に置き、実際のユーザーに試してもらいます。理想的には、利害関係者とのある時点でユーザーインターフェイスをすでにテストしていますが、この段階では、ターゲットオーディエンスが製品を気に入っているかどうかがわかります。これは、他のベンダーによる「リリース候補」のようなものと呼ばれることを聞いたことがあるかもしれません。この段階ですべてがうまくいけば、本番環境に移行しても問題はありません。認定テストは、本番環境で使用するのと同じ環境(または少なくとも同じ環境)で常に実行する必要があります。

もちろん、誰もがこのプロセスに従うわけではないことを私は知っていますが、それを端から端まで見ると、個々のコンポーネントの利点を理解し始めることができます。ビルド検証テストは別のタイムライン(毎日など)で行われるため、ここには含めていません。単体テストは、アプリケーションのどの特定のコンポーネントがどの特定のユースケースで失敗しているかについての深い洞察を提供するため、個人的に重要であると私は信じています。単体テストは、どのメソッドが正しく機能しているかを特定するのにも役立ちます。これにより、メソッドに問題がない場合に、障害に関する詳細情報を探すために時間を費やす必要がなくなります。

もちろん、単体テストも間違っている可能性がありますが、機能/技術仕様からテストケースを開発する場合(1つありますよね?;))、それほど問題はないはずです。

8
Ed Altorfer

純粋なテスト駆動開発を実践している場合は、失敗したテストがあった後にのみコードを実装し、失敗したテストがない場合にのみテストコードを実装します。さらに、失敗または合格のテストを行うための最も単純なもののみを実装します。

限られたTDDの実践で、これがコードによって生成されたすべての論理条件の単体テストをフラッシュするのにどのように役立つかを見てきました。私のプライベートコードの論理機能の100%がパブリックインターフェイスによって公開されているとは完全に確信していません。 TDDの実践はその指標を補完するように見えますが、パブリックAPIで許可されていない隠された機能がまだあります。

この方法は、パブリックインターフェイスの将来の欠陥から私を保護すると言えるでしょう。これが便利である(そして新しい機能をより迅速に追加できる)か、時間の無駄であることがわかります。

2
Karl the Pagan

機能テストをコーディングできます。それはいいです。ただし、実装のテストカバレッジを使用して検証し、テスト対象のコードがすべて機能テストに関連する目的を持っていること、および実際に関連する何かを実行することを実証する必要があります。

2
Ira Baxter

ユニット==クラスだと盲目的に考えるべきではありません。それは逆効果になると思います。私がユニットテストを書くと言うとき、私は論理ユニットをテストしています-いくつかの振る舞いを提供する「何か」。ユニットは単一のクラスの場合もあれば、複数のクラスが連携してその動作を提供する場合もあります。単一のクラスとして開始されることもありますが、後で3つまたは4つのクラスに進化します。

1つのクラスから始めてそのテストを作成したが、後で複数のクラスになる場合、通常は他のクラスに個別のテストを作成しません。これらは、テスト対象のユニットの実装の詳細です。このようにして、デザインを成長させることができ、テストはそれほど脆弱ではありません。

私は以前、この質問でCrisWが示したように、より高いレベルでのテストの方が良いと考えていましたが、経験を積んだ後、私の考えはそれと「すべてのクラスにテストクラスが必要」の間の何かに緩和されます。すべてのユニットにテストが必要ですが、以前とは少し異なるユニットを定義することにしました。これはCrisWが話している「コンポーネント」かもしれませんが、非常に多くの場合、単一のクラスでもあります。

さらに、機能テストは、システムが想定どおりに機能することを証明するのに十分な場合がありますが、例/テスト(TDD/BDD)を使用して設計を推進する場合は、レバーの低いテストが当然の結果です。実装が完了したら、これらの低レベルのテストを破棄することもできますが、それは無駄になります。テストはプラスの副作用です。低レベルのテストを無効にする大幅なリファクタリングを行うことにした場合は、それらを破棄して、一度新しいものを作成します。

ソフトウェアのテスト/証明の目標を分離し、テスト/例を使用して設計/実装を推進することで、この議論を明確にすることができます。

更新:また、TDDを実行するには、基本的に2つの方法があります。outside-inとinside-outです。 BDDはoutside-inを促進し、それがより高いレベルのテスト/仕様につながります。ただし、詳細から始める場合は、すべてのクラスの詳細なテストを作成します。

1
Torbjørn

あなたはまだこのアプローチに従っていますか?また、これは正しいアプローチだと思います。パブリックインターフェイスのみをテストする必要があります。現在、パブリックインターフェイスは、ある種のUIまたはその他のソースから入力を受け取るサービスまたはコンポーネントにすることができます。

ただし、Test Firstアプローチを使用して、puplicサービスまたはコンポーネントを進化させることができるはずです。つまり、パブリックインターフェイスを定義し、基本的な機能をテストします。失敗します。バックグラウンドクラスAPIを使用してその基本機能を実装します。この最初のテストケースのみを満たすようにAPIを記述します。次に、サービスがさらに何をして進化できるかを尋ね続けます。

行うべきバランスの決定は、1つの大きなサービスまたはコンポーネントを再利用可能ないくつかの小さなサービスおよびコンポーネントに分割することだけです。コンポーネントがプロジェクト全体で再利用できると強く信じている場合。次に、そのコンポーネントの自動テストを作成する必要があります。ただし、大きなサービスまたはコンポーネント用に作成されたテストは、コンポーネントとしてすでにテストされた機能を複製する必要があります。

特定の人々は、これがユニットテストではないという理論的な議論に入るかもしれません。だからそれでいい。基本的な考え方は、ソフトウェアをテストする自動テストを用意することです。では、ユニットレベルでない場合はどうでしょうか。それがデータベース(あなたが制御する)との統合をカバーするなら、それはそれだけより良いです。

あなたがあなたのために働く良いプロセスを開発したかどうか私に知らせてください..あなたの最初の投稿以来..

ameetに関して

1
Shameet

私はここのほとんどの投稿に同意しますが、これを追加します:

パブリックインターフェイスをテストし、次に保護し、次にプライベートにすることを最優先します。

通常、パブリックインターフェイスと保護されたインターフェイスは、プライベートインターフェイスと保護されたインターフェイスの組み合わせの要約です。

個人的に:すべてをテストする必要があります。小さな関数の強力なテストセットが与えられると、その隠されたメソッドが機能するという高い信頼性が得られます。また、リファクタリングに関する他の人のコメントにも同意します。コードカバレッジは、コードの余分なビットがどこにあるかを判断し、必要に応じてそれらをリファクタリングするのに役立ちます。

1
monksy

[私自身の質問への回答]

おそらく、非常に重要な変数の1つは、コーディングしているさまざまなプログラマーの数です。

  • 公理:各プログラマーは独自のコードをテストする必要があります

  • したがって、プログラマーが1つの「ユニット」を作成して配信する場合は、おそらく「ユニットテスト」を作成することによって、そのユニットもテストする必要があります。

  • 当然の結果:1人のプログラマーがパッケージ全体を作成する場合、プログラマーはパッケージ全体の機能テストを作成するだけで十分です(これらのユニットは他のプログラマーが実装の詳細であるため、パッケージ内のユニットの「ユニット」テストを作成する必要はありません)。直接アクセス/暴露はありません)。

同様に、テストできる「モック」コンポーネントを構築する方法は次のとおりです。

  • 2つのチームが2つのコンポーネントを構築している場合、コンポーネントが後続の「統合テスト」の準備ができていると見なされる前に、それぞれが他のコンポーネントを「モック」して、自分のコンポーネントをテストするための何か(モック)を用意する必要があります。他のチームがコンポーネントを提供する前に、コンポーネントをテストできます。

  • システム全体を開発している場合は、システム全体を拡張できます。たとえば、新しいGUIフィールド、新しいデータベースフィールド、新しいビジネストランザクション、および1つの新しいシステム/機能テストをすべて1つの一部として開発します。反復。レイヤーの「モック」を開発する必要はありません(代わりに本物に対してテストできるため)。

0
ChrisW

保護されたパーツも、継承されたタイプに対して「パブリック」であるため、個人的にテストします。

0
Ali Shafai

それはあなたのデザインと最大の価値がどこにあるかによります。あるタイプのアプリケーションは、別のタイプへの異なるアプローチを要求する場合があります。機能/統合テストが驚きをもたらすのに対して、ユニットテストで面白いものをほとんど捕まえないことがあります。ユニットテストは、開発中に何百回も失敗し、作成中の多くのバグをキャッチすることがあります。

時にはそれは些細なことです。一部のクラスが連携する方法により、すべてのパスをテストする投資収益率が低下するため、線を引いて、より重要な/複雑な/頻繁に使用されるものをハンマーで叩くことに進むことができます。

特に興味深いロジックが潜んでいるため、パブリックAPIをテストするだけでは不十分な場合があります。また、システムを動かして特定のパスを実行するのは非常に面倒です。それはそれの内臓をテストすることが報われるときです。

最近、私は1つか2つのことを行う多くの(しばしば非常に)単純なクラスを書く傾向があります。次に、複雑な機能をすべてそれらの内部クラスに委任することにより、目的の動作を実装します。つまり私は少し複雑な相互作用を持っていますが、本当に単純なクラスです。

実装を変更して、それらのクラスの一部をリファクタリングする必要がある場合、通常は気にしません。私はできる限りテストを隔離しているので、テストを再び機能させるための簡単な変更であることがよくあります。ただし、doいくつかの内部クラスを破棄する必要がある場合は、いくつかのクラスを置き換えて、代わりにまったく新しいテストを作成することがよくあります。リファクタリング後にテストを最新の状態に保つ必要があると不満を言う人をよく耳にします。それは避けられないこともあり、面倒なこともありますが、粒度のレベルが十分であれば、コードとテストを破棄することは通常大したことではありません。

これは、テスト容易性を考慮した設計と煩わしくない設計の大きな違いの1つだと思います。

0
Mark Simpson

コードカバレッジは理想的には100%である必要があることに同意します。これは、必ずしも60行のコードに60行のテストコードがあることを意味するわけではありませんが、各実行パスがテストされることを意味します。バグよりも厄介なのは、まだ実行されていないバグだけです。

パブリックAPIをテストするだけで、内部クラスのすべてのインスタンスをテストしないというリスクが発生します。私はそれを言って本当に明白なことを述べていますが、それは言及されるべきだと思います。それぞれの動作をテストすればするほど、動作が壊れていることだけでなく、何が壊れているかを認識しやすくなります。

0
ryanv

プライベート実装の詳細とパブリックインターフェイスをテストします。実装の詳細を変更し、新しいバージョンにバグがある場合、これにより、エラーが何に影響しているのかだけでなく、エラーが実際にどこにあるのかをより正確に把握できます。

0
Nathaniel Flath

公理:各プログラマーは独自のコードをテストする必要があります

これは普遍的に真実ではないと思います。

暗号化では、よく知られている言葉があります。「暗号を作成するのは簡単なので、自分で解読する方法がわからないほど安全です。」

通常の開発プロセスでは、コードを記述し、コンパイルして実行し、思ったとおりに動作することを確認します。これを何度も繰り返すと、コードにかなり自信が持てるようになります。

あなたの自信はあなたの警戒心を弱めるでしょう。コードに関するあなたの経験を共有しない人は問題を抱えていません。

また、新しい目は、コードの信頼性だけでなく、コードの機能についても先入観が少ない可能性があります。結果として、彼らはコードの作者が考えていなかったテストケースを思い付くかもしれません。それらがより多くのバグを発見するか、コードが組織全体で何をするかについての知識をもう少し広めることを期待するでしょう。

さらに、優れたプログラマーになるにはEdgeのケースについて心配する必要があるが、優れたテスターに​​なるには執拗に心配する必要があるという議論があります;-)また、テスターは安価である可能性があるため、別のものを用意する価値があるかもしれませんそのため、テストチーム。

包括的な質問はこれだと思います。ソフトウェアのバグを見つけるのに最適な方法はどれですか。私は最近、ランダム化されたテストが人間が生成したテストよりも安価で効果的であると述べているビデオ(リンクなし、申し訳ありません)を見ました。

0
Jonas Kölker