web-dev-qa-db-ja.com

単体テストは(特にC ++のコンテキストで)時期尚早な一般化につながりますか?

序論

さまざまな種類のテストの違いについては触れませんが、これらのサイトには すでに いくつか questions があります。

「アプリケーションの最小の分離可能なユニットをテストする」という意味での(= /// =)ユニットテストこの質問が実際に導き出すもの

分離の問題

プログラムの分離可能な最小単位は何ですか。ええと、私が見ているように、それは(非常に?)あなたがコーディングしている言語に依存します。

Micheal Feathersが語るseamの概念:[WEwLC、p31]

シームは、その場所で編集せずにプログラムの動作を変更できる場所です。

詳細については説明しませんが、ユニットテストの文脈では、「テスト」が「ユニット」とやり取りできるプログラム内の場所であるということを理解しています。

単体テスト(特にC++の場合)は、テスト対象のコードから、特定の問題に対して厳密に呼び出されるさらにシームを追加する必要があります。

例:

  • 非仮想実装で十分だったはずの仮想インターフェースを追加する
  • 分割-一般化(?)-(小さめの)クラスは、テストの追加を容易にするためにさらに「ただ」だけです。
  • 単一の実行可能プロジェクトを一見「独立した」ライブラリに分割し、テストのためにそれらを独立してコンパイルすることを容易にするためだけに。

質問

うまくいけば同じ点について尋ねるいくつかのバージョンを試してみます:

  • 単体テストは、アプリケーションのコードを「のみ」構築するために単体テストに有益な方法であるか、または実際にアプリケーションの構造に有益な方法です。
  • 単体テストを可能にするために必要なコードの一般化は、単体テスト以外のすべてに役立ちますか?
  • 単体テストを追加すると、不必要に一般化する必要がありますか?
  • シェイプユニットテストのフォースは常に「常に」コードに影響を与えますか。問題ドメインから見た場合、一般的にコードにとって良いシェイプですか?

あなたがする必要があるまで/コードを使用する2番目の場所があるまで一般化しないでくださいと言った経験則を覚えています。ユニットテストでは、コードを使用する2番目の場所が常にあります。つまり、ユニットテストです。この理由で一般化するには十分でしょうか?

20
Martin Ba

単体テスト(特にC++の場合)は、テスト対象のコードから、特定の問題に対して厳密に要求される継ぎ目を追加する必要があります。

問題解決に不可欠な部分のテストを考慮しない場合のみ。重要な問題については、ソフトウェアの世界だけでなく、そうあるべきです。

ハードウェアの世界では、これはずっと前に、困難な方法で学ばれています。さまざまな機器の製造業者は、数百年に渡る落下する橋、爆発する車、喫煙CPUなどから、ソフトウェアの世界で現在学んでいることを何世紀にもわたって学びました。それらのすべては、それらをテスト可能にするために彼らの製品に「余分な継ぎ目」を作ります。最近のほとんどの新しい車は、修理工がエンジン内部で何が起こっているかについてのデータを取得するための診断ポートを備えています。すべてのCPUのトランジスタのかなりの部分が診断の目的で使用されます。ハードウェアの世界では、「余分な」ものはすべてコストがかかります。製品が何百万人も製造されている場合、これらのコストは確かに多額の金額になります。それでも、製造業者はテスト可能性のためにこのすべてのお金を使う用意があります。おそらく彼らは彼らの製品がエンドユーザーの手に(またはお尻の下で)失敗し、その後の評判、訴訟、悪いPRなどの損失などのリスクが道であることを理解しました(または難しい方法を学びました)。最初から製品を設計およびテスト可能にするコストよりも高い。

ソフトウェアの世界に戻ると、C++は、動的クラスローディング、リフレクションなどを特徴とする後の言語よりも、単体テストが実際に困難です。それでも、ほとんどの問題は少なくとも軽減できます。これまで単体テストを使用していた1つのC++プロジェクトでは、テストを実行する頻度はそれほど高くありませんでした。 a Javaプロジェクト-それでも、それらはCIビルドの一部であり、有用であることがわかりました。

単体テストがアプリケーションのコードを「のみ」構築するのに必要な方法は、単体テストに有益ですか、それとも実際にアプリケーションの構造に有益ですか?

私の経験では、テスト可能な設計は全体として有益であり、単体テスト自体のためだけではありません。これらのメリットはさまざまなレベルで提供されます。

  • デザインをテスト可能にすると、アプリケーションを小さな、ある程度独立した部分に分割する必要があります。これは、限られた明確に定義された方法でのみ相互に影響を及ぼします。これは、プログラムの長期的な安定性と保守性にとって非常に重要です。これがないと、コードはスパゲッティコードに劣化する傾向があり、コードベースの任意の部分で行われた変更は、プログラムとは無関係に見える個別の部分で予期しない影響を引き起こす可能性があります。それは言うまでもなく、すべてのプログラマーの悪夢です。
  • テスト自体をTDD方式で記述すると、実際にはAPI、クラス、およびメソッドが実行され、設計が理にかなっているかどうかを検出するための非常に効果的なテストとして機能します。テストに対する記述とインターフェイスが不快または難しいと感じた場合、早期に貴重なフィードバックを得ることができます。 APIを簡単に作成できます。つまり、これにより、APIが時期尚早に公開されるのを防ぐことができます。
  • TDDによって実施される開発のパターンは、具体的なタスクに集中するのに役立ち、目標を達成し続け、想定されている問題以外の問題の解決に迷い、不必要な機能や複雑さを追加する可能性を最小限に抑えます。 、など.
  • ユニットテストの高速フィードバックにより、コードのリファクタリングを大胆に行うことができ、コードの有効期間全体にわたって設計を絶えず適応および進化させることができるため、コードエントロピーを効果的に防止できます。

あなたがする必要があるまで/コードを使用する2番目の場所があるまで一般化しないでくださいと言った経験則を覚えています。単体テストでは、コードを使用する2番目の場所、つまり単体テストが常に存在します。この理由で一般化するには十分でしょうか?

ソフトウェアが想定どおりの動作をすることを証明できる場合-ユニットテストによって強制される「余分な」一般化や継ぎ目なしに、顧客を満足させるのに十分な方法で、高速で再現性があり、安価で確定的であることを証明できる場合- (そして、あなたがそれをどのように行うかを私たちに知らせてください。このフォーラムの多くの人々が私と同じくらい興味があると確信しているからです:-)

ところで、「一般化」とは、単一の具象クラスの代わりに、インターフェース(抽象クラ​​ス)と多態性を導入するようなものを意味すると思います。そうでない場合は、明確にしてください。

23
Péter Török

私は The Way of Testivus をあなたに投げますが、要約すると:

システムの1つの部分をテストするためにコードをより複雑にするために多大な時間とエネルギーを費やしている場合は、構造が間違っているか、テスト方法が間違っている可能性があります。

最も簡単なガイドは次のとおりです。テストしているのは、システムの他の部分での使用を目的としたコードのパブリックインターフェイスです。

テストが長く複雑になっている場合は、パブリックインターフェイスの使用が困難であることを示しています。

継承を使用して、現在使用されている単一のインスタンス以外でクラスを使用できるようにする必要がある場合、クラスがその使用環境に過度に関連付けられている可能性があります。これが真実である状況の例を挙げられますか?

ただし、単体テストの教義には注意してください。 クライアントがあなたに叫ぶ原因となる問題を検出できるテストを作成します

4
deworde

TDDと単体テストは、単体テストだけでなく、プログラム全体に適しています。その理由は、脳に良いからです。

これは特定のものに関するプレゼンテーションです RobotLegsという名前のActionScriptフレームワーク。ただし、最初の10枚のスライドをめくると、脳の良い部分に行き着きます。

TDDとユニットテストでは、脳が情報を処理して記憶するのに適した方法で動作するように強制します。したがって、目の前の正確なタスクは、より優れた単体テストを作成するか、コードをより単体テスト可能にすることですが、実際に行うことは、コードを読みやすくし、コードをより保守しやすくすることです。これにより、ハビッツでのコーディングが速くなり、機能を追加/削除したり、バグを修正したり、一般にソースファイルを開いたりする必要があるときに、コードをより早く理解できます。

2
Bob

アプリケーションの分離可能な最小単位のテスト

これは本当ですが、あまりに長く取りすぎると、多くのことは得られず、コストもかかります。TDDの本来の目的であるBDDという用語の使用を促進しているのは、この側面だと思います沿って-最小の分離可能なユニットは、あなたがそれがなりたいものです。

たとえば、私は(他のビットの中でも)2つのメソッドを持つネットワーククラスをデバッグしました。1つはIPアドレスを設定し、もう1つはポート番号を設定します。当然、これらは非常に単純な方法であり、最も簡単なテストに簡単に合格しますが、ポート番号を設定してからIPアドレスを設定すると、機能しなくなります。IPセッターはデフォルトでポート番号を上書きしていました。したがって、正しい動作を確認するためにクラス全体をテストする必要がありました。TDDの概念が欠けていると思いますが、BDDが提供します。アプリケーション全体の最も賢明で最小の領域(この場合はネットワーククラス)をテストできる場合は、実際に各小さなメソッドをテストする必要はありません。

結局のところ、テストに特効薬はありません。限られたテストリソースをどのくらいの粒度で適用するかについて、賢明な決定を行う必要があります。自動でスタブを生成するツールベースのアプローチはこれを行わず、鈍い力のアプローチです。

したがって、これを考えると、TDDを達成するために特定の方法でコードを構造化する必要はありませんが、達成するテストのレベルは、コードの構造に依存します-すべてのロジックがしっかりと結合されたモノリシックGUIがある場合GUI構造を使用すると、これらの部分を分離するのが難しくなりますが、「ユニット」がGUIを参照し、すべてのバックエンドDB作業が模擬されている単体テストを作成できます。これは極端な例ですが、それでも自動テストを実行できることを示しています。

小さなユニットをテストしやすくするためにコードを構造化することの副作用は、アプリケーションをより適切に定義するのに役立ち、それによってパーツをより簡単に交換することができます。また、2人の開発者がいつでも同じコンポーネントで作業する可能性が低くなるため、コーディング時にも役立ちます。依存関係が混在しているモノリシックアプリとは異なり、他のすべての人の作業が中断されるので、マージ時間になります。

1
gbjbaanb

あなたは言語設計のトレードオフについて良い認識に達しました。 C++での中核的な設計決定(静的関数呼び出しメカニズムと混合された仮想関数メカニズム)のいくつかは、TDDを困難にします。この言語は、簡単にするために必要なものを実際にはサポートしていません。単体テストでは不可能に近いC++を書くのは簡単です。

TDD C++コードを、プロシージャ(引数を取らず、voidを返す関数)ではなく、関数型の準関数から書き込み関数を使用して作成し、可能な限り構成を使用したほうがいいです。これらのメンバークラスを置き換えるのは難しいので、これらのクラスをテストして信頼できるベースを構築することに焦点を当て、それを他の何かに追加すると、基本的なユニットが機能することを知っています。

重要なのは、準機能的アプローチです。考えてみてください。すべてのC++コードがグローバルにアクセスしない無料の関数である場合、単体テストにスナップできます:)

0
anon