web-dev-qa-db-ja.com

モッキングフリー機能

問題が発生し、解決策が見つからないようです。

コードのコンパイルにVS2005 SP1を使用しています。

私はグローバル機能を持​​っています:

_A* foo();
_

モッククラスがあります

_class MockA : public A {
public:
    MOCK_METHOD0 (bar, bool());
    ...
};
_

ソースでは、foo()->bar()のようにアクセスします。私はこの振る舞いをあざける方法を見つけることができません。そして、私はソースを変更できないので、グーグルモッククックブックのソリューションは疑わしいです。

正しい方向のヘルプやポインタは高く評価されます。 :)

12
Muhammad Hassan

ソースを変更したり、実行可能コードにリンクされている独自のバージョンのfoo()を用意したりしなければ、それは不可能です。


GoogleMockのFAQ より

私のコードは静的/グローバル関数を呼び出します。あざけることはできますか?

できますが、いくつか変更する必要があります。

一般に、静的関数をモックする必要がある場合は、モジュールの結合が強すぎる(そして柔軟性が低く、再利用可能性が低く、テスト可能性が低いなど)ことを示しています。小さなインターフェイスを定義し、そのインターフェイスを介して関数を呼び出す方がよいでしょう。そうすれば、簡単にモック化できます。最初は少し手間がかかりますが、通常はすぐに元が取れます。

このGoogle Testing Blog post はそれを非常によく述べています。見てみな。

クックブック からも

フリー関数のモック

Google Mockを使用して、フリー関数(つまり、Cスタイルの関数または静的メソッド)をモックすることができます。インターフェイス(抽象クラ​​ス)を使用するには、コードを書き換えるだけです。

フリー関数(OpenFileなど)を直接呼び出す代わりに、そのためのインターフェースを導入し、フリー関数を呼び出す具象サブクラスを用意します。

_class FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) = 0;
};

class File : public FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) {
    return OpenFile(path, mode);
  }
};
_

コードは、ファイルを開くためにFileInterfaceと通信する必要があります。これで、関数をモックアウトするのは簡単です。

これはかなり面倒に思えるかもしれませんが、実際には、多くの場合、同じインターフェースに配置できる複数の関連関数があるため、関数ごとの構文オーバーヘッドははるかに低くなります。

仮想関数によって発生するパフォーマンスのオーバーヘッドが心配であり、プロファイリングで問題が確認された場合は、これを非仮想メソッドのモックのレシピと組み合わせることができます。


foo()の独自のバージョンを実際に提供するとコメントで述べたように、別のモッククラスのグローバルインスタンスを使用してこれを簡単に解決できます。

_struct IFoo {
    virtual A* foo() = 0;
    virtual ~IFoo() {}
};

struct FooMock : public IFoo {
     FooMock() {}
     virtual ~FooMock() {}
     MOCK_METHOD0(foo, A*());
};

FooMock fooMock;

// Your foo() implementation
A* foo() {
    return fooMock.foo();
}

TEST(...) {
    EXPECT_CALL(fooMock,foo())
        .Times(1)
        .WillOnceReturn(new MockA());
    // ...
}
_

テストケースを実行するたびに、すべてのコールの期待値をクリアすることを忘れないでください。

16

もちろん、GTest/GMockのドキュメントに従ってソリューションを説明する答えは、はるかに正確である可能性があります。

しかし、一時的で汚いアプローチを追加したいと思います。これは、レガシーC/C++コードを可能な限り迅速かつ非侵襲的にテストしたい場合に適用できます。 (修正、リファクタリング、より適切なテストをできるだけ早く続行する必要があります。)

したがって、テストするコードに表示される無料の関数void foo(int)をモックするには、ソースファイル内で次のように変更するだけです。

#if TESTING
#define foo(param) // to nothing, so calls to that disappear
#endif

// ... code that calls foo stays untouched and could be tested

マクロTESTINGは、コードがテストの下で実行されることを示しますが、GTest/GMockには付属していません。自分でテストターゲットに追加する必要があります。

可能性はかなり限られていますが、質問の例のA*のように、戻り値の型に役立つものを作成できる場合もあります。

残念ながら、これもコードを変更しないと解決策にはなりません。それが本当に必要な場合は、「継ぎ目をリンク」するためにグーグルできます。しかし、私の推測では、これは実際にはかなり面倒なことかもしれません。そして、それは多くの/ほとんどの場合でまったく不可能であるかもしれません!?

1
yau

2つのオプションがあります。

Gmockの使用を主張する場合は、アプリオリットからのグローバルモッキングの「拡張機能」があります。 https://github.com/apriorit/gmock-global

しかし、それはかなり制限されています-または、少なくとも5分では、偽の呼び出しに副作用をもたらす方法を理解できませんでした。

Gmockからの切り替えを希望する場合は、hippomocksを使用すれば、思い通りの操作を行うことができます。

次に、cstdioを使用してファイルから読み取るメンバー関数をテストするためのfopen、fclose、およびfgetsをモックする例を示します(ストリームは非常に非効率的です)。

TEST_CASE("Multi entry") {
    std::vector<std::string> files{"Hello.mp3", "World.mp3"};
    size_t entry_idx = 0;
    MockRepository mocks;
    mocks.OnCallFunc(fopen).Return(reinterpret_cast<FILE *>(1));
    mocks.OnCallFunc(fgets).Do(
        [&](char * buf, int n, FILE * f)->char *{ 
            if (entry_idx < files.size())
            {
                strcpy(buf, files[entry_idx++].c_str());
                return buf;
            }
            else
                return 0;
            }
        );
    mocks.OnCallFunc(fclose).Return(0);

    FileExplorer file_Explorer;
    for (const auto &entry: files)
        REQUIRE_THAT(file_Explorer.next_file_name(), Equals(entry.c_str()));
    REQUIRE_THAT(file_Explorer.next_file_name(), Equals(""));
}

テスト対象の関数は次のようになります。

string FileExplorer::next_file_name() {
    char entry[255];
    if (fgets((char *)entry, 255, _sorted_entries_in_dir) == NULL)
        return string();
    _current_idx++;
    if (_current_idx == _line_offsets.size())
        _line_offsets.Push_back(static_cast<unsigned>(char_traits<char>::length(entry)) + _line_offsets.back());
    return string(entry);
} 

ここではテストフレームワークとしてcatch2を使用していますが、カバはGoogleのテストフレームワークでも機能すると思います(ところで、私はcatch2をお勧めします)。

0
Mr.WorshipMe

私のために働いているのは

  • メインプロジェクトの別のソースファイル_foo.cpp_でA* foo()を定義するには、
  • notテストプロジェクトに_foo.cpp_を含めるには、
  • A* foo()のモック実装を提供するテストプロジェクトに別のソースファイル_mock-foo.cpp_を含めます。

たとえば、メインプロジェクトファイルの疑似コード(_.vcxproj_または_CMakeLists.txt_など):

_include src/foo.hpp # declare A* foo()
include src/foo.cpp # define A* foo()
_

そしてテストプロジェクトファイル:

_include src/foo.hpp
include test/mock-foo.cpp # define mocked A* foo()
_

シンプルで甘いですが、あなたのケースではうまくいかないかもしれません。

0
mrts