Google Testを使う

GoogleのC++用のテストフレームワーク。これを使うと何が嬉しいのか
・出力がきれい
・デフォルトでいろんなコマンドラインをつけてくれる
・コマンドラインでテストの制御ができる
・パラレルで実行できる(多分)
・繰り返し実行して簡易計測ができる
・デステストができる

しかしGUIアプリの場合はテストは難しいのでここではスルー。テストはあくまで機能として分離がし易いものをテストする段階。付属のGoogleMockなどをつかって、Mockオブジェクトを作り分離させるために頑張る方法もあるのだろうがここではスルー。

実験

ここでは既存の自作テストプロジェクトをGoogleTestに置き換えていくことを想定。別に新規に追加しても良い。Windows, VS2013, C++/CLIプロジェクト

git submoduleで追加

ソリューションのフォルダで以下を実行して、googletestを持ってくる。べつにダウンロードしてもいい。

テストプロジェクトの設定

テストプロジェクトにgoogletestのソースコードを追加する。テストプロジェクトがなければコンソールアプリで作る。

まず、googletest\googletest\src\gtest-all.ccをプロジェクトのソースコードに追加。このファイルは必要なソースをincludeしてこのファイルだけで全部ビルドできる。

次にプロジェクトのインクルードパスに以下の2つを追加

これで準備が整ったのでmain.cpp(自分のファイル)にテストコードを書く。

InitGoogleTestは最初に書いたようにコマンドラインを処理してくれる。RUN_ALL_TESTSはこちらが定義したテストを実行する。今はまだ何も定義してない。

テストの定義

以下のように書く。

TESTマクロは2つの引数を取る。最初の引数はテストケースと呼ばれていて、テストを大きくカテゴライズしたもの、次の引数はそのカテゴリー内のテスト。1つのクラスをテストケースと考えてその中のメンバ関数をテスト名と考えてもいいかもしれない。

テストコード内でマクロを書く

テストコード内でテスト用のマクロを書く。これに引っかかるとテストに失敗するが、いろんな失敗のさせ方があるらしい。EXPECTは失敗しても続行の失敗(のはず)。ASSERTは失敗すると止める。

コマンドラインのチェック

ここで一旦コーディングをやめて、コマンドラインを試してみる。-hでヘルプを見れる。

–gtest_list_tests

オプション無しでテスト実行

GoogleTestをプロジェクトに追加しておくと、自分がテストするだけでなく、他人がプロジェクトの内容を知ることもできるし、実行ファイルを生成するので、どのように動くのかもわかって便利。

このようにテストをどんどん追加していくと、持つべきデータや初期化作業が共通化してくるので、これをFixtureと読んでおく。このFixture付きのテストがTEST_F。

TEST_F

以下の普通のTESTがあるとする。

これをTEST_Fに置き換えたのが以下。

TEST_Fの中でclassのインスタンスにアクセスできる。このクラスの初期化と終了処理はSetUpとTearDownのオーバーライドで行う。ちなみにこのクラスはテストごとに作られて、インスタンスは共通ではない。

フィルター

-gtest_filterでテストをフィルタできる。テストケースとテスト名は’.’ピリオドで区切る。ワイルドカードも使える。

powershellとリピートでトータル時間を計算する

フィルターでテストを指定し、リピートで繰り返し実行し、powershellのMeasure-Commandで時間を計算すれば、指定されたテストのみの実行時間を計算できるので、コードを変えたときの変化を見ることができる

パラレルで実行する

gtest-parallel(python2スクリプト)でパラレル実行ができるみたいなので試してみる。git submoduleや単にダウンロードしてインストールされたものとする。

デステスト

デステストとはアプリがちゃんと終了するか(クラッシュするか)をテストするツールである。

こられのマクロのMyExitはこのプロセスでは実行されない。同じ実行ファイルを内部引数で起動し(InitGoogleTestで処理される)そのプロセスで実行される。実行された結果の出力と最後に指定された正規表現を比較してテストし、EXPECT_EXITの場合は二番目にに指定された述語(関数オブジェクト)をExitCodeを引数に呼ぶ。これがfalseを返したり、MyExitが実際に終了しなかったりするとテストはエラーを報告する。

gitのsubmoduleでの変更を無視する

gitにsubmoduleを追加してそのサブモジュール内でファイルの変更や追加をすると親でstatusした時になんか出る。(下のfile)

これはgitlinkといって特別な扱いらしい。.gitignoreに追加しても無視できなかった。これを無視するには親ディレクトリの.gitmodulesを開いてignore = dirtyを追加する。

参照:
https://stackoverflow.com/questions/7912022/do-you-ignore-a-git-submodule-in-your-gitignore-or-commit-it-to-your-repo

https://stackoverflow.com/questions/3240881/git-can-i-suppress-listing-of-modified-content-dirty-submodule-entries-in-sta

AnyCPUのアセンブリから自動でx86かx64のアセンブリをロードする

普通にC#でアプリケーションを作るとAnyCPUのアセンブリができる。これは64ビット環境では64ビットで動作し、32ビットの場合は32ビットで動作する。

C++やC++/CLIで作ったライブラリはAnyCPUにすることができない。そしてWindowsでは64ビットアプリは64ビットのライブラリしかロードできない。32ビットも同じ。

AnyCPUのアプリをつくるときにこの問題をどう解決するかのはなし。ここではC++/CLIでライブラリをつくり、それをC#から利用することを考える。

C++/CLIで32ビットと64ビットのライブラリをつくる

作り方は省略。MyLib.x86.dllとMyLib.x64.dllができたとする。

C#でMyLib.x86.dllを参照する

参照するときに、プロジェクトの参照をするのではなく、ファイルを直接参照する。参照のプロパティのCopy LocalをFalseにする。こうすることでビルド時にライブラリがコピーされない。

C#のビルドイベントでMyLib.*をターゲットディレクトリにコピーする

この時のコピー先のパスはランタイムが自動で見つけられない場所にする。自動で見つけると違うdllをロードして例外が発生する。

以下はビルド後のコマンドの例、アプリのフォルダ配下のplatformにコピーしている。

この状態でアプリを起動してもライブラリが見つからない、のエラーが出る。

staticコンストラクタでカスタムローダを設定する

staticコンストラクタはstatic main()よりも早く動く。ここでライブラリが見つからないときのイベントハンドラを設定し、ハンドラで適切なライブラリを読み込む。以下はC#でデフォルトでできるクラスProgramに記述する例。

もともとx86の方を参照しているので、ランタイムはこのファイルがないと通知してくる。ここで環境(Environment.Is64BitProcess)に合わせてライブラリを読み込む。

参照

Using Side-by-Side assemblies to load the x64 or x32 version of a DLL

Msys2 and ming-w64

msys2とming-w64が分かりづらいので少し調べた。

MSYS2

msys2はCygwinをベースにしていてmsys-2.0.dllがパスの切り替えやforkなど(多分)を行う。cygwinにはコマンドラインパッケージマネージャがないので、pacmanを有するmsys2の方がインストールがしやすい。

msys-2.0.dllに依存しない通常のexeをmsys2から起動するとパスの変換が行われる(コマンドラインの)。cygwinではこういうことは行われないようだ。

MINGW

Mingw-w64で一つの名前。Mingwの64ビットバージョンの意味ではない。これらはmsys2.0.dllに依存していないツールでWindows向けにビルドされたもの。
msys2でgccをインストールしようとすると以下の2つがある。

mingw-w64-i686-gcc
mingw-w64-x86_64-gcc

mingw-w64で一つの名前なのでi686が32bitでx86_64が64bitのgcc、これらをインストールするとそれぞれ/mingw32/binと/mingw64/binに置かれる。msys2をインストールすると、mingw32.exeとmingw64.exeができるが、この違いはパスの違いである。

これとは別のgccもあってそれはmsys2で動く実行ファイルを作るためのgcc。これはmsys2-develに含まれる。msys2.exeを起動すると上記パスに/mingw32などが含まれない。