4

私が実装するクラスの多くが、会社の他の部門によって提供されるクラスから派生したコード ベースを持っています。これらの他の部門との共同作業は、多くの場合、サード パーティのミドルウェア ベンダーであるかのような関係を築いています。

これらの基本クラスを変更せずにテスト コードを記述しようとしています。ただし、インターフェイスがないため、意味のあるテスト オブジェクトを作成するには問題があります。

//ACommonClass.h
#include "globalthermonuclearwar.h" //which contains deep #include dependencies...
#include "tictactoe.h" //...and need to exist at compile time to get into test...

class Something //which may or may not inherit from another class similar to this...
{
public:
  virtual void fxn1(void);  //which often calls into many other classes, similar to this
  //...
  int data1;  //will be the only thing I can test against, but is often meaningless without fxn1 implemented
  //...
};

通常はインターフェイスを抽出してそこから作業しますが、これらは「サード パーティ」であるため、これらの変更をコミットすることはできません。

現在、「Working with Legacy Code」という書籍で説明されているように、必要に応じて、サードパーティが提供する基本クラス ヘッダーで定義されている関数の偽の実装を保持する別のファイルを作成しました。

私の計画は、これらの定義を引き続き使用し、必要なサードパーティ クラスごとに代替のテスト実装を提供することでした。

//SomethingRequiredImplementations.cpp
#include "ACommonClass.h"
void CGlobalThermoNuclearWar::Simulate(void) {};  // fake this and all other required functions...
// fake implementations for otherwise undefined functions in globalthermonuclearwar.h's #include files...
void Something::fxn1(void) { data1 = blah(); } //test specific functionality.

しかし、それを始める前に、私のようなコードベースで実際のオブジェクトを提供しようとした人がいるかどうか疑問に思っていました。これにより、実際のサードパーティクラスの代わりに使用する新しいテスト固有のクラスを作成できるようになります。

問題のコード ベースはすべて C++ で記述されていることに注意してください。

4

5 に答える 5

1

現在、非常によく似た問題に直面しています。テスト目的でのみ存在する一連のインターフェイスを追加したくないため、既存のモック オブジェクト ライブラリを使用できません。これを回避するために、私は同じことを行い、偽の実装を含む別のファイルを作成し、テストを偽の動作にリンクさせ、製品コードを実際の動作にリンクさせます。

この時点で私がやりたいことは、別のモック フレームワークの内部を取得し、それを偽のオブジェクト内で使用することです。次のようになります。

Production.h

class ConcreteProductionClass { // regular everyday class
protected:
    ConcreteProductionClass(); // I've found the 0 arg constructor useful
public:
    void regularFunction(); // regular function that I want to mock
}

Mock.h

class MockProductionClass 
    : public ConcreteProductionClass
    , public ClassThatLetsMeSetExpectations 
{
    friend class ConcreteProductionClass;
    MockTypes membersNeededToSetExpectations;
public:
    MockClass() : ConcreteProductionClass() {}
}

ConcreteProductionClass::regularFunction() {
    membersNeededToSetExpectations.PassOrFailTheTest();
}

ProductionCode.cpp

void doSomething(ConcreteProductionClass c) {
    c.regularFunction();
}

テスト.cpp

TEST(myTest) {
    MockProductionClass m;
    m.SetExpectationsAndReturnValues();
    doSomething(m);
    ASSERT(m.verify());
}

このすべての中で最も厄介な部分は、他のモック フレームワークがこれに非常に近いことですが、正確には実行できません。また、マクロが非常に複雑であるため、それらを適応させるのは簡単ではありません。空き時間にこれを調べ始めましたが、なかなか進んでいません。メソッドが思いどおりに機能し、期待値設定コードが配置されていたとしても、このメソッドにはまだいくつかの欠点があります。そのうちの 1 つは、リンクする必要がある場合にビルド コマンドが長くなる可能性があることです。 1 つの .a ではなく、多数の .o ファイルが必要ですが、それで十分です。リンクしていないため、デフォルトの実装にフォールスルーすることもできません。とにかく、私はこれが質問に答えていないことを知っています。

于 2009-04-22T17:05:13.060 に答える
1

モック オブジェクトは、この種のタスクに適しています。それらを使用すると、他のコンポーネントの存在を必要とせずに、それらの存在をシミュレートできます。テストで期待される入力と出力を定義するだけです。

Googleには、C++ 用の優れたモック フレームワークがあります。

于 2009-01-07T17:09:09.567 に答える
0

質問で指摘しなかったことの 1 つは、クラスが他の部門の基本クラスから派生する理由です。その関係は本当に IS-A 関係ですか?

クラスをフレームワークで使用する必要がない限り、継承よりも委任を優先することを検討できます。次に、依存性注入を使用して、単体テストでクラスのモックをクラスに提供できます。

それ以外の場合は、提供されるヘッダーから必要なインターフェイスを抽出して作成するスクリプトを記述し、これをコンパイル プロセスに統合して、単体テストをチェックインできるようにすることをお勧めします。

于 2009-01-08T19:58:43.077 に答える
0

潜在的な解決策として、ふりをするのではなく、あざけることを検討することをお勧めします。場合によっては、元のクラスがモック可能でない場合に、モック可能なラッパー クラスを作成する必要がある場合があります。私はこれを C#/.Net のフレームワーク クラスで行いましたが、C++ ではなく YMMV で行いました。

于 2009-01-07T17:03:39.640 に答える
0

テストで実行できない (または実行したくない) 何かから派生した、テストで必要なクラスがある場合、次のようにします。

  1. 新しいロジックのみのクラスを作成します。
  2. code-i-wanna-test をロジック クラスに移動します。
  3. インターフェイスを使用して実際のクラスに話しかけ、基本クラスや、ロジックに入れられない、または入れないものと対話します。
  4. 同じインターフェイスを使用してテスト クラスを定義します。このテスト クラスには、実際のクラスをシミュレートする単純なコードまたは凝ったコードしかありません。

テストで使用する必要があるクラスがあるが、実際のクラスを使用すると問題が発生する場合 (依存関係または望ましくない動作):

  1. 呼び出す必要があるすべてのパブリック メソッドのように見える新しいインターフェイスを定義します。
  2. テスト用に、そのインターフェイスをサポートするオブジェクトのモック バージョンを作成します。
  3. そのクラスの「実際の」バージョンで構築された別のクラスを作成します。また、そのインターフェイスをサポートしています。すべてのインターフェイスは、実際のオブジェクト メソッドに転送された a を呼び出します。
  4. すべてのパブリック メソッドではなく、実際に呼び出すメソッドに対してのみこれを行います。さらにテストを作成するときに、これらのクラスに追加します。

たとえば、MFC の GDI クラスを次のようにラップして、Windows GDI 描画コードをテストします。テンプレートを使用すると、これを簡単に行うことができますが、さまざまな技術的な理由 (Windows DLL クラスのエクスポートなど) により、最終的にはそうしないことがよくあります。

これはすべて Feather のWorking with Legacy Codeの本にあると確信しています - そして私が説明していることには実際の用語があります。棚から本を引っ張らせないで...

于 2009-01-07T17:22:42.270 に答える