19

#include ディレクティブを使用して、物理的な依存関係が大きいレガシー コード モジュールの単体テストを書き始めたところです。私は非常に退屈だと感じたいくつかの方法 (空のヘッダーを提供して長い #include 依存関係リストを分割し、#define を使用してクラスがコンパイルされないようにする) を処理してきましたが、これらの問題を処理するためのより良い戦略を探していました。

私は頻繁に、ほとんどすべてのヘッダー ファイルを空のバージョンで複製して、テストしているクラス全体を分離し、必要なオブジェクトの実質的なスタブ/モック/偽のコードを記述するという問題に遭遇しています。それらは現在未定義であるため、置き換えられました。

より良いプラクティスを知っている人はいますか?

4

6 に答える 6

10

応答の落ち込みには圧倒されます... しかし恐れる必要はありません。従来の C++ コードの悪魔を追い払うための聖典があります。レガシ C++ コードを 1 週間以上も試してみたいという人は、真剣にこの本を購入してください。

127 ページに目を向けてください:恐ろしいインクルード依存関係の事例。(今、私はマイケル・フェザーズから何マイルも離れていませんが、ここで私が管理できる限りの短い答えです..)

問題: C++ では、クラス A がクラス B について知る必要がある場合、クラス B の宣言は、クラス A のソース ファイルにそのまま/テキストで含まれています。そして、私たちプログラマーはそれを間違った極端に扱うのが好きなので、ファイルは無数の他のファイルを推移的に再帰的に含めることができます。ビルドには何年もかかります..しかし、少なくともそれがビルドされる..待つことができます.

「テスト ハーネスの下で ClassA をインスタンス化するのは難しい」と言うのは控えめな表現です。(MF の例を引用 - スケジューラは deps 豊富な私たちのポスター問題の子です。)

#include "TestHarness.h"
#include "Scheduler.h"
TEST(create, Scheduler)     // your fave C++ test framework macro
{
  Scheduler scheduler("fred");
}

これにより、ビルド エラーが相次ぐインクルード ドラゴンが表示されます。
Blow#1 忍耐と永続性: 各インクルードを一度に 1 つずつ実行し、その依存関係が本当に必要かどうかを判断します。SchedulerDisplay がそのうちの 1 つであり、その displayEntry メソッドが Scheduler の ctor で呼び出されるとします。
Blow#2 Fake-it-till-you-make-it (RonJ に感謝):

#include "TestHarness.h"
#include "Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
  Scheduler scheduler("fred");
}

そしてポップは依存関係とそのすべての推移的なインクルードに行きます。Fakes.h ファイルにカプセル化してテスト ファイルに含めることで、Fake メソッドを再利用することもできます。
Blow#3 練習: いつもそれほど単純ではないかもしれません..しかし、あなたはアイデアを得る. 最初の数回の決闘の後、依存関係を破るプロセスは簡単で機械的なものになります

警告(警告があると言いましたか? :)

  • このファイルには、テスト ケース用の別のビルドが必要です。プログラムで SchedulerDisplay::displayEntry メソッドの定義を 1 つだけ持つことができます。そのため、スケジューラ テスト用に別のプログラムを作成します。
  • プログラム内の依存関係を壊していないため、コードをよりクリーンにしていません。
  • テストが必要な限り、これらの偽物を維持する必要があります。
  • しばらくの間、あなたの美的感覚が損なわれるかもしれません..ただ唇を噛んで「より良い明日のために我慢してください」

この手法は、依存関係の問題が深刻な非常に大きなクラスに使用します。頻繁にまたは軽く使用しないでください。より深いリファクタリングの開始点としてこれを使用してください。時間の経過とともに、このテスト プログラムは、(独自のテストを使用して) より多くのクラスを抽出するにつれて、納屋の後ろに持ち込むことができます。

詳細については、本を読んでください。かけがえのない。仲間と戦え!

于 2008-09-15T19:22:35.633 に答える
1

私はあなたの質問に直接答えているわけではありませんが、大量のレガシーコードを使用している場合は、単体テストを実行できない可能性があります。

グリーンフィールド開発プロジェクトでXPチームを率いた後、私はユニットテストが大好きでした。物事が起こり、数年後、私は多くの品質問題を抱えている大規模なレガシーコードベースに取り組んでいることに気付きました。

アプリケーションにユニットテストを追加する方法を見つけようとしましたが、最終的にはキャッチ22でスタックしました。

  1. 意味のある完全な単体テストを作成するには、コードをリファクタリングする必要があります。
  2. 単体テストがないと、コードをリファクタリングするのは危険すぎます。

ヒーローのように感じて、単体テストでクールエイドを飲む場合でも、試してみることができますが、価値の低いテストコードが増えてしまい、これも維持する必要があるという本当のリスクがあります。

作業するように「設計」された方法でコードを作業するのが最善の場合もあります。

于 2008-09-15T17:58:01.687 に答える
1

レガシーコードをテストしているので、そのコードをリファクタリングして依存関係を減らすことはできないと思います(たとえば、pimpl イディオムを使用して)

そうなると選択肢がほとんどなくなります。型または関数に含まれていたすべてのヘッダーには、すべてをコンパイルするためにその型または関数のモック オブジェクトが必要です。できることはほとんどありません...

于 2008-09-15T17:53:15.737 に答える
1

これがあなたのプロジェクトでうまくいくかどうかはわかりませんが、ビルドのリンク段階から問題を攻撃しようとするかもしれません。

これにより、 #include 問題が完全に解消されます。インクルード ファイル内のインターフェイスを再実装して、必要なことを実行し、インクルード ファイル内のインターフェイスを実装するために作成したモック オブジェクト ファイルにリンクするだけです。

この方法の大きな欠点は、ビルド システムが複雑になることです。

于 2008-09-15T18:24:00.047 に答える
0

スタブ/モック/フェイクコードを書き続けると、メインプロジェクトでコンパイルした場合とは異なる動作をするクラスで単体テストを実行するリスクがあります。

しかし、それらのインクルードが存在し、追加の動作がない場合は、問題ありません。

単体テストを実行している間は、インクルードで何も変更しないようにします。これにより、実際のコードをテストしていることを確認できます(レガシーコードを使用できる限り:))。

于 2008-09-15T17:57:56.210 に答える
0

あなたは間違いなく、大きな依存関係を持つレガシーコードで岩と困難な場所の間にいます。あなたはそれをすべて整理するために先に長いハードスローを持っています。

あなたの言うことから、あなたは各モジュールのソースコードを順番に無傷に保ち、外部の依存関係をモックアウトしたテストハーネスに配置しようとしているようです。ここでの私の提案は、依存関係を排除(または反転)するためにリファクタリングを試みるというさらに勇敢なステップを踏むことです。これはおそらくあなたが避けようとしているまさにそのステップです。

あなたがテストを書くときに依存関係があなたを殺すだろうと私は推測しているので、私はこれを提案します。あなたが依存関係を排除することができれば、あなたは確かに長期的にはより良いでしょう。

于 2008-09-15T18:09:34.420 に答える