質問:
BDDスタイルの受け入れテストを使用して、単体テスト、統合テスト、機能テスト、およびその他すべてのテストを廃止しないのはなぜですか?
私は、さまざまな種類のテストとさまざまなソフトウェア開発哲学の違いについていくつかの調査を行ってきました。
ソフトウェア開発哲学 のリストを見て、さまざまなタイプのテストについて読むと、受け入れテストがある場合にユニットテスト、統合テスト、機能テストを使用する理由を見つけるための研究に私を導きます。
私は聖なる牛を殺す人になるのは嫌いですが、のようなBDDスタイルの環境で受け入れテストを行った場合、そのときは実際にはそうではないように感じます受け入れテストだけで、とにかくビジネスに必要な機能を正確に提供するため、他のテストが必要です。
単体テスト、統合テスト、機能テストの使用に関するいくつかの議論:
ユニットテストは、下位レベルのものが正しいことを確認します:誰が気にしますか?彼らが受け取ったものにビジネスが満足している場合、それはどのような違いをもたらしますか?
単体テストは長期的な開発時間を短縮します:それは本当にトレードオフの価値がありますか?コードにバグがある場合、テストはこれから免除されません。 5,000の単体テストの作成とデバッグにかかる時間と、50の受け入れテストのデバッグにかかる時間は、明らかな選択のように思えますか?
単体テスト/ TDDは優れたアーキテクチャを保証します:いいえ、そうではありません。各単体テストは設計上の決定であり、全体的な貧弱なアーキテクチャに対して非常に脆弱です。
統合テストにより、全体的なパフォーマンスが正確で最適であることを確認します:これが単なる別のものではないのはなぜですか次に受け入れテストの状態?受け入れテストでSeleniumを使用しないのはなぜですか?
機能テストは出力が期待どおりであることを確認します:受け入れテストは、潜在的に誤った結果を待つのではなく、ビルド時に回答を提供することでこれを強化します。
TDDは、変更が実装されたときに、プロセスで他に何も中断しないことを保証します:すべてがビジネス要件/機能に基づいているため、受け入れテストでもこれが保証されます。
受け入れテストは、たとえば単体テストとは非常に異なるレベルで機能します。
単体テストは非常に正確です。1つのメソッド、場合によってはメソッドの一部を扱います。これにより、回帰テストに最適です。あなたは変更を加えます。前のコミット中に合格したが、テストが失敗した。すばらしいです。簡単に回帰の原因を特定する時間(コミットN-1からコミットNまで)とスペース( this メソッド this class)。
受け入れテストでは、いくつかが失敗し始めたら幸運。 1つの悪い変更により、1つの受け入れテストが失敗するか、10のテスト、または100のテストが失敗する可能性があります。バグの場所について何のヒントも与えずにこれらの100個のテストが赤くなるのを見ると、頭に浮かぶのは前のコミットに戻って最初からやり直すことだけです。
受け入れテストを、システムをブラックボックスと見なし、システムの feature をテストするテストとして想像してください。この機能には、実行する数千のメソッドが含まれる場合があり、データベース、メッセージキューサービス、その他数十ものものに依存する場合があります。受け入れテストは、関係するものの量を気にせず、舞台裏で発生するすべての魔法を気にしません。これは、複数の受け入れテストが同じメソッドに依存する可能性があることも意味します。つまり、単一のメソッドでの回帰により、複数の受け入れテストが失敗することがよくあります。リグレッションにより、バグの場所に関するヒントを提供せずに、突然約50のシステムテストと受け入れテストが失敗する場合がありました。
ユニットテストでは、システムをホワイトボックスと見なします。彼らは具体的な実装を認識しており、機能ではなく特定の method をテストします。モックとスタブを使用することにより、ユニットテストは、世界の影響を受けないように十分な分離を実現します。メソッドが機能する場合、テストは成功します。メソッドに回帰がある場合、それらのテストは失敗します。コードベースのどこかで別のメソッドが機能しない場合でも、それらの単体テストは合格です。
他のメソッドを呼び出すメソッドを表す次の図を想像してください。
単体テストは次のように表すことができます。
たとえば、ユニットテストU3およびU3 'は、スタブに置き換えられているため、メソッド6のバグの影響を受けません。スタブ5はメソッド2に依存しないため、メソッド2の回帰による影響も受けません。同じように、U8″はメソッド7を考慮しません。これは、9がスタブであり、11に依存しないためです。次に7を使用します。
コミットを行い、CIがU8 'が失敗したことを通知するとします。どこで問題を検索しますか?
U8で。テストするコードは正しいかもしれませんが、テストは正しくありません。これは、たとえば、要件が変更された場合に発生します。コードを変更したが、テストに変更を反映するのを忘れた場合です。
方法8では、回帰が発生する可能性があります。
スタブ9で。コードとテストに変更を実装したが、スタブを変更するのを忘れた可能性があります。
一方、問題は方法11または方法7にあるのではないと確信しています。
これで、受け入れテストまたはシステムテストは次のようになります。
コミットを行い、CIがA/S2が失敗したことを通知するとします。問題はどこにあると思いますか?もっと難しいですよね。
明らかに、別の側面は、上で説明したように、小さな変更によって多くの受け入れテストが失敗する可能性があることです。たとえば、方法7のリグレッションにより、A/S1 および A/S2が失敗する可能性があります。ユニットテストでは、メソッド11の回帰によってU11が失敗する可能性がありますが、たとえばU8には影響しません。
これは大きな利点につながります:回帰を見つけるのに費やす時間です。コードが混乱しているため、リグレッションが見つかったときにソースを最新の作業コミットに戻し、最初からやり直すプログラマーを見たことがあります。彼らは、デバッグの起源を見つけることを望んで、デバッグに何時間も費やすことを楽しんでいません。問題。これは残念なことです。特に、コミットが可能な限り頻繁に行われない場合はなおさらです。
ユニットテストを使用すると、この時間を無駄にすることはありません。特定のクラスの特定のメソッドに問題があることを通知するだけなので、リグレッションを発見した直後に、関連するメソッドに注意を向けることができます。
- 単体テストは、下位レベルの事柄が正しいことを確認します。彼らが受け取ったものにビジネスが満足している場合、それはどのような違いをもたらしますか? [...]
ランダムな場所で少なくとも10個のことを壊さずに変更を加えることができない、本当に悪いプロジェクトで作業したことがありますか?
ユニットテストはこの問題を魔法のように解決しません。ただし、下位レベルでは物事が正しいことに注意する必要があります。 クイックハックにはかなりのメンテナンスコストがかかります。一方、プログラマーが受け入れテストのみを行っている場合(そして締め切りが厳しく、仕事を正しく行うことに鈍感でない場合)、受け入れテストが失敗するためどこかで、フォントは12pxであるはずですが、10pxのように見え、最終的には次のように書くことになります。
this.font = 12;
テスターが戻ってきて、フォントが14pxであるはずの状況で12pxになったことを伝えるまで待ちます。
- ユニットテストは長期的な開発時間をスピードアップします:それは本当にトレードオフの価値がありますか?コードにバグがある場合、テストはこれから免除されません。 [...]
テストによって、魔法のようにバグがなくなることはありません。ただし、練習では、テストを作成し、失敗したことを確認してから、機能を実装し、テストに合格したことを確認することは、一般的にバグが少ない練習であるように見えました。
同様に、なぜ誰もがコードレビューを行うのでしょうか?レビューアは注意不足を免れることはできず、複数のレビューアやメンテナがバグを見逃す場合がたくさんあります。そうは言っても、コードレビューがないとバグが多くなります。
その価値はありますか?あなたの個人的な小さなアプリの場合、実際にはそうではありません。ビジネスクリティカルなコードの場合、確かにそうです。
- ユニットテストは優れたアーキテクチャを保証します:いいえ、そうではありません。 [...]
どういうわけか。メソッドを個別にテストすることを強制することで、プログラマにカップリングを再考を強制します。単体テストは通常、短いメソッド、1つだけのことを行うクラス(単一責任の原則)、依存性注入などにつながります。
他の数百のメソッドに依存し、データベースへのアクセスを必要とする4000 LOCメソッドは、単体テストを行うことはできません(そのようなメソッドに受け入れテストを追加することは完全に正常ですが)。
プロジェクトの途中でTDDを実行する では、この種のプロジェクトについて具体的に説明します。悪いアーキテクチャは、後で単体テストを追加することが実質的に不可能であるという事態につながりました。ユニットテストを最初から検討すれば、災害を減らすことができます。コードはまだ悪いですが(400 LOCスパゲッティメソッドをうまく書いていた同じプログラマーによって書かれたので)、それほど悪くはありません。
- 統合テストは、全体的なパフォーマンスが正確で最適であることを確認します[...]
分からない。聞いたことがない。パフォーマンスは、パフォーマンスの非機能要件に対応するテストによってチェックする必要があります。
- 機能テストにより、出力が期待どおりであることを確認します[...]
分からない。
- TDDは、変更が実装されたときに、プロセスで他に何も中断しないことを保証します。すべてがビジネス要件/機能に基づいているため、受け入れテストでもこれが保証されます。 [...]
いいえ、これは回帰テストの役割です。 TDDの目的は、主に何もテストしていないテストを作成しないようにすることです。これが、TDDで、機能を実装する前にテスト should が失敗する理由です。そうでない場合、テストが間違っているか、実際には機能が必要ありません。
BDDは実際にはクラスレベルで開始されました。 JBehaveはもともとJUnitの代わりになるものでした。
2004年のJBehaveとJUnitの唯一の意味のある違いは、「テスト」という単語の削除でした。 「すべき」の使用 クラスの動作のさまざまな側面を駆り立て、それらの質問を奨励する行動の側面(「すべきか?」)。
Dan North(BDDを作成した)とChris Matts(当時、 コード、特にモックについてもっと学んだ )との会話により、同じパターンをシステムレベルで使用できることが明らかになりました。 「Given、When、Then」を使用して再利用可能なステップを自動化するというアイデアが生まれました。
それらを「テスト」として考えるのではなく、Danは開発者に、それらをクラスまたはシステムがどのように動作するかの例として考えるように勧めました。システムのものを「シナリオ」と呼び、主にクラスレベルの「例」と区別しましたが、どちらも同義語です。
ステップは、クラスまたはシステムが機能するコンテキスト(Given)およびそれらのコンテキストが結果をどのように変更したか(Then)に従って作成されました。このパターンにより、コンテキストを検討する必要があるかどうか、または結果が望まれるかどうかを人々が質問できるようになりました。
さまざまなレベルがさまざまな対象者に適しています。クラスの動作に関する会話は通常、開発者間で行われますが、システムの動作に関する会話には、テスター、開発者、およびビジネスエキスパートの「3つのアミゴ」が関係します。
開発者は、常に存在するとは限らない利害関係者から、ビジネスよりも多くの要件を認識していることがよくあります。たとえば、開発者は通常、保守性、パフォーマンス、サードパーティまたはレガシーシステムのAPI、3年後にコードベースに戻ること、または新しい参加者が参加することなどを考慮します。
これらはすべて、それが機能する限り、ビジネスがしばしば気にしない要件の一部です。技術レベルでの動作について話すことで、開発者は自分たちが行っていることが適切かどうかを疑問視する機会を得ることができます。
一部の小規模なプロジェクトでは、特にプロジェクトの機能が単純で予測可能である場合、これによるメリットがあまり見られない可能性があります。大規模なプロジェクトは、「テストピラミッド」の恩恵を受けます。少数のフルスタックシステムテスト、より多くの統合テスト、さらに多くのクラスレベルのテストです。詳細については、Lisa CrispinおよびJanet Gregoryの「アジャイルテスト」を参照してください。
例として、 これはクラスレベルのテストです これはコメントで「Given、When、Then」を使用してウェイターの責任と動作を説明します(C#ですが読み取り可能)。
ただし、それらをテストとして考えない場合に役立ちます。これらは、システムの動作を説明する例だと思います。アイデアはバグをキャッチすることではありません。そもそもそこにあるとは考えられていなかったシナリオからのバグを防ぐためです。回帰バグは通常、不十分な設計の兆候であり、別のシステムレベルのシナリオを組み合わせて使用する代わりに、クラスレベルのリファクタリングとテストの恩恵を受けます。
テスト関連の質問がいくつかあります。
また、TDDとBDDは、ソフトウェアを設計およびテストする方法です。一羽の鳥に二石!
例:
また、私はあなたが述べていることについていくつかのメモと説明があります:
ユニットテストは、より低いレベルのものが正しいことを確認します
ちょっと、でも実際はそうではありません。それらは、knownバグの欠如/存在を示しているだけです。正しさに興味がある場合は、フォーマル検証を検討することをお勧めします。
ユニットテストは長期的な開発時間をスピードアップします
必ずしもそうとは限りません。設計が不十分なシステムはテストが困難または不可能になる可能性があるためです。そのような場合、私はそれは価値がないと信じています。それがTDDが発明された理由です。
システムがテスト可能であれば、それは多くの価値があります。エラーは、早期に検出された場合のコストがはるかに低くなります。それらはコストがかかりすぎる可能性があり、生産段階で発見されます。
ユニットテストは優れたアーキテクチャを保証します:いいえ、そうではありません。
確かに彼らはしません。 TDDとBDDはそうします。
統合テストにより、全体的なパフォーマンスが正確で最適であることを確認します
だけではありません。機能性と信頼性も。 2つのシステムが「ローカル」テストに合格しても、一緒に機能しない場合があります。統合テストは、パーツがどの程度うまく連携しているかを示します。
TDDは、変更が実装されたときに、プロセスで他に何も中断しないことを保証します
秘密裏に、すべてのタイプの自動テストがそれを行います。 「回帰バグ」を表示します。つまり、モジュール(または関数、またはシステム)Aにテストがあり、モジュールBを変更し、次にAが壊れ、モジュールAのテストを確認してモジュールAを修正します。