2009-06-29 15 views
5

クラスをテスト可能にするために、インターフェイスを使ってすべての依存関係を解除するのはいいですか?通常のメソッド呼び出しではなく、多くの仮想呼び出しのため、実行時に大きなオーバーヘッドが発生します。C++での単体テスト

実世界のC++アプリケーションでテスト駆動型開発がどのように機能しますか?私はWorking Effectively With Legacy Codeを読んで、かなり好きですが、TDDの練習をスピードアップすることはできません。

私はリファクタリングを行うと、非常に頻繁に発生し、大規模なロジック変更のためにユニットテストを完全に書き直す必要があります。私のコードの変更は、データ処理の基本的なロジックを変更することがよくあります。大規模なリファクタリングで変更する必要のない単体テストを書く方法はありません。

誰かがTDDを使って例を学ぶオープンソースのC++アプリケーションを教えてくれるかもしれません。

答えて

5

更新は:this question too.

を参照してください。私はここにいくつかの部分に答えることができます。

はただクラスがテスト可能にするインターフェースを使用して、すべての依存関係を壊すために大丈夫ですか?通常のメソッド呼び出しではなく、多くの仮想呼び出しのため、実行時に大きなオーバーヘッドが発生します。

パフォーマンスがそれのために、無あまり苦しむことになる場合は(ベンチマークを!)。あなたの開発があまりにも苦しんでいるなら、いいえ(余分な努力を見積もる)。それが大事なことではなく、長期的には助けとなり、品質であなたを助けてくれるようなら、そうです。

あなたはいつもあなたのテストクラスをテストすることができますし、TestAccessorオブジェクトを使用することでテストでテストすることもできます。これにより、テストのためだけにすべてを動的にディスパッチ可能にすることが回避されます。 (かなりの作業のように聞こえる)

テスト可能なインターフェイスの設計は簡単ではありません。場合によっては、テストのためだけに内部にアクセスするいくつかの追加メソッドを追加する必要があります。それはあなたが少しひどくなりますが、それらの機能が実際のアプリケーションにも役立ちます。

私がリファクタリングを行うと、非常に頻繁に発生し、大規模なロジック変更のためにユニットテストを完全に書き直す必要があります。私のコードの変更は、データ処理の基本的なロジックを変更することがよくあります。大規模なリファクタリングで変更する必要のない単体テストを書く方法はありません。

ディフェンションによる大規模なリファクタリングは、テストを含めて多く変更されます。リファクタリング後もテストをしてくれるので幸せです。

新しい機能を作成するよりもリファクタリングに時間を費やしている場合は、多少の変更を余儀なくされるより良いインターフェイスを見つけるために少し前に考えてください。また、インターフェイスが安定する前にユニットテストを書くことは、あなたが何をしていても苦痛です。

多くのコードが変更されたインターフェースに対して、より多くのコードを変更する必要があります。私はあなたの問題がそこにあると思う。私はほとんどの場所で十分に安定したインターフェースを持ち、今やリファクタリングだけの部品を管理してきました。

希望します。

+0

私はコーディングする前に私は考えていません。多くの場合、クライアントが新しい機能の初期実装を見た後に要件が変更されます。もう一つの理由は、マーケティングの理由から、迅速かつ汚れた実装を行うことが時折必要であるということです。この場合、私は単体テストを気にしませんが、タイトなスケジュールのために速く汚れたハックが本当になることがあります。後でテスト可能にすることは容易ではありません。 – frast

+0

申し訳ありませんが、私はそれを正確に暗示するつもりはありませんでした。 ;)私は実際の人生はときどき働くことが難しいことを知っています。 Quick'n汚れたハックは、不足しているテスト、必要なリファクタリング/クリーンアップ、または通常は両方の債務を伴います。現金で最初に(適切な仕事をして)、またはその後に家賃で支払う(清算)。それを回避することはできません。あなたがそれに影響を与えることができない条件の下で働くのは難しいです。 (そこにいた...) – Macke

3

CとC++で単体テストを行うために、マクロ、#ifなどのプリプロセッサのトリックを使用して依存関係をモックアウトしています。そのようなマクロでは、実行時に支払う必要はないからですコードをテスト用ではなく生産用にコンパイルする場合のコストエレガントではありませんが、合理的に効果的です。

リファクタリングに関しては、あなたが説明するように圧倒的に大きくて威圧的なときにテストを変更する必要があります。私は自分自身がリファクタリングしているので、それほど頻繁にリファクタリングしているわけではありません。

+1

私はあなたのソリューションについて考えていますが、私はプリプロセッサーの定義のために "実際のコード"ではないことが偶然にも起こることを恐れています。しかし、私はそれを試す価値があると思います。 – frast

+0

ええ、マクロや他のプリプロセッサのハックは、細心の注意を払って特定の限られたパターンに従って使用しない限り、壊れやすいものです。依存関係注入のマクロに相当する(場合によっては@jaifのように、テンプレートやtypedefによって達成されることもありますが)非常に限定された訓練されたケースですが、それはあなたの依存関係を「模倣」するために必要なものですユニットテストの目的で使用します。 –

1

明白な答えは、インターフェイスではなくテンプレートを使用して依存関係を除外することです。もちろん、コンパイル時には(コンパイル時の方法にもよりますが)コンパイル時に問題が生じるかもしれませんが、実行時のオーバーヘッドは最低限に抑える必要があります。少し単純な解決策は、いくつかのマクロまたは同様のものと交換できるtypedefのセットに頼ることだけかもしれません。あなたの最初の質問に関しては

1

- 時々あなたがリファクタリングの一環として、よりよいそれらを作る前に、物事を破るために持っているかもしれませんが、それは、単にテストのために物事を破ることはめったにない価値があります。ソフトウェア製品の最も重要な基準は、それが動作可能であることです。テスト可能ではありません。テスト可能とは、エンドユーザーにとってより安定して機能する製品を作成するのに役立ちます。

テスト駆動開発の大きな部分は、単体テストのために変更される可能性の低い、小さなコード部分を選択することです。大規模なロジック変更のために多くの単体テストを書き直す必要がある場合は、よりきめ細かなレベルでテストするか、コードが安定するようにコードを再設計する必要があります。安定した設計は時間の経過とともに大幅に変わるべきではなく、テストが必要となる大規模なリファクタリングを回避するのに役立つものではありません。しかし、正しいテストが行​​われた場合、リファクタリングが成功した場合、変更する必要のないテストがいくつかあることを前提に、リファクタリングを成功させることができます。

1

クラスをテスト可能にするために、インターフェイスを使用してすべての依存関係を解除しても問題ありませんか?通常のメソッド呼び出しではなく、多くの仮想呼び出しのため、実行時に大きなオーバーヘッドが発生します。

より良いインターフェースにつながるので、依存関係を解除することは問題ないと思います。

私がリファクタリングを行うと、非常に頻繁に発生し、大規模なロジック変更のためにユニットテストを完全に書き直す必要があります。私のコードの変更は、データ処理の基本的なロジックを変更することがよくあります。大規模なリファクタリングで変更する必要のない単体テストを書く方法はありません。

テストではコードの本意を表現する必要があるため、これらの大きなリファクタリングはどの言語でも乗りません。したがって、ロジックが変更された場合は、テストを変更する必要があります。

たぶん、あなたが本当にTDDを行っていない、のような:

  1. は、コードを修正失敗した別のテストを作成し、テスト
  2. を渡すようにコードを作成します
  3. を失敗したテストを作成します。両方のテストに合格する
  4. コードが何をすべきかを示す十分なテストがあると思われるまで、リンスして繰り返します。

これらの手順では、大きな変更ではなくマイナーな変更を行う必要があると言います。あなたが後者にとどまっているなら、大きなリファクタを逃れることはできません。コンパイル時、リンク時間、エラーメッセージが間違っているなどの理由で、C++は最悪のものになります。

私は実際にC++で書かれた現実世界のソフトウェアで作業していますその下には巨大なレガシーコードがあります。私たちはTDDを使用しており、実際にはソフトウェアの設計を進化させています。

0

私がリファクタリングを行うと、非常に頻繁に発生し、大規模なロジック変更のためにユニットテストを完全に書き直す必要があります。 ...大規模なリファクタリングで変更する必要のない単体テストを書く方法はありません。

There are multiple layers of testingであり、これらの層のいくつかは大きなロジック変更の後でも破損しません。一方、単体テストはメソッドやオブジェクトの内部をテストするためのものであり、それより頻繁に変更する必要があります。必ずしも間違っていることはありません。それは物事がどういうものなのかです。

クラスをテスト可能にするためにインターフェイスを使用してすべての依存関係を解除するのは大丈夫ですか?

It's definitely OK to design classes to be more testable。結局のところ、それはTDDの目的の一部です。

単純なメソッド呼び出しではなく、多くの仮想呼び出しのために実行時に大きなオーバーヘッドが発生します。

すべての従業員が従わなければならない規則のリストがあります。無知な企業は、彼らが考えることができるすべての良質(「従業員は効率的、責任ある、倫理的で、決してコーナーを切らない」)を単にリストしています。よりインテリジェントな企業は実際に優先順位をランク付けします。誰かが効率的であるために非倫理的なやり方を思いついたら、会社はそれをやりますか?最高の企業は、プライオリティがランク付けされていることを示すパンフレットを印刷するだけでなく、経営陣がランキングに従うことを確認します。

プログラムが効率的で簡単にテストできることは全く可能です。しかし、より重要なものを選択する必要がある時があります。これはその時代の一つです。私はあなたとあなたのプログラムにとってどれほど重要な効率性があるのか​​分かりませんが、そうしています。だから、「遅くて徹底的にテストされたプログラム、または完全なテストカバレッジのない速いプログラムを持っていますか?

+0

あなたは非常に良い点を作っています。あなたの最後の質問には、ケースバイケースのパフォーマンスとテストのカバレッジについて考えるべきだと思います。アプリケーションのすべての部分で最大のパフォーマンスが必要なわけではありません。 – frast

0

それは理由 代わりにプレーンなメソッド呼び出しの多くの仮想呼び出しの 実行時に大きなオーバーヘッドを必要とします。

インターフェイスまたはオブジェクトに対してポインタ(またはリファレンス)を使用してメソッドにアクセスするのは、仮想呼び出しオーバーヘッドに過ぎないことに注意してください。スタック内の具体的なオブジェクトを通してメソッドにアクセスすると、仮想オーバーヘッドは発生せず、インライン化することもできます。

また、コードをプロファイリングする前にこのオーバーヘッドが大きいと想定しないでください。あなたの方法が何をしているのかを比較すれば、ほとんどの場合、仮想呼び出しは価値がありません。 (ペナルティの大部分は、コールの余計な間接的な指示ではなく、1行の方法をインライン化することができないことから生じる)。

関連する問題