web-dev-qa-db-ja.com

モックをstd :: unique_ptrとしてテスト中のクラスに渡す方法

私はgoogletestとgooglemockを使用していくつかのテストユニットを書いていますが、C++ 11スマートポインターとポリモーフィズムに関連する問題で立ち往生しています。

次のクラスがあるとします。

class A {
public:
    virtual void doThings() {...};
};

class B {
private:
    B(std::unique_ptr<A> a): a_(std::move(a)) {}
    std::unique_ptr<A> a_;
};

クラスBをテストしたいので、Aのモックを作成し、コンストラクターに渡します。

class MockA: public A {
public:
    virtual ~MockA() {}
    MOCK_METHOD0(doThings, void());
};

TEST(...) {
    auto ma = std::make_unique<MockA>();
    B b(std::move(ma));

    // Set expectation on mock

    // call B method
}

問題はこれです:

  • モックインはBインスタンス内に移動されたため、maがnullであるため、期待値検証で例外がスローされます。

それを解決するための私の最初の試みは、次のようにBを変更することでした:

class B {
private:
    B(std::unique_ptr<A>& a): a_(a) {}
    std::unique_ptr<A>& a_;
};

現在、Bは一意のポインタreferencesを処理し、std::moveを使用しません。テストも変更されました(std::moveの使用はなくなりました):

TEST(...) {
    auto ma = std::make_unique<MockA>();
    B b(ma);

    // Set expectation on mock

    // call B method
}

(エラーを正しく理解している場合)参照が多態的ではないため、コードはコンパイルされません(MockAの一意のポインターをAの一意のポインターにキャストできません)。

スマートポインターに関する基本的な情報が不足していますか?または、これは一意のポインタで予想される動作なので、クラスを再考する必要があります(共有ポインタを使用している可能性があります)。

1
Marco Stramezzi

最も簡単な方法は、クラスBを以前とまったく同じに保つことですが、テストケースで「b」に渡す前に「ma」からポインタを取得します。そのようです…

TEST(...){
    auto ma = std::make_unique<MockA>();
    //this will be valid even after the current unique_ptr transfers ownership
    //It will be invalidated, however, if a smart pointer deletes it
    auto ( or MockA*) rawPtr = ma.get();
    B b(ma);

    //ma now points to NULL, but rawptr is still valid
    //Run analysis on mock via rawPtr
}

このような方法でBの署名を変更することは賢明ではありません。クラスにポインターの排他的所有権を持たせたい場合は、そのポインター用に独自のunique_ptrが必要です。 Bのunique_ptrを参照に変更すると、ポインターを所有するように動作するようにクラスを記述したとしても、Bは技術的にポインターを所有しなくなります。これは、いくつかの本当に奇妙な動作/クラッシュにつながる可能性があります。

BとAの両方がポインターの所有権を持つことを期待する場合、つまり、Bと別のクラス/関数が生きている/実行している間、ポインターが有効である必要がある場合は、shared_ptrを使用する必要があります。ポインターが有効である必要があるのは、Bだけが生きている間だけである場合は、unique_ptrを使用してBのメンバーにする必要があります。

コードの残りの部分を確認しないと、確実に言うことはできませんが、この場合は、unique_ptrをBのメンバーとして使用し続けたいと確信しています。

[〜#〜]編集[〜#〜]

Calethが引き出したように、unique_ptrを持つBの全体的な目的は、ポインタの所有権を持つことです。したがって、Bがptrを削除するときだと判断した場合、そのメモリは無効になり、rawPtrを介してそれにアクセスしようとすると、非常に悪いことが起こります(未定義の動作、クラッシュなど)。

これを行うのは、Bがunique_ptrを処理する方法を知っている場合のみです。 Bがptrを削除するか、unique_ptrがスコープ外になるまで、rawPtrにのみアクセスしてください。

4
Ryan

そもそも Ryanの答え は正当です。これは、モックがホワイトボックステスト環境で使用されているためです。つまり、プログラマーはすべてのソースコードを見ることができ、スマートポインターの所有権に違反する方法を正確に知っています。コードが正しく機能することを保証します。

2つのオブジェクトが "Dead drop"配置 でデータを共有する必要がある場合、次の図は設定を示しています。

Diagram illustrating two objects sharing data via a "dead drop" arrangement without direct communication

「spymaster」は、すべてを制御する単体テストコードです。 「情報提供者」(「デッドドロップ」)のインスタンスは、std::shared_ptrとして作成されます。この情報提供者はsetterメソッドを持ち、これは(裏切り者)(データ受信用)から呼び出され、getterメソッドデータを「スパイマスター」が取得できるようにします。

「スパイマスター」は「裏切り者」をstd::unique_ptrとしてインスタンス化し、「無知」(テスト中のコード)に渡します。いったん引き渡されると、「スパイマスター」は「裏切り者」に直接アクセスできなくなります。代わりに、テスト対象のコードと対話し、対話が完了すると、「インフォーマント」または「デッドドロップ」からデータを取得します。

3
rwong