googlemockのつかいかたまとめ

http://src.chromium.org/svn/trunk/src/remoting/client/chromoting_view_unittest.cc
ここらへんとかを読み解きながら。

基本の書き方

class Hoge{
public:
    Hoge(){};
    ~Hoge(){};

public:
    int hogeMethod1(int x){ return x;};
};

というクラスがあった時は、まずモッククラスを作成する。

using ::testing::Mock;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;

class MockHoge : public Hoge{
public:
    MockHoge(){
        //モックのメソッドが呼び出されたときに、Hogeクラスの対応するメソッドを呼び出すように設定する(書かなくてもよい)
        ON_CALL(*this, hogeMethod1(_)).WillByDefault(Invoke(&hoge_, &Hoge::hogeMethod1));
    };
    //デコンストラクタはvirtualにすると書いてあったけどしなくてもよいかも?
    virtual ~MockHoge(){};

    //引数が0だったらMOCK_METHOD0, 1だったらMOCK_METHOD1,...と書いていく
    //第1引数はメソッド名、第二引数は"戻り値(引数)"
  MOCK_METHOD1(hogeMethod1, int(int));

private:
    Hoge hoge_;  //ON_CALL用。コンストラクタで書かない場合はいらない。
};

テストメソッドは以下のようにかく。

//TEST(任意、任意)
TEST(MockHogeTest, invokeTest){
    MockHoge mh;
    //引数が何であってもよい。1回呼ばれたらsuccessを返してくれる
    EXPECT_CALL(mh, hogeMethod1(_)).Times(1);
    EXPECT_EQ(1, mh.hogeMethod1(1));

    //引数指定、WillOnce(Return(戻り値))で戻り値が本来かえってくるものとは別のものを指定することも可
    EXPECT_CALL(mh, hogeMethod1(1)).Times(1).WillOnce(Return(10));
    EXPECT_EQ(10, mh.hogeMethod1(1));
}

あるメソッドの中で同じクラスのメソッドが呼び出されることがあって、それをテストしたい場合はやり方が少し違うので注意。
例えばこんなクラスがあったとする。

class Calc{
public:
    Calc(){};
    ~Calc(){};

    int shiftUpNBit(int x, int n){ return x << n; };
    int shiftDownNBit(int x, int n){ return x << n; };
    bool isEven(int x){ return x % 2 == 0; };
    //偶数だったらnビット左にシフト、奇数だったらnビット右にシフトする
    int calc(int x, int n){ return isEven(x) ? shiftUpNBit(x, n) : shigtDownNBit(x, n); };
};

calcを呼ぶと中でCalcクラスのshiftUpNBit, shiftDownNBit, isEvenが呼ばれる。全部pubicにしてるけど…

class MockCalc : public Calc{
public:
    MockCalc(){
        ON_CALL(*this, shiftUpNBit(_, _)).WillByDefault(Invoke(&calc_, &Calc::shiftUpNBit));
        ON_CALL(*this, shiftDownNBit(_, _)).WillByDefault(Invoke(&calc_, &Calc::shiftDownNBit));
        ON_CALL(*this, isEven(_)).WillByDefault(Invoke(&calc_, &Calc::isEven));
        ON_CALL(*this, calc(_, _)).WillByDefault(Invoke(&calc_, &Calc::calc));
    };
    virtual ~MockCalc(){};

public:
    MOCK_METHOD2(shiftUpNBit, int(int, int));
    MOCK_METHOD2(shitDownNBit, int(int, int));
    MOCK_METHOD1(isEven, bool(int));
    MOCK_METHOD1(calc, int(int, int));

private:
    Calc calc_;
};

ここまではその上とおんなじだが。

TEST(CalcTest, testCalcInvoke){
    //ヒープ領域に取らないとエラーが出るぽい
    MockCalc* mc = new MockCalc();
    {
        //順番のテストとメソッドの中でちゃんとメソッドが呼び出されているかテストする(EXPECT_CALLはまちがいでON_CALLが正しいです;#2011/2/17)
        InSequence s;
        EXPECT_CALL(*mc, calc(_, _));
        EXPECT_CALL(*mc, isEven(_)).WillOnce(Return(true));  //どんな場合もtrueがかえる
        EXPECT_CALL(*mc, shiftUpNBit(_, _));
    }
    EXPECT_EQ(4, mc->calc(1, 2));

    //モックオブジェクトは勝手に解放されないので、メモリーリークするかもしれないというWarningが出力される
    //表示させたくない場合はMock::AllowLeakを使う
    MOCK::AllowLeak(&mc);
    //モックオブジェクトを初期化する
    Mock::VerifyAndClear(&mc);
    //普通に解放してもよい。
    mc->~MockCalc();
  //delete mc; //こちらでもよい。
}

Windows7のネットワークが異様に遅い問題

こっちにも書いとこう。

      • -

家庭内の共有ファイルアクセスも遅くてこまるし頻繁にネットワークが切断される(というかタイムアウトしてる?)のでどうにかせにゃなーと検索してみた。64bit版でアプリケーション自体の速度は問題ない(ややメモリが使いすぎな感があるが)けどネットワークは本当に遅い。くだりが平均20k、早くても50k…Visual Studio試用版のネットワークインストールが半日たっても終わらない…

チェックサムは、ネットワークを介して転送されるデータの完全性をチェックするシンプルなエラー検出方式です。TCP/IP/UDPなどの通信プロトコルでは、この方式が実装され、受信データがネットワーク上で破損していないかどうかの確認に使用されます。IPv4データグラムの送信元は、データに基づいてチェックサムの値を計算し、それをフレームに埋め込みます。受信側もチェックサムを独自に計算し、その結果に基づいてデータの完全性を確認します。同様に、IPデータグラムのペイロードになるTCP/UDPデータについても、チェックサムが計算され、その値がTCP/UDPフレームに埋め込まれます。
一般に、Fast Ethernetシステムでは、この計算はCPU上で実行されるドライバ・ソフトウェアによって実行されます。しかし、Gigabit Ethernetや10Gbpsの通信速度によるデータ転送では、ソフトウェア・ドライバによるチェックサムの計算はCPUにとって大きな負担になります。このことがホストの最大の課題のひとつであり、アプリケーション処理にまわすCPUサイクルが減少する原因になっています。その結果、ネットワークのフル活用が不可能になり、パフォーマンスが低下してしまうのです。

ということなのだが10Gbpsもあるはずがない家のネットワークではチェックサムオフロード機能はいらんだろう…だいたいデュアルコアなんだからCPUもうちょっと働けむしろ(´∀` )オマエガナーと思ったのでとりあえずIPv6だけ切ってみた。

  1. [コントロールパネル]-[ネットワークと共有センター]-[アダプターの設定の変更]と進んで、ローカルエリア接続の右クリックメニューからプロパティを開く。
  2. ついでなのでインターネットプロトコルバージョン6のチェックをはずして(個人的にはこれはおまじない程度だと思っている)OKののちもう一度同じようにプロパティを開く。
  3. [構成]でネットワークアダプタのプロパティを開いて詳細設定タブをクリック
  4. TCP チェックサムオフロード(IPv6), UDPチェックサムオフロード(IPv6),一括送信オフロード v2(IPv6)をすべて無効にしてOK

ネットワークが一度切断されるけどすぐに再接続される。
転送速度をみると20k->1800k


ちょwwwwwおまwwwwwwwwwwwwwwwww


IPv4のほうもはずしてみたんだけどそしたら900kくらいになったのではずさなくてもいいのかも。XPでははずしてなかったし(というかデフォルトにしていた)Vistaでも問題がない(もちろんデフォルトまま)なので、そこら辺はネットワークカード上のチップとCPUのトレードオフになるあたりなのかも。

IPv6がどうも悪者扱いされているようだが、別に悪いわけじゃないんだけどなぁ。おまじないではなく正しい対処法が広まってほしい。
http://blog.penchi.jp/archives/558.html
http://www.akakagemaru.info/port/windows7ipv6.html

これはネットワークがブツブツ途切れる場合の対処法。ネットワークの切り替えが頻繁に行われるための切断もありうるので、特にブラウジングをしているわけでもないのにネットワークが切断と再接続を繰り返している場合は上記のリンク先の対処法をするのが正しい。

一方でページのロード含め、ネットワーク使用中に切断される場合は、ネットワークの太さに対してデータが大きすぎるために待ち時間が発生していると考えたほうがよい。この場合IPv6を切っても意味がない。なぜならそこが原因ではないからだ。特にブラウジングをしていないときにネットワークが切断される現象が起きないのなら、余計な計算や処理をしているために待ち時間が発生してタイムアウト→切断→再接続になっていると思われる。
のでここで書いてある方法か
http://netserc.blog63.fc2.com/blog-entry-231.html
ここら辺を参考にするのがよいかと。


この記事がもう少しいろんな人に読まれるといいんだがなぁ。

templateのパラメータ引数に制限を設ける

templateはどんな型でも取れちゃうけど取れると困る場合がある。C++0xだとコンセプトっていうのがあるらしいけどC++0xなにそれおいしいの?なので…

実行してからじゃないとわからないようなのだとバグを仕込んでいるのと変わりないので、できるだけコンパイルエラーで検出したいですね。

型はなんでもいいけどスカラかポインタかは区別したい

参照で入れる場合などは注意ということでしょうな。

int main(void){
    int var;
    int* pVar;
    test(&var);
    test(&pVar);
}

class A{
public:
    template<class T> void test(T* p);
}

確かにこれは区別がつかないけど困るな。というわけで制限をかける。
スカラ以外はだめだよという場合は、

template<class T> void test(T* p){//実装する};
template<class T> static inline void test(T**); //実装しない

これは別にメンバ関数でもOK。template static...と一行で書くと怒られるのかな…(うろ覚え(あとで直します

逆にすからはだめで配列だとOKという場合はどうすればいいのだろう?あんまないからいいのか…?

型を制限したいその1(特殊化)

テンプレートの特殊化については以下を参照のこと。
http://www.geocities.jp/ky_webid/cpp/language/037.html
特殊化自体は「この型だけは別の実装で!」とするための手法(型がそんなに多くないならインターフェイスつくって継承先で実装した方がよさげだ)なんだけど、これを使って特定の型だけに制限をかける。

template<class T, class U> struct check_type;
template<class T, class U> void test(T var1, U* var2);
//許可する型の組
template<> struct check_type<int, char*>{};
template<> struct check_type<unsigned int, char*>{};

template<class T, class U> void test(T var1, U* var2){
    //型チェック
    (void)sizeof(check_type<T, U>);
    //実装
};

よく見てないでtest関数を特殊化しようとした馬鹿は僕だけで十分です。
TとUの組で知らないのが出てくるとどのcheck_typeかわからなくてエラーが出るんだねー。これである特定のだけはじけるとかできればいいのになぁ。
これは割と楽でいいんだけど、もっとたくさんある場合は全部書くの大変だし、継承関係あるのなら一気に調べたいという場合はその2へ。

型を制限したいその2
//テンプレートのパラメータ引数に入るはずのクラス
class A{};
class B{};
class A1{};
class B1{};
class A2: public A
{};
class B2: public B
{};

/* C.h */
/* テンプレートメンバ関数を持ったクラス */
#define STATIC_ASSERT(p) typedef int static_assertion_faild[ p ? 1 : -1]

typedef char correct_type;
typedef struct { char dummy2[]; } incorrect_type; //分かりやすいコンパイルエラーを出力

//正しい
correct_type type_check(const volatile A*, const volatile B*);
correct_type type_check(const volatile A1*, const volatile B1*);
//正しいもの以外は全部はじく
incorrect_type type_check(...)

class C
{
...
public:
	template<T, U> void func(T p1, U p2);
}

/* C.cpp */
template<T, U>
void func(T p1, U p2){
	STATIC_ASSERT((sizeof(type_check((T*)0, (U*)0)) == sizeof(correct_type));
	//あとの処理
}

void callFunc(){
    A a;
    B b;
    A1 a1;
    B1 b1;
    A2 a2;
    B2 b2;
    Adummy ad;
    Bdummy bd;

    //コンパイルエラーでない
    func(a, b);
    func(a1, b1);
    func(a2, b2);

  //コンパイルエラー出る
    func(ad, bd);
}

これはかっこいい!
これでcorrect_type以外の組み合わせの場合は弾いてくれる。volatileが付いてると継承クラスまでは許可するのかな。厳密にこれだけとするならvolatileはつけないほうがヨサゲ
http://proger.blog10.fc2.com/blog-entry-20.html
volatileについてはこちら。最適化を抑制するから子クラスからは継承元の公開されてるものは全部見えるので、同じものだよとわかってくれるっつーことか。

特に制限を書いてないけど制限できちゃうよという例

テンプレートパラメータが二つあるとできるぽい。

/* CMyList.h */
template<class T>
class CMyList{
public:
    void copyNum(Func1 func_){ m_func = func_; };
private:
    Func1 m_func;
};

/* Func.h */
class Func1 : public Func
{
public:
    &Func1 operator=(Func1 var){ 
        m_num = var.m_num;
        return *this; 
    };
private:
    int m_num;
}

/* CTest.h */
template<class T, class U> void test(CMyList<T>* list_, U fct);

/* CTest.cpp */
template<class T, class U>
void test(CMyList<T>* list_, U fct){
    list_->copy(fct);
}

/* main */
int main(void){
    CMyList<MyFactor> list;
    Func1 func1;
    Func func
    test(&list, func1);    //OK
    //test(&list, func);   //Compiler error
}

間にテンプレートクラスがあるからかどうかはまた別問題として、テンプレートはビルド時だったかコンパイル時に展開するために、展開先が見つからないとエラーを出してくれる(今までのも全部それでエラーが出る)ので、このTとUの組み合わせが常に決まっているような場合はエラーが出力されます。この場合だとMyListのコピーメソッドのところでFunc1と決定しているのでそれ以外のが来ちゃうとエラーが出る。
こんなバカみたいな設計になんねーからとか思うあなたは正しいと思います。
こういうのはそうなったからラッキーなのであって、基本的には自分でアサーション出させるとかにした方がいいんだろうなぁ。うーむ。