お気に入りツール紹介:C++test (1)
前回の記事「動かしてからバグを取る?動かす前にバグを取る?」にて、C++testというツールを結構ヨイショしちゃいました。
せっかくなので、さらにヨイショしておこうかと思います。(ヨイショしても、1円の得にもなりませんけどね!)
C++testというのは、CppUnitのようなテスト自動化ツールです。CppUnitはフリーのツールですから、どうしても有償ツールと比べると見劣りがします。
私も数年前まではCppUnitを使用していたのですが、その時の不満点をC++testがことごとく解決していて、ちょっと驚きました。そこで、何に不満をいだいていて、それをどう解決したのか?という観点で書いてみます。
CppUnitの代替ツール選定作業
2008年の春頃、CppUnitが不便なのと、さらにソフトウェア品質を上げたいという思いから、代替ツールの選定作業を行いました。
選定作業には、以下の評価ポイントを使用しました。優先順位の順番にならべてあります。
- (MUST)対象言語はC++とする
- (MUST)CppUnitより簡単にユニットテストが書ける (※詳細は後述します)
- (MUST)コードカバレッジが測定できる
- (WANT)ツール価格は100万円以下とする
- (WANT)コードの静的解析が実行できる
Webをさまよい、評価版があれば評価を行い・・・いろいろな調査を行ったのですが、そもそも1~3のMUST条件を満たす物すらほとんど見つかりませんでした。
結局、1~5を満たしてくれるツールが、ただ1つだけ見つかりました。それこそが、C++testだったのです。(もう2年近く前の話ですから、今もう1回調査するとちがった結果になるかも分かりません。でも、もうC++test買っちゃいましたから!)
CppUnitにおける悩み
CppUnitを使ってテストを行う際、困っていた事があります。それは、複雑に入り組んだクラス間の依存関係です。
CppUnitは、本来テストしたいプロジェクトとは別プロジェクトを作製し、そこにテスト対象となるクラスを放り込んで使用します。
CppUnit専用のプロジェクトを用意する
そして、CppUnitを実行すると、有名なGUIが表示されるわけです。(CppUnitの実行イメージ参照。)
さて、上図はとても単純な例に見えます。しかし、CppUnitプロジェクトをコンパイルする事はできません。なぜなら、クラスAがクラスBに依存しているからです。きちんとコンパイルするには、CppUnitプロジェクトにもクラスBを持ってこないといけません。
さて、もしもクラスBが、さらにクラスC、クラスD・・・クラスZにも依存していたら?その場合には、とにかくぜんぶ、CppUnitプロジェクトに登録しておかないといけません。単にクラスを1つテストしたいだけなのに!
このように、開発対象のプロジェクトとCppUnitのプロジェクト、両方のメンテナンスをしないといけないのがすごく面倒です。クラスだけでなく、コンパイルオプションから依存ライブラリから、とにかくすべての設定をメンテナンスしないといけません。開発対象のプロジェクトはコンパイルできるのに、CppUnitプロジェクトはコンパイルできない事がしょっちゅうありました。これは面倒すぎる・・・というわけで、いつしかCppUnitを敬遠するようになっていました。
C++testによってお悩み解決!
C++testにおけるユニットテスト作製は、とても簡単です。開発対象のプロジェクトに、ユニットテストのコードを登録します。すると、そこから自動的にユニットテスト実行用のEXEが生成されます。ですから、クラス間の依存関係を心配する必要は一切ありません。
ユニットテスト実行用EXEは自動生成される
とにかく、「プロジェクトに登録されているすべてのクラス」+「ユニットテストコード」を、C++testが全てひっくるめてコンパイルしてくれます。
ここで、「えっ?テストコードが本番コードにまぎれこむのかい?」と思われた方もいるでしょう。ご心配なく!ユニットテスト用コードは、あくまでファイルとして登録されているだけ。単体テスト実行時にはコードとしてコンパイルされますが、開発対象のプロジェクトビルド時には、コンパイル対象になっていません。Visual Studioの画面では、こうなっています。
ユニットテストコードは、コンパイル対象外
ハイライトされている「HandTestSuite_Function_Main.cpp」というのが、ユニットテスト用のコードです。この子に、道路標識でいう進入禁止のマークがついていますよね?これは、このファイルはコンパイル対象じゃないよ、という意味です。
C++testのユニットテストEXE生成機能は、とても強力です。たとえば、開発対象のプロジェクトが、DLLプロジェクトだったり、OCXプロジェクト(ActiveX)だったりしてもOKです。MFCアプリだって問題なくユニットテストできちゃいます。
特にこのMFCアプリですら特別な工夫なくユニットテストできてしまう、というのはとてもすごいことです。(いまどきMFC使ってる人なんて、めったに居ないでしょうけどね)
そんなわけで、C++testを導入してからはユニットテスト作りがとても楽になりました。とにかく開発対象のプロジェクトを作っていって、そこにC++test用のユニットテストクラスを追加していくだけでOKです。もう、CppUnitプロジェクトをなんとかコンパイル出来るようにしようと、四苦八苦する必要は無くなりました。これだけでも、C++testを購入したかいがあるというものです。
いろいろ書きましたが要するに、C++testを使えば、クラス間の依存関係とか一切心配しなくていいよ、という話です。
C++testにおけるテストケース作成手順
では、C++testにおけるテストケース作成手順をお見せしましょう。
CppUnitの場合は、テストスイート用のクラスを作って、そこに「1つのテストケース」=「1つのメソッド」という形で実装していきました。C++testでも、基本的な構造は同じです。
C++testを使っていて嬉しいのは、いわゆるお決まりの作業が自動化されている、という点です。(テストスイートのためのクラスを用意して、メソッドを1つ1つ用意して・・・)
まずテストスイートクラスを作るには、右クリックメニューから「新規テストスイートを作製」を選択します。
新規テストスイートを作製
そして、テストスイート名と、テスト対象ファイルの指定を行います。
テストスイート設定
テスト対象ファイルの指定
これだけで、テストスイートのスケルトンクラスが自動的に生成され、Visual Studioに登録されます。
生成されたテストスイートクラス
あとは1つ1つのテストケース(=1つ1つのメソッド)を追加していくのですが、これもC++testのウィザードがやってくれます。ですから、ユーザーとしてはテストケースの中身を書くことだけに集中できます。簡単ですね!
C++testにおけるテストケース実行手順
作製したテストケースを実行するには、テストコードを右クリックして「Run Unit Tests」をクリックするだけです。
さきほど、ユニットテスト実行用EXEが生成されるとか書きましたが、それはあくまで内部の話。ユーザーは、「Run Unit Tests」をクリックすると自動的にテスト実行される、という事だけ知っていればOKです。
ユニットテストを実行
すると、実行状況が表示されます。
実行状況
CppUnitでは、エラーがあるとプログレスバーが赤くなったりしました。しかし、C++testではそうはなりません。単純にエラーがいくつあったか?を報告してくれるだけです。
その代わり、どんなエラーが起きたのか?をVisual Studio内の画面でツリー表示してくれます。
C++testのレポート画面
これで、プロジェクト全体で何がエラーになっているのかが把握できます。
C++testではPrivateメソッドも呼び出せる
意外とうれしいこの機能。C++testでは、テスト対象クラスのProtectedメンバー、Priivateメンバーにアクセスできます。
ユニットテストからPrivateメソッドにアクセスするのってどうなの?という議論がある事は知っています。しかしそれは、うまく運用でカバーしてやることでデメリットをなくしてメリットだけ享受できるようになります。詳細は、次回か次々回の記事でご紹介します。
C++testではスタブを挿入できる
ユニットテストを作る時には、スタブが重要になります。たとえば、ホンモノのデータベースにアクセスする代わりに、いっつも同じ応答しか返さないニセのデータベースにアクセスする事で、自動テストを実行しやすく出来ます。
ツールの助けなしにやるのであれば、スタブの切り替えはユーザーが手でやらないといけません。(Javaであれば、DIコンテナ使うっていう手もあるんですけどね)
しかし、C++testでは、任意のグローバル関数、任意のメンバー関数を、ユーザーが用意したスタブに置き換えることができます。きわめつけは、new オペレータですらスタブ化できます。
newをスタブで置き換える意味は、メモリ不足時の動作検証です。new MyClass() として、これが失敗したときにどうなるかテストしたいとしましょう。CppUnitでこれをやるのはすごく難しい。なぜなら、MyClass()というのはサイズが決まっていて、たいがいはとても小さなもの。これを意図的にメモリ割り当て失敗させるだなんてまずムリです。
しかし、C++testでもってスタブ化すれば、newを失敗させる事なんて簡単にできます。しかも、スタブを挿入するために、プロジェクトのソースコードを修正する必要は一切ありません。ただ、こういうスタブで置き換えて欲しいなー、というファイルを1つ登録してやるだけで、C++testが自動的にスタブを挿入してくれます。
スタブの登録
スタブの記述
C++testでは、テストパターンの自動生成もできる
前回の記事でご紹介した、次のコードにたいしてテストパターンの自動生成をしてみましょう。
int iabs( int i ) { int result = 0; if( i < 0 ) { result = -i; } else { result = i; } return( result ); }
自動生成したいソースコードを右クリックして、Generate Unit Tests とするだけです。
テストパターンの自動生成
すると、5つのテストパターンが自動生成されます。
- i = 0
- i = -1
- i = 1
- i = cpptestLimitsGetMaxInt(); 要するにintの最大値
- i = cpptestLimitsGetMinInt(); 要するにintの最小値
これらが、1つのユニットテストクラスにまとめられ、自動登録されます。
自動生成されたテストケース
この自動生成機能は、ある程度までC++コードを解析し、境界値を分析したうえでパターン生成してくれます。ものすごく単純な関数であれば、自動生成でも十分です。
しかし私は、C++testがどうこういう話ではなく、そもそも自動テストパターン生成自体に懐疑的です。決して役に立たないとは言いませんが、効果はとても限定的です。(理由については、別の機会に述べます。)
とにかく、自動生成テストパターンさえあればオールオッケー、なんていうのは幻想ですから、あまり期待しない方がいいと思います。特に、人手でロクにテストもしないで、自動生成テストパターン走らせただけで「ほら!こんなにカバレッジ高い!がんばったでしょ!」とか言うのはもう論外です。あくまで、さんざん人手でテストしたあとで、最後のダメ押しとして自動テストパターン生成を行う、というくらいのスタンスが良いと思います。
次回は
今回は、C++testをつかって「いかにユニットテストを作製するか?」という事を中心に書きました。
次回は、C++testをつかって「いかにユニットテストを実行するか?」という事を中心に書いてみます。