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


20

予備メモ

さまざまな種類のテストの違いについては説明しませんが、これらのサイトには既にいくつかの質問があります。

私はそこに何を取るだろうと、それは言う:単位は、「アプリケーションの最小単離ユニットテスト」の意味でのテストこの質問は、実際に由来します

分離の問題

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

Micheal Feathersは縫い目の概念について語っています:[WEwLC、p31]

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

そして、詳細に立ち入ることなく、私は、ユニットテストのコンテキストで、あなたの「テスト」があなたの「ユニット」とインターフェースできるプログラムの場所であると理解しています。

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

例:

  • 非仮想実装で十分な仮想インターフェースを追加する
  • 分割-generalizing(?)-テストを追加しやすくするための(さらに小さな)クラス
  • 単一の実行可能プロジェクトを、一見「独立した」ライブラリに分割し、テストのためにそれらを独立してコンパイルしやすくするために「ちょうど」。

質問

同じことを願ういくつかのバージョンを試してみましょう。

  • ユニットテストでアプリケーションのコードを構造化する必要があるのは、ユニットテストに「のみ」有益であるか、実際にはアプリケーション構造に有益ですか。
  • 必要とされるコードの一般化は、それがユニット・テスト可能な何のために有用にすることですが、ユニットテスト?
  • 単体テストを追加すると、不必要に一般化されますか?
  • シェイプユニットテストは、コードに「常に」強制することも、問題の領域から見た一般的なコードの良い形ですか。

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


8
よくあるミームは、どのパターンでもアンチパターンになりすぎてしまうということです。TDDでも同じことが言えます。テスト可能なコードは、追加された一般化されたテストインターフェイスよりも少ないリターンのポイントを超えて、テスト可能なインターフェイスを追加できます。深宇宙ミッションOSのようなテスト用のインターフェイスが追加されたカジュアルゲームは、市場の窓を完全に見逃す可能性があります。追加されたテストがそれらの変曲点の前にあることを確認してください。
hotpaw2

@ hotpaw2冒とく!:)
maple_shaft

回答:


23

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

問題解決の不可欠な部分をテストすることを考慮しない場合のみ。どんな些細な問題でも、ソフトウェアの世界だけでなく、そうあるべきです。

ハードウェアの世界では、これはずっと前に-難しい方法で学習されています。さまざまな機器の製造業者は、何世紀にもわたって、無数の落下橋、爆発する車、CPUの喫煙などから、ソフトウェアの世界で今学んでいることを学びました。それらはすべて、テスト可能にするために、製品に「余分な継ぎ目」を組み込みます。最近のほとんどの新しい車は、エンジン内部で何が起こっているかに関するデータを取得するための修理担当者向けの診断ポートを備えています。すべてのCPUのトランジスタのかなりの部分が診断目的に使用されます。ハードウェアの世界では、すべての「余分な」ものにコストがかかり、数百万人が製品を製造すると、これらのコストは確実に多額のお金になります。それでも、製造業者はこのすべてのお金をテスタビリティに費やすことをいとわない。

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

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

私の経験では、テスト可能な設計は全体的に有益であり、単体テスト自体のためではありません。これらの利点にはさまざまなレベルがあります。

  • 設計をテスト可能にすることで、アプリケーションを小さく、多かれ少なかれ独立した部分に分割し、限定的かつ明確に相互に影響を与えることができます-これは、プログラムの長期的な安定性と保守性にとって非常に重要です。これがないと、コードはスパゲッティコードに劣化する傾向があり、コードベースの任意の部分で行われた変更は、プログラムの外見上は無関係の異なる部分に予期しない影響を引き起こす可能性があります。それは言うまでもなく、すべてのプログラマーの悪夢です。
  • テスト自体をTDD形式で記述すると、実際にAPI、クラス、およびメソッドを実行し、設計が理にかなっているかどうかを検出する非常に効果的なテストとして機能します。 APIを簡単に作成できます。つまり、これにより、APIが時期尚早に公開されるのを防ぎます。
  • TDDによって実施される開発のパターンは、実行する具体的なタスクに焦点を当て、ターゲットを維持し、想定されている問題以外の問題を解決する可能性を最小限に抑え、不要な追加機能と複雑さを追加するのに役立ちますなど
  • 単体テストの高速フィードバックにより、コードのリファクタリングを大胆に行うことができ、コードの有効期間全体にわたって設計を絶えず適応および進化させることができるため、コードエントロピーを効果的に防止できます。

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

ユニットテストで強制される「余分な」一般化や継ぎ目なしに、ソフトウェアが行うべきことを正確に実行できることを証明できます。 (そして、このフォーラムの多くの人々が私と同じくらい興味があると確信しているので、あなたがそれをどうするか教えてください:-)

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


敬礼します
GordonM

簡潔ですが、つまらない注意:「診断ポート」は、政府が排出制御スキームの一部としてそれらを義務付けているため、ほとんどそこにあります。その結果、それには厳しい制限があります。このポートでは診断できない可能性のあるものが多数あります(つまり、排出制御に関係のないもの)。
ロバートハーヴェイ

4

私はあなたに証言の道を投げますが、要約します:

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

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

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

継承を使用して、現在使用されている単一のインスタンス以外でクラスを使用できるようにする必要がある場合、クラスが使用環境にあまりにも強く結び付けられている可能性が高くなります。これが当てはまる状況の例を挙げていただけますか?

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


私は同じものを追加しようとしていました:APIを作成し、外部からAPIをテストします。
クリストファーマハン

2

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

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

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


1

アプリケーションの最小分離可能ユニットのテスト

これは本当ですが、あまりにも遠くに行ってもあまり意味がありませんし、コストもかかります。TDDが本来あるべきものであるというBDDという用語の使用を促進しているのはこの側面だと思いますに沿って-最小の分離可能なユニットは、あなたが望むものです。

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

最終的にテストには魔法の弾丸はありません。限られたテストリソースをどの程度、どの粒度で適用するかについて、賢明な決定を下す必要があります。自動的にスタブを生成するツールベースのアプローチはこれを行いません、それは鈍いアプローチです。

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

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


0

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

準機能的な考え方からTDD C ++コードを実行して、手続き(引数を取らずvoidを返す関数)ではなく関数を記述し、可能な限り構成を使用することをお勧めします。これらのメンバークラスを置き換えるのは難しいため、これらのクラスをテストして信頼できるベースを構築し、それを他の何かに追加するときに基本ユニットが機能することを理解することに焦点を当てています。

キーは準機能的アプローチです。あなたのすべてのC ++コードがグローバルにアクセスしない無料の関数であった場合、それについて考えてください、それはユニットテストへのスナップです:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.