28

最近、既存の Java アプリケーションに自動テストを追加しています。

私たちが持っているもの

これらのテストの大部分は統合テストであり、次のような呼び出しのスタックをカバーする場合があります。

  1. サーブレットへの HTTP ポスト
  2. サーブレットはリクエストを検証し、ビジネス層を呼び出します
  3. ビジネスレイヤーは、休止状態などを介して一連のことを行い、いくつかのデータベーステーブルを更新します
  4. サーブレットはいくつかの XML を生成し、これを XSLT を介して実行して応答 HTML を生成します。

次に、サーブレットが正しい XML で応答したこと、および正しい行がデータベース (開発中の Oracle インスタンス) に存在することを確認します。その後、これらの行は削除されます。

単一のメソッド呼び出しをチェックするいくつかの小さな単体テストもあります。

これらのテストはすべて、ナイトリー (またはアドホック) ビルドの一部として実行されます。

質問

システムの境界をチェックしているので、これは良いことのように思えます。一方はサーブレットのリクエスト/レスポンス、もう一方はデータベースです。これらが機能する場合は、自由にリファクタリングしたり、途中で何かをいじったりして、テスト中のサーブレットが引き続き機能することを確信できます。

このアプローチでは、どのような問題が発生する可能性がありますか?

個々のクラスにさらに多くの単体テストを追加することがどのように役立つかわかりません。テストを破棄して書き直す必要が生じる可能性が非常に高いため、リファクタリングが難しくなるのではないでしょうか?

4

12 に答える 12

34

単体テストは、障害をより厳密にローカライズします。統合レベルのテストは、ユーザーの要件により厳密に対応するため、配信の成功をより適切に予測できます。どちらも、構築して維持しないとあまり良くありませんが、適切に使用すれば、どちらも非常に価値があります。


(もっと...)

単体テストの問題は、優れた単体テストのセットほどすべてのコードを実行できる統合レベルのテストはないということです。はい、それはテストをいくらかリファクタリングする必要があることを意味しますが、一般的に、テストは内部にあまり依存すべきではありません。たとえば、2 のべき乗を取得する関数が 1 つあるとします。あなたはそれを説明します(正式な方法論者として、私はあなたがそれを指定すると主張します)

long pow2(int p); // returns 2^p for 0 <= p <= 30

テストと仕様は本質的に同じに見えます (これは説明用の疑似 xUnit のようなものです):

assertEqual(1073741824,pow2(30);
assertEqual(1, pow2(0));
assertException(domainError, pow2(-1));
assertException(domainError, pow2(31));

これで、実装を複数の for ループにすることができます。あとで、それをシフトに変更することができます。

たとえば、16 ビットを返すように実装を変更した場合 (sizeof(long)は 未満ではないことが保証されていることを思い出してsizeof(short)ください)、このテストはすぐに失敗します。統合レベルのテストはおそらく失敗するはずですが、確実ではありませんpow2(28)

ポイントは、さまざまな状況を実際にテストすることです。十分に詳細で大規模な統合テストを構築できれば、同じレベルのカバレッジときめ細かなテストを取得できるかもしれませんが、それを行うのはおそらく難しく、指数関数的な状態空間の爆発があなたを打ち負かします. 単体テストを使用して状態空間を分割することにより、必要なテストの数は指数関数的に増加するよりもはるかに少なくなります。

于 2009-04-21T04:23:19.373 に答える
29

あなたは 2 つの異なることの長所と短所を尋ねています (馬に乗ることとオートバイに乗ることの長所と短所は何ですか?)

もちろん、どちらも「自動テスト」(~riding) ですが、代替手段であるという意味ではありません (何百マイルも馬に乗らず、泥だらけの車に閉じこもったバイクに乗らないでください)。場所)


単体テストは、コードの最小単位 (通常はメソッド) をテストします。各単体テストは、それがテストしているメソッドと密接に結びついており、適切に記述されていれば、それとのみ (ほぼ) 結びついています。

これらは、新しいコードの設計と既存のコードのリファクタリングをガイドするのに最適です。システムが統合テストの準備が整うずっと前に、問題を発見するのに最適です。私がガイドを書いたことに注意してください。すべてのテスト駆動開発はこの言葉に関するものです。

単体テストを手動で行うのは意味がありません。

あなたの主な関心事と思われるリファクタリングについてはどうですか? メソッドの実装 (コンテンツ) のみをリファクタリングし、その存在や「外部動作」をリファクタリングしない場合でも、単体テストは有効であり、信じられないほど便利です (実際に試してみるまで、どれだけ役立つかは想像できません)。

より積極的にリファクタリングし、メソッドの存在や動作を変更する場合は、新しいメソッドごとに新しい単体テストを作成し、古いメソッドを破棄する必要があります。しかし、特にコード自体の前にユニット テストを記述すると、実装の詳細 (つまり、メソッドがどのようにすべきか) に惑わされることなく、設計 (つまり、メソッドが何をすべきか、すべきでないか) を明確にするのに役立ちます。やるべきことをやる)。


自動化された統合テストは、コードの最大単位、通常はアプリケーション全体をテストします。

手動でテストしたくないユースケースをテストするのに最適です。ただし、手動の統合テストを使用することもできますが、それらは効果的です (利便性は劣りますが)。


今日新しいプロジェクトを開始する場合、単体テストがないことは意味がありませんが、あなたのような既存のプロジェクトの場合、既に持っていて機能しているすべてのものに対して単体テストを作成することはあまり意味がありません。

あなたの場合、私はむしろ「中間」アプローチを使用したいと思います:

  1. リファクタリングするセクションのみをテストする小規模な統合テスト。全体をリファクタリングする場合は、現在の統合テストを使用できますが、たとえば XML 生成のみをリファクタリングする場合は、データベースの存在を要求しても意味がありません。シンプルで小さな XML 統合テスト。
  2. これから作成する新しいコードの単体テストの束。すでに上で書いたように、ユニットテストは、「その間に何かをいじる」とすぐに準備が整い、「混乱」がどこかに行くことを確認します。

実際、統合テストは、「混乱」が機能していないことを確認するだけです (最初は機能しないからですよね?)。

  • なぜ機能しないのか
  • 「混乱」のデバッグが本当に何かを修正している場合
  • 「混乱」のデバッグが他の何かを壊している場合

統合テストは、変更全体が成功した場合にのみ、最後に確認を行います (そして、答えは長い間「いいえ」になります)。統合テストは、リファクタリング自体の間は何の助けにもなりません。そのためには単体テストが必要です。

于 2009-05-04T20:02:29.417 に答える
20

ユーザーアクションとシステム全体の正確性により対応する統合レベルのテストについては、Charlie に同意します。ただし、単体テストには、失敗をより厳密にローカライズするだけでなく、より多くの価値があると思います。単体テストは、統合テストよりも 2 つの主な価値を提供します。

1) 単体テストを書くことは、テストと同じくらい設計の行為です。テスト駆動開発/動作駆動開発を実践している場合、単体テストを作成するという行為は、コードで何をすべきかを正確に設計するのに役立ちます。高品質のコードを作成するのに役立ち (疎結合はテストに役立つため)、テストに合格するのに十分なコードを作成するのに役立ちます (テストは仕様に影響を与えるため)。

2) 単体テストの 2 つ目の価値は、適切に記述されていれば非常に高速であるということです。プロジェクトのクラスに変更を加えた場合、対応するすべてのテストを実行して、何か壊れていないかどうかを確認できますか? どのテストを実行するかを知るにはどうすればよいですか? そして、どれくらいの時間がかかりますか? よく書かれた単体テストよりも長くなることは保証できます。せいぜい数分ですべての単体テストを実行できるはずです。

于 2009-04-21T12:07:16.873 に答える
16

個人的な経験からのほんの数例:

単体テスト:

  • (+) テストを関連するコードに近づける
  • (+) すべてのコード パスのテストが比較的簡単
  • (+) 誰かがうっかりメソッドの動作を変更したかどうかを簡単に確認できる
  • (-) 非 GUI よりも UI コンポーネントを書く方がはるかに難しい

統合テスト:

  • (+) プロジェクトにナットとボルトがあるのは良いことですが、統合テストではそれらが互いに適合することを確認します
  • (-) エラーの原因を突き止めるのが難しい
  • (-) すべての (またはすべての重要な) コード パスをテストするのが難しい

理想的には、両方が必要です。

例:

  • 単体テスト: 入力インデックス >= 0 かつ < 配列の長さであることを確認してください。範囲外になるとどうなる?メソッドは例外をスローするか、null を返す必要がありますか?

  • 統合テスト: 負の在庫値が入力された場合、ユーザーには何が表示されますか?

2 つ目は、UI とバックエンドの両方に影響します。両方の側が完全に機能する可能性がありますが、2 つの間のエラー条件が明確に定義されていないため、間違った答えが得られる可能性があります。

私たちが発見した単体テストの最も優れた点は、開発者がコード→テスト→思考から思考→テスト→コードへと移行できることです。開発者が最初にテストを書かなければならない場合、開発者は何がうまくいかないかを前もって考える傾向があります。

最後の質問に答えると、単体テストはコードのすぐ近くにあり、開発者は前もって考える必要があるため、実際には、コードをあまりリファクタリングしない傾向があるため、移動するコードが少なくなることがわかりました。 - そのため、常に新しいテストを投げたり書いたりすることは問題ではないようです。

于 2009-05-04T13:22:36.303 に答える
5

この質問には確かに哲学的な部分がありますが、実用的な考慮事項も指摘しています。

より良い開発者になるための手段として使用されるテスト駆動設計にはメリットがありますが、必須ではありません。単体テストを書いたことがない優れたプログラマーはたくさんいます。単体テストを行う一番の理由は、特に多くの人が同時にソースを変更している場合に、リファクタリングの際に強力になることです。チェックイン時にバグを発見することは、プロジェクトの時間を大幅に節約することにもなります (CI モデルに移行し、夜間ではなくチェックイン時にビルドすることを検討してください)。したがって、単体テストを作成する場合、それがテストするコードを作成する前または後に、作成した新しいコードについてその時点で確信しています。単体テストが保証するのは、後でそのコードに起こりうることであり、それは重要な場合があります。単体テストは、QA に進む前にバグを止めることができるため、プロジェクトをスピードアップできます。

統合テストは、正しく行われた場合、スタック内の要素間のインターフェースにストレスを与えます。私の経験では、統合はプロジェクトの最も予測不可能な部分です。個々の部品を機能させることはそれほど難しくありませんが、この段階で発生する可能性のあるバグの種類のために、すべてをまとめるのは非常に難しい場合があります. 多くの場合、統合で起こることが原因でプロジェクトが遅れます。このステップで発生したエラーの一部は、一方の側で行われた変更が他方の側に伝達されなかったために壊れたインターフェイスで見つかりました。統合エラーのもう 1 つの原因は、開発で発見されたが、アプリが QA に進むまでに忘れられていた構成にあります。統合テストは、両方のタイプを劇的に減らすのに役立ちます。

各テスト タイプの重要性については議論の余地がありますが、最も重要なことは、特定の状況にいずれかのタイプを適用することです。問題のアプリは、少人数のグループまたは多数の異なるグループによって開発されていますか? すべてのリポジトリを 1 つ持っていますか、それともアプリの特定のコンポーネントごとに多数のリポジトリを持っていますか? 後者の場合、異なるコンポーネントの異なるバージョンの相互互換性に問題があります。

各テスト タイプは、開発段階でさまざまなレベルの統合の問題を明らかにして時間を節約するように設計されています。単体テストは、多くの開発者が 1 つのリポジトリで作業している出力の統合を促進します。統合テスト (不適切な名前) は、スタック内のコンポーネントの統合を促進します。多くの場合、コンポーネントは別々のチームによって作成されます。統合テストによって明らかになる問題のクラスは、通常、修正に時間がかかります。

実用的に言えば、自分の組織/プロセスで最もスピードが必要な場所に要約されます。

于 2009-05-02T11:54:27.907 に答える
3

単体テストと統合テストを区別するのは、テストを実行するために必要なパーツの数です。

単体テストでは、(理論的には) 実行する他の部分がほとんど (またはまったく) 必要ありません。統合テストでは、(理論的には) 多くの (またはすべての) 他の部分を実行する必要があります。

統合テストは、動作とインフラストラクチャをテストします。単体テストは通常​​、動作のみをテストします。

したがって、単体テストは、一部のもののテストや、他のものの統合テストに適しています。

では、なぜ単体テストを行うのでしょうか。

たとえば、統合テストで境界条件をテストするのは非常に困難です。例: バックエンド関数は正の整数または 0 を想定していますが、フロントエンドは負の整数の入力を許可していません。バックエンド関数に負の整数を渡したときにバックエンド関数が正しく動作することをどのように保証しますか? おそらく、正しい動作は例外をスローすることです。これを統合テストで行うのは非常に困難です。

そのため、(関数の) 単体テストが必要です。

また、単体テストは、統合テスト中に見つかった問題を排除するのに役立ちます。上記の例では、単一の HTTP 呼び出しに多くの障害点があります。

HTTP クライアントからの呼び出し サーブレットの検証 サーブレットからビジネス層への呼び出し ビジネス層の検証 データベースの読み取り (ハイバネート) ビジネス層によるデータ変換 データベースの書き込み (ハイバネート) データ変換 -> XML XSLT 変換→ HTML HTMLの送信 → クライアント

統合テストが機能するには、これらのすべてのプロセスが正しく機能する必要があります。サーブレット検証の単体テストでは、1 つだけ必要です。サーブレットの検証 (他のすべてから独立している可能性があります)。1 つのレイヤーの問題は追跡が容易になります。

単体テストと統合テストの両方が必要です。

于 2009-05-04T15:19:45.087 に答える
2

単体テストは、クラス内のメソッドを実行して、アプリケーションのより大きなコンテキストでクラスをテストすることなく、適切な入出力を検証します。モックを使用して依存クラスをシミュレートする場合があります。クラスをスタンドアロン エンティティとしてブラック ボックス テストを行っています。単体テストは、外部サービスやソフトウェアを必要とせずに開発者ワークステーションから実行できる必要があります。

統合テストには、アプリケーションの他のコンポーネントとサードパーティ ソフトウェア (たとえば、Oracle 開発データベース、または Web アプリケーションの Selenium テスト) が含まれます。これらのテストは依然として非常に高速で、継続的なビルドの一部として実行される可能性がありますが、追加の依存関係を挿入するため、コードに問題を引き起こす新しいバグを挿入するリスクもありますが、コードが原因はありません。できれば、統合テストは、実際の/記録されたデータを挿入し、アプリケーション スタック全体がそれらの入力に対して期待どおりに動作していることを確認する場所でもあります。

問題は、見つけようとしているバグの種類と、それらをどれだけ早く見つけたいかということです。単体テストは「単純な」ミスの数を減らすのに役立ちますが、統合テストはアーキテクチャと統合の問題を突き止め、できればアプリケーション全体に対するマーフィーの法則の影響をシミュレートするのに役立ちます。

于 2009-04-29T06:27:31.460 に答える
2

Joel Spolsky は、単体テストに関する非常に興味深い記事を書きました (Joel と他の人物との対話でした)。

主なアイデアは、単体テストは非常に良いものですが、「限られた」量で使用する場合に限るというものでした。Joel は、コードの 100% がテストケースの下にあるときに状態を達成することをお勧めしません。

単体テストの問題は、アプリケーションのアーキテクチャを変更したい場合、対応するすべての単体テストを変更する必要があることです。そして、それには非常に時間がかかります (おそらく、リファクタリング自体よりもさらに時間がかかります)。そして、すべての作業の後、失敗するテストはほとんどありません。

したがって、実際に問題を引き起こす可能性のあるコードに対してのみテストを記述してください。

単体テストの使い方: 私は TDD が好きではないので、最初にコードを書き、それを (コンソールまたはブラウザーを使用して) テストして、このコードが必要な作業を行うことを確認します。その後、「トリッキーな」テストを追加します。最初のテストで 50% が失敗します。

それは機能し、それほど時間はかかりません。

于 2009-05-04T15:34:45.487 に答える
2

多くの人が言及しているように、統合テストはシステムが機能するかどうかを示し、単体テストはどこが機能しないかを示します。テストの観点から厳密に言えば、これら 2 種類のテストは互いに補完し合います。

個々のクラスにさらに多くの単体テストを追加することがどのように役立つかわかりません。テストを破棄して書き直す必要が生じる可能性が非常に高いため、リファクタリングが難しくなるのではないでしょうか?

いいえ。リファクタリングがより簡単かつ適切になり、どのリファクタリングが適切で関連性があるかが明確になります。これが、TDD はテストではなく設計に関するものであると私たちが言う理由です。1 つのメソッドのテストを作成し、そのメソッドの結果をどのように表現すべきかを理解するために、テスト対象のクラスの他のメソッドに関して非常に単純な実装を考え出すことは、私にとって非常によくあることです。その実装は、テスト対象のクラスに頻繁に組み込まれます。よりシンプルでより堅実な実装、より明確な境界、より小さなメソッド: TDD (具体的には単体テスト) はこの方向に導きますが、統合テストはそうではありません。どちらも重要で便利ですが、目的が異なります。

はい、リファクタリングに対応するために単体テストを変更したり削除したりすることがあります。それは問題ありませんが、難しいことではありません。そして、これらの単体テストを作成し、それらを作成する経験を積むことで、コードをより深く理解し、より優れた設計を行うことができます。

于 2009-05-05T17:03:20.977 に答える
2

プロジェクトには 4 種類のテストがあります。

  1. 必要に応じてモックを使用した単体テスト
  2. 単体テストと同様に動作するが、db に触れて後でクリーンアップする DB テスト
  3. 私たちのロジックは REST を通じて公開されているため、HTTP を実行するテストがあります。
  4. 実際に IE インスタンスを使用し、主要な機能を調べる WatiN を使用した Web アプリケーション テスト

単体テストが好きです。それらは非常に高速に実行されます (#4 テストよりも 100 倍から 1000 倍高速です)。それらは型安全であるため、リファクタリングは非常に簡単です (優れた IDE を使用)。

主な問題は、それらを適切に行うためにどれだけの作業が必要かということです。データベース アクセス、ネットワーク アクセス、その他のコンポーネントなど、すべてをモックする必要があります。モックできないクラスを装飾する必要があり、無数のほとんど役に立たないクラスを取得します。コンポーネントが密結合されていないためにテストできないように、DI を使用する必要があります (DI の使用は実際にはマイナス面ではないことに注意してください:)

テスト#2が好きです。彼らはデータベースを使用し、データベース エラー、制約違反、および無効な列を報告します。これを使って貴重なテストができると思います。

#3、特に#4はより問題があります。ビルド サーバー上に実稼働環境の一部のサブセットが必要です。アプリをビルドしてデプロイし、実行する必要があります。毎回クリーンな DB が必要です。しかし、最終的には報われます。Watin テストには一定の作業が必要ですが、一定のテストも行われます。私たちはすべてのコミットでテストを実行し、何かを壊したときに非常に簡単に確認できます。

それで、あなたの質問に戻ります。単体テストは高速で (これは非常に重要です。ビルド時間は、たとえば 10 分未満にする必要があります)、リファクタリングも簡単です。設計が変更された場合、watin 全体を書き直すよりもはるかに簡単です。優れた使用法検索コマンド (IDEA または VS.NET + Resharper など)を備えた優れたエディターを使用すると、コードがテストされている場所をいつでも見つけることができます。

REST/HTTP テストを使用すると、システムが実際に機能することを十分に検証できます。しかし、テストの実行は遅いため、このレベルで完全な検証を行うことは困難です。あなたのメソッドは複数のパラメーターまたはおそらく XML 入力を受け入れると思います。XML の各ノードまたは各パラメーターをチェックするには、数十回または数百回の呼び出しが必要です。単体テストではそれを行うことができますが、REST 呼び出しではそれを行うことができません。

私たちの単体テストは、#3 テストよりもはるかに頻繁に特別な境界条件をチェックします。彼ら (#3) は、主な機能が機能していることを確認し、それだけです。これは私たちにとってかなりうまくいくようです。

于 2009-05-04T22:31:21.523 に答える
1

あなたが説明したセットアップは良さそうですが、単体テストも重要なことを提供します。単体テストは、細かいレベルの粒度を提供します。疎結合と依存性注入を使用すると、すべての重要なケースをほとんどテストできます。ユニットが堅牢であることを確認できます。多数の入力や、統合テスト中に必ずしも発生しない興味深い事柄を使用して、個々のメソッドを精査できます。

たとえば、複雑なセットアップが必要なある種の障害 (サーバーから何かを取得するときのネットワーク例外など) をクラスがどのように処理するかを決定論的に確認したい場合は、独自のテスト二重ネットワーク接続クラスを簡単に作成し、注入して伝えることができます。気が向いたらいつでも例外をスローします。その後、テスト対象のクラスが例外を適切に処理し、有効な状態で続行することを確認できます。

于 2009-05-04T21:46:38.497 に答える