GoogleのC++用のテストフレームワーク。これを使うと何が嬉しいのか
・出力がきれい
・デフォルトでいろんなコマンドラインをつけてくれる
・コマンドラインでテストの制御ができる
・パラレルで実行できる(多分)
・繰り返し実行して簡易計測ができる
・デステストができる
しかしGUIアプリの場合はテストは難しいのでここではスルー。テストはあくまで機能として分離がし易いものをテストする段階。付属のGoogleMockなどをつかって、Mockオブジェクトを作り分離させるために頑張る方法もあるのだろうがここではスルー。
実験
ここでは既存の自作テストプロジェクトをGoogleTestに置き換えていくことを想定。別に新規に追加しても良い。Windows, VS2013, C++/CLIプロジェクト
git submoduleで追加
ソリューションのフォルダで以下を実行して、googletestを持ってくる。べつにダウンロードしてもいい。
1 |
git submodule add https://github.com/google/googletest.git |
テストプロジェクトの設定
テストプロジェクトにgoogletestのソースコードを追加する。テストプロジェクトがなければコンソールアプリで作る。
まず、googletest\googletest\src\gtest-all.ccをプロジェクトのソースコードに追加。このファイルは必要なソースをincludeしてこのファイルだけで全部ビルドできる。
次にプロジェクトのインクルードパスに以下の2つを追加
これで準備が整ったのでmain.cpp(自分のファイル)にテストコードを書く。
1 2 3 4 5 6 7 |
#include "gtest/gtest.h" .... int main() { testing::InitGoogleTest(&__argc, __argv); RUN_ALL_TESTS(); } |
InitGoogleTestは最初に書いたようにコマンドラインを処理してくれる。RUN_ALL_TESTSはこちらが定義したテストを実行する。今はまだ何も定義してない。
テストの定義
以下のように書く。
1 2 3 4 5 |
#include "gtest/gtest.h" .... TEST(FileOperationTest, MoveFile) { ... } |
TESTマクロは2つの引数を取る。最初の引数はテストケースと呼ばれていて、テストを大きくカテゴライズしたもの、次の引数はそのカテゴリー内のテスト。1つのクラスをテストケースと考えてその中のメンバ関数をテスト名と考えてもいいかもしれない。
テストコード内でマクロを書く
テストコード内でテスト用のマクロを書く。これに引っかかるとテストに失敗するが、いろんな失敗のさせ方があるらしい。EXPECTは失敗しても続行の失敗(のはず)。ASSERTは失敗すると止める。
1 2 3 |
TEST(FileOperationTest, MoveFile) { EXPECT_EQ(1,1); } |
コマンドラインのチェック
ここで一旦コーディングをやめて、コマンドラインを試してみる。-hでヘルプを見れる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Running main() from gtest_main.cc This program contains tests written using Google Test. You can use the following command line flags to control its behavior: Test Selection: --gtest_list_tests List the names of all tests instead of running them. The name of TEST(Foo, Bar) is "Foo.Bar". --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS] Run only the tests whose name matches one of the positive patterns but none of the negative patterns. '?' matches any single character; '*' matches any substring; ':' separates two patterns. --gtest_also_run_disabled_tests Run all disabled tests too. Test Execution: --gtest_repeat=[COUNT] Run the tests repeatedly; use a negative count to repeat forever. --gtest_shuffle Randomize tests' orders on every iteration. --gtest_random_seed=[NUMBER] Random number seed to use for shuffling test orders (between 1 and 99999, or 0 to use a seed based on the current time). Test Output: --gtest_color=(yes|no|auto) Enable/disable colored output. The default is auto. --gtest_print_time=0 Don't print the elapsed time of each test. --gtest_output=xml[:DIRECTORY_PATH\|:FILE_PATH] Generate an XML report in the given directory or with the given file name. FILE_PATH defaults to test_detail.xml. Assertion Behavior: --gtest_break_on_failure Turn assertion failures into debugger break-points. --gtest_throw_on_failure Turn assertion failures into C++ exceptions. --gtest_catch_exceptions=0 Do not report exceptions as test failures. Instead, allow them to crash the program or throw a pop-up (on Windows). Except for --gtest_list_tests, you can alternatively set the corresponding environment variable of a flag (all letters in upper-case). For example, to disable colored text output, you can either specify --gtest_color=no or set the GTEST_COLOR environment variable to no. For more information, please read the Google Test documentation at https://github.com/google/googletest/. If you find a bug in Google Test (not one in your own code or tests), please report it to <googletestframework@googlegroups.com>. |
–gtest_list_tests
1 2 |
FileOperationTest. MoveFileW |
オプション無しでテスト実行
1 2 3 4 5 6 7 8 9 10 |
[==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from FileOperationTest [ RUN ] FileOperationTest.MoveFileW [ OK ] FileOperationTest.MoveFileW (203 ms) [----------] 1 test from FileOperationTest (218 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (234 ms total) [ PASSED ] 1 test. |
GoogleTestをプロジェクトに追加しておくと、自分がテストするだけでなく、他人がプロジェクトの内容を知ることもできるし、実行ファイルを生成するので、どのように動くのかもわかって便利。
このようにテストをどんどん追加していくと、持つべきデータや初期化作業が共通化してくるので、これをFixtureと読んでおく。このFixture付きのテストがTEST_F。
TEST_F
以下の普通のTESTがあるとする。
1 2 3 4 5 6 7 8 |
TEST(FileOperationTest, MoveFile) { } TEST(FileOperationTest, CopyFile) { EXPECT_TRUE(true); } TEST(FileOperationTest, DeleteFile) { EXPECT_TRUE(true); } |
これをTEST_Fに置き換えたのが以下。
1 2 3 4 5 6 7 8 9 10 |
class FileOperationTest : public ::testing::Test {}; TEST_F(FileOperationTest, MoveFile) { } TEST_F(FileOperationTest, CopyFile) { EXPECT_TRUE(true); } TEST_F(FileOperationTest, DeleteFile) { EXPECT_TRUE(true); } |
TEST_Fの中でclassのインスタンスにアクセスできる。このクラスの初期化と終了処理はSetUpとTearDownのオーバーライドで行う。ちなみにこのクラスはテストごとに作られて、インスタンスは共通ではない。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class FileOperationTest : public ::testing::Test { protected: wstring mystring; virtual void SetUp() { mystring = L"aaa"; } virtual void TearDown() { } }; |
フィルター
-gtest_filterでテストをフィルタできる。テストケースとテスト名は’.’ピリオドで区切る。ワイルドカードも使える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
> lsMiscTest.exe --gtest_list_tests ChangeFilenamable. Basic OpParser. Null ParenOnly BasicSingle BasicAnd BasicParenAnd BasicOr BasicParenOr BasicMultiParen ComplexNullParen ComplexSingleWord ComplexParen ComplexMultiParen ImplicitAndError UnmatchedParenthesisError Pod BasicWithArg BasicWithMultiArg GetProcessList. Basic UTF8TOUTF16. convert8to16 CommandLineParser. IterateOur Basic OptionConstructorAll Bool Int CommandLineString. SameWithOtherMethods subStringBasic subStringComplex FormatSizeof. Basic GetVersionString. Explorer ThisExe NonExistentFile Hira2Kata. Basic MoveWindowCommon. Basic OpenCommon. ThisFolder I18N. BASIC PathUtil. IsRoot IsChild RevealFolder. Basic FileOperationTest. MoveFileW CopyFileW DeleteFileW ShowBalloon. Show UrlEncode. BasicWithChar BasicWithWchar Complex > > > > lsMiscTest.exe --gtest_filter=OpParser.* Note: Google Test filter = OpParser.* [==========] Running 17 tests from 1 test case. [----------] Global test environment set-up. [----------] 17 tests from OpParser [ RUN ] OpParser.Null [ OK ] OpParser.Null (0 ms) [ RUN ] OpParser.ParenOnly [ OK ] OpParser.ParenOnly (0 ms) [ RUN ] OpParser.BasicSingle [ OK ] OpParser.BasicSingle (0 ms) [ RUN ] OpParser.BasicAnd [ OK ] OpParser.BasicAnd (0 ms) [ RUN ] OpParser.BasicParenAnd [ OK ] OpParser.BasicParenAnd (0 ms) [ RUN ] OpParser.BasicOr [ OK ] OpParser.BasicOr (0 ms) [ RUN ] OpParser.BasicParenOr [ OK ] OpParser.BasicParenOr (0 ms) [ RUN ] OpParser.BasicMultiParen [ OK ] OpParser.BasicMultiParen (1 ms) [ RUN ] OpParser.ComplexNullParen [ OK ] OpParser.ComplexNullParen (0 ms) [ RUN ] OpParser.ComplexSingleWord [ OK ] OpParser.ComplexSingleWord (0 ms) [ RUN ] OpParser.ComplexParen [ OK ] OpParser.ComplexParen (0 ms) [ RUN ] OpParser.ComplexMultiParen [ OK ] OpParser.ComplexMultiParen (0 ms) [ RUN ] OpParser.ImplicitAndError [ OK ] OpParser.ImplicitAndError (0 ms) [ RUN ] OpParser.UnmatchedParenthesisError [ OK ] OpParser.UnmatchedParenthesisError (0 ms) [ RUN ] OpParser.Pod [ OK ] OpParser.Pod (1 ms) [ RUN ] OpParser.BasicWithArg [ OK ] OpParser.BasicWithArg (0 ms) [ RUN ] OpParser.BasicWithMultiArg [ OK ] OpParser.BasicWithMultiArg (0 ms) [----------] 17 tests from OpParser (2 ms total) [----------] Global test environment tear-down [==========] 17 tests from 1 test case ran. (2 ms total) [ PASSED ] 17 tests. |
powershellとリピートでトータル時間を計算する
フィルターでテストを指定し、リピートで繰り返し実行し、powershellのMeasure-Commandで時間を計算すれば、指定されたテストのみの実行時間を計算できるので、コードを変えたときの変化を見ることができる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
> powershell Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved. > Measure-Command { .\lsMiscTest.exe --gtest_filter=OpParser.BasicWithMultiArg --gtest_repeat=1000 } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 215 Ticks : 2151433 TotalDays : 2.49008449074074E-06 TotalHours : 5.97620277777778E-05 TotalMinutes : 0.00358572166666667 TotalSeconds : 0.2151433 TotalMilliseconds : 215.1433 > |
パラレルで実行する
gtest-parallel(python2スクリプト)でパラレル実行ができるみたいなので試してみる。git submoduleや単にダウンロードしてインストールされたものとする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
> python.exe ..\..\..\gtest-parallel\gtest_parallel.py --help Usage: gtest_parallel.py [options] binary [binary ...] -- [additional args] Options: -h, --help show this help message and exit -d OUTPUT_DIR, --output_dir=OUTPUT_DIR Output directory for test logs. Logs will be available under gtest-parallel-logs/, so --output_dir=/tmp will results in all logs being available under /tmp/gtest- parallel-logs/. -r REPEAT, --repeat=REPEAT Number of times to execute all the tests. --retry_failed=RETRY_FAILED Number of times to repeat failed tests. --failed run only failed and new tests -w WORKERS, --workers=WORKERS number of workers to spawn --gtest_color=GTEST_COLOR color output --gtest_filter=GTEST_FILTER test filter --gtest_also_run_disabled_tests run disabled tests too --print_test_times list the run time of each test at the end of execution --shard_count=SHARD_COUNT total number of shards (for sharding test execution between multiple machines) --shard_index=SHARD_INDEX zero-indexed number identifying this shard (for sharding test execution between multiple machines) --dump_json_test_results=DUMP_JSON_TEST_RESULTS Saves the results of the tests as a JSON machine- readable file. The format of the file is specified at https://www.chromium.org/developers/the-json-test- results-format --timeout=TIMEOUT Interrupt all remaining processes after the given time (in seconds). --serialize_test_cases Do not run tests from the same test case in parallel. > > > > python.exe ..\..\..\gtest-parallel\gtest_parallel.py -w 8 lsMiscTest.exe [0/46] Running tests... [1/46] OpParser.BasicParenOr (99 ms) [2/46] OpParser.ImplicitAndError (90 ms) [3/46] PathUtil.IsChild (120 ms) [4/46] CommandLineString.subStringBasic (200 ms) [5/46] CommandLineParser.Int (124 ms) [6/46] OpParser.UnmatchedParenthesisError (86 ms) [7/46] OpParser.ComplexParen (134 ms) [8/46] OpenCommon.ThisFolder (533 ms) [9/46] CommandLineParser.OptionConstructorAll (164 ms) [10/46] OpParser.BasicMultiParen (162 ms) [11/46] FileOperationTest.DeleteFileW (141 ms) [12/46] OpParser.BasicWithArg (167 ms) [13/46] OpParser.ParenOnly (117 ms) [14/46] OpParser.ComplexMultiParen (275 ms) [15/46] I18N.BASIC (98 ms) [16/46] GetVersionString.ThisExe (245 ms) [17/46] OpParser.BasicWithMultiArg (234 ms) [18/46] OpParser.Pod (324 ms) [19/46] CommandLineString.subStringComplex (179 ms) [20/46] FileOperationTest.MoveFileW (1490 ms) [21/46] PathUtil.IsRoot (128 ms) [22/46] GetVersionString.NonExistentFile (504 ms) [23/46] CommandLineString.SameWithOtherMethods (230 ms) [24/46] UrlEncode.Complex (343 ms) [25/46] OpParser.Null (450 ms) [26/46] FileOperationTest.CopyFileW (433 ms) [27/46] GetProcessList.Basic (526 ms) [28/46] RevealFolder.Basic (722 ms) [29/46] MoveWindowCommon.Basic (2478 ms) [30/46] UrlEncode.BasicWithWchar (381 ms) [31/46] CommandLineParser.IterateOur (326 ms) [32/46] CommandLineParser.Bool (362 ms) [33/46] OpParser.ComplexSingleWord (235 ms) [34/46] OpParser.BasicSingle (311 ms) [35/46] OpParser.BasicAnd (316 ms) [36/46] GetVersionString.Explorer (604 ms) [37/46] OpParser.BasicParenAnd (103 ms) [38/46] OpParser.BasicOr (108 ms) [39/46] Hira2Kata.Basic (166 ms) [40/46] ShowBalloon.Show (3332 ms) [41/46] UTF8TOUTF16.convert8to16 (255 ms) [42/46] FormatSizeof.Basic (424 ms) [43/46] OpParser.ComplexNullParen (268 ms) [44/46] UrlEncode.BasicWithChar (179 ms) [45/46] ChangeFilenamable.Basic (171 ms) [46/46] CommandLineParser.Basic (475 ms) |
デステスト
デステストとはアプリがちゃんと終了するか(クラッシュするか)をテストするツールである。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "stdafx.h" #include <gtest/gtest.h> using namespace std; void MyExit(bool death,int exitCode) { if (death) { cerr << "this is my death test" << endl; exit(exitCode); } cerr << "I'm Alive!!" << endl; } bool MyExited(int exitCode, int exitedCode) { return exitCode == exitedCode; } TEST(GTestTest, DeathTest) { int exitCode = 0; EXPECT_EXIT(MyExit(true, 1), ::testing::ExitedWithCode(1), "this is my death test"); EXPECT_EXIT(MyExit(true, 54321), std::bind(MyExited, 54321, std::placeholders::_1), "this is my death test"); EXPECT_DEATH(MyExit(true, 1), "this is my death test"); // failed test EXPECT_EXIT(MyExit(false, 1), ::testing::ExitedWithCode(1), "this is my death test"); EXPECT_EXIT(MyExit(true, 12345), std::bind(MyExited, 54321, std::placeholders::_1), "this is my death test"); EXPECT_DEATH(MyExit(true, 1), "this is NOT my death test"); } |
こられのマクロのMyExitはこのプロセスでは実行されない。同じ実行ファイルを内部引数で起動し(InitGoogleTestで処理される)そのプロセスで実行される。実行された結果の出力と最後に指定された正規表現を比較してテストし、EXPECT_EXITの場合は二番目にに指定された述語(関数オブジェクト)をExitCodeを引数に呼ぶ。これがfalseを返したり、MyExitが実際に終了しなかったりするとテストはエラーを報告する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
> lsMiscTest.exe --gtest_filter=GTestTest.DeathTest Note: Google Test filter = GTestTest.DeathTest [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from GTestTest [ RUN ] GTestTest.DeathTest gtestgtest.cpp(27): error: Death test: MyExit(false, 1) Result: failed to die. Error msg: [ DEATH ] I'm Alive!! [ DEATH ] gtestgtest.cpp(28): error: Death test: MyExit(true, 12345) Result: died but not with expected exit code: Exited with exit status 12345 Actual msg: [ DEATH ] this is my death test [ DEATH ] gtestgtest.cpp(29): error: Death test: MyExit(true, 1) Result: died but not with expected error. Expected: this is NOT my death test Actual msg: [ DEATH ] this is my death test [ DEATH ] [ FAILED ] GTestTest.DeathTest (630 ms) [----------] 1 test from GTestTest (630 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (630 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] GTestTest.DeathTest 1 FAILED TEST |