52

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

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

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

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

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

このポリシーは、 Refactoring Test Codeのようなドキュメントとは対照的だと思います...

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

... 私はそのすべてに同意しないと思います (または、少なくとも練習していません)。

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

要約すれば:

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

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

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

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

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


脚注: 記事Object-Oriented Testing: Myth and Realityは次のように述べています。

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

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

ホワイトボックスの機能テストを行っていることを付け加えておきます。コード (実装内) を確認し、機能テスト (パブリック API を駆動する) を作成して、さまざまなコード ブランチ (機能の実装の詳細) を実行します。

4

15 に答える 15

33

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

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

単体テストは、今書いた 10 行のコードが本来の動作をすることを確認するためにここにあります。これにより、コードに対する信頼が高まります。

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

于 2009-05-13T08:15:48.120 に答える
19

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

于 2009-05-13T05:27:46.587 に答える
9

私は自分のLakosのコピーを目の前に持っていないので、引用するのではなく、すべてのレベルでテストが重要である理由を説明するよりも、彼が優れた仕事をしていることを指摘するだけにとどめます.

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

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

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

于 2009-05-13T05:43:30.350 に答える
8

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

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

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

もちろん、単体テストも間違っている可能性がありますが、機能/技術仕様からテスト ケースを作成する場合 (既にありますよね? ;))、あまり問題は発生しないはずです。

于 2009-05-13T18:28:38.683 に答える
2

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

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

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

于 2009-05-13T05:19:12.527 に答える
2

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

于 2009-08-29T02:54:47.507 に答える
1

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

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

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

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

于 2009-10-08T23:26:12.510 に答える
1

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

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

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

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

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

ameetに関して

于 2010-07-27T08:00:58.620 に答える
1

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

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

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

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

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

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

于 2009-08-15T09:42:10.850 に答える
0

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

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

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

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

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

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

于 2009-10-09T00:05:58.460 に答える
0

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

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

于 2009-05-13T05:25:01.787 に答える
0

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

于 2009-05-13T05:49:37.173 に答える
0

【自問自答】

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

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

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

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

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

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

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

于 2009-05-13T18:44:07.860 に答える
0

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

これが普遍的に正しいとは思いません。

暗号学では、「自分で解読する方法がわからないほど安全な暗号を作成するのは簡単だ」という有名な言葉があります。

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

あなたの自信は、あなたをあまり用心深くないテスターに​​します。あなたの経験をコードと共有しない人には、問題はありません。

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

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

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

于 2009-05-26T03:04:11.647 に答える
0

継承された型に対して「公開」されているため、保護された部分も個人的にテストします...

于 2009-05-13T05:06:27.650 に答える