2017-05-04 4 views
1

プリプロセッサ定義に基づいてクラスのスタブまたはモックバージョンを作成したいと思います。プリプロセッサマクロが "enable"に設定されている場合、クラスは通常のクラスに過ぎません。 "disable"に設定されている場合、クラスは空のスタブであり、コンパイラは完全に離れて最適化することができます。しかし、実際のクラスで正常にコンパイルされたりコンパイルされなかったりするコードは、対応する振る舞いもスタブクラスで持つ必要があります。 isset()の定義はif (a_foo.isset()) { code(); }のようなコードは何にも離れて最適化することができるように意図されてC++クラスのスタブバージョンを作成する

class _foo { 
public: 
    foo(int x) : x_(x) {} 
    void add(int x) { x_ += x; } 
    void add(const char *str) { x_ += atoi(str); } 
    bool isset(void) { return x_ > 0; } 
private: 
    int x_; 
}; 

#if ENABLE_FOO 
using foo = _foo; 
#else 
class foo { 
public: 
    foo(int x) {} 
    void add(int x) { return; } 
    void add(const char *str) { return; } 
    bool isset(void) { return false; } 
}; 
#endif 

:ここ

が、これがどのように見えるかの例です。明らかに、これは、どのメソッドおよびそのメソッドの使用に対しても普遍的には機能しません。 0、偽、NULLなどが無効な場合の合理的な戻り値になるようにクラスを設計する必要があります。

これはうまくいきますが、fooのスタブバージョンを実際のバージョンと完全に同期させておく必要があります。どのメソッドへの変更もすべて、スタブに複製する必要があります。これは迷惑です。どのようにスタブをより自動化するのですか?理想的にはまたはSTUB(foo)と書くことができ、スタブクラスはそれだけで作成されます。この目的を達成するために

、私がこれまで思い付くことができました:

_fooのスタブバージョンを作成
class foo { 
public: 
    CTOR_STUB(_foo, foo); 
    METHOD_STUB(_foo, add); 
    METHOD_STUB(_foo, isset); 
}; 

。各メソッド名をリストする必要がありますが、戻り値の型も引数も引数の数も指定する必要はありません。すべての過負荷(すなわち、add()の両方の方法)は、METHOD_STUBでカバーされています。オーバーロードには異なる戻り値の型があります。スタブ付きメソッドがメソッドテンプレートの場合でも機能します。ここで

はこれを行うマクロです:

#define METHOD_STUB(base, func) \ 
    template <typename... Args> \ 
    auto func(Args... args) { \ 
     using RetType = decltype(std::declval<base>().func(std::forward<Args>(args)...)); \ 
     return (RetType)0; } 

#define CTOR_STUB(base, name) \ 
    template <typename... Args> \ 
    name(Args... args) { return; base _dummy{std::forward<Args>(args)...}; } 

アイデアはまだ、適切な引数や戻り値の型を持つメソッドが正常にコンパイルするために、スタブクラスに存在することを要求するテンプレートを定義することですコンパイラによって何も最適化されません。

マクロを避ける方法はありますか?これはテンプレートのみを使用しますか?メソッドの名前をテンプレートパラメータにしたいと思うようですが、私はそれを行う方法がわかりません。

CTOR_STUB()に現在のクラスの名前を指定する必要性を避ける方法はありますか?コンパイラは名前を知っていますが、名前をコンストラクタテンプレートの定義に使用できるシンボルとして取得する方法や、クラス名をテキスト文字列または型として取得する方法はわかりません。

クラスの実際のバージョンが同じことをしないときに、スタブが正しくコンパイルされるか、コンパイルに失敗するといういくつかの欠陥がありますか?

+0

シグネチャの変更を伝播することは、別々の定義と宣言を持つ一般的なクラス(つまり非テンプレート)の変更を伝播することとまったく同じではありません。それを忘れると、コンパイル時にエラーが発生します。私はここではマクロを使わなくてはならないというメリットは見られません。 –

+0

'(RetType)0'は危険です。おそらく 'static_cast (0)'を意味します。 – cdhowie

+0

@NirFriedmanの違いは、署名が変わったときに更新する場所がもう1つありますが、条件付きコンパイルです。 "有効"の場合にスタブとビルドを更新することを忘れると、スタブが使用されないのでエラーは発生しません。また、クラスのユーザーを更新できない場合、「無効」の場合はエラーは発生しません。それらを自動的にリンクすることで、有効なケースと無効なケースの両方で同じコンパイル時間のチェックを受けることができます。 – TrentP

答えて

1

ない完璧なソリューションがありますが、

#ifdef ENABLE 
#define IF_ENABLED(x) x 
#define IF_DISABLED(x) 
#else 
#define IF_ENABLED(x) 
#define IF_DISABLED(x) x 
#endif 

class Foo { 
public: 
    foo(int x) IF_ENABLED(: x_(x)) {} 
    void add(int x) { IF_ENABLED(x_ += x;) } 
    void add(const char *str) { IF_ENABLED(x_ += atoi(str);) } 
    bool isset(void) { IF_ENABLED(return x_ > 0;) IF_DISABLED(return false;) } 
private: 
#ifdef ENABLE 
    int x_; 
#endif 
}; 
0

次のように完全に(ビルドシステムから入るもの以外)のマクロを避ける良い解決策がありませんでした。

#if ENABLE_FOO 
constexpr bool g_use_foo = true; 
#else 
constexpr bool g_use_foo = false; 
#endif 

template <bool FooEnabled> 
struct Foo { 
    void bar1() {} 
}; 

template <> 
void Foo<true>::bar1() { std::cerr << "not a mock\n"; } 

using UserFoo = Foo<g_use_foo>; 

ライブの例を: http://coliru.stacked-crooked.com/a/ecdb7c1a7f0a6068

基本的には、テンプレートクラスを宣言し、一般的な簡単な模擬実装をインラインにします。体の外には、実際の機能を持つクラスのための専門化を定義します。宣言インラインとアウトラインが正確に一致する必要はありません。それ以外の場合は、宣言されていないものを特化しています。したがって、一方を更新して他方を更新しないと、コンパイル時エラーが発生します。これは、インライン宣言と定義がある典型的な非テンプレートクラスをよく反映しているので、かなり妥当だと思います。

これは、実際のオブジェクトのメソッドを削除するか実装するのを忘れた場合のみです。これはまだコンパイルされますが、実際のオブジェクトを使用していると思ってもモック機能を取得できます。しかし、私はこれを大きな懸念事項とは見なしていません。その場合、最も基本的な1行単位のテストでさえ失敗するでしょう。これを心配している場合は、次のように書くことができます:

template <bool FooEnabled> 
struct Foo { 
    void bar1() { static_assert(!FooEnabled, "");} 
}; 

状態もかなり簡単にカバーできます。ただ、個人的に状態を持つ構造体から継承します。両方のクラスが完全にビルドに関係なく定義されており、使用可能なされている

template <bool FooEnabled> 
struct Foo : private std::conditional_t<FooEnabled, FooState, EmptyStruct> { 
    void bar1() {} 
}; 

注意、これはテストとツーリングのためにかなりの利点があります。

関連する問題