June 2011

Volume 26 Number 06

アジャイル C++ - Visual Studio と TFS でアジャイル C++ 開発とテストを行う

John Socha-Leialoha | June 2011

コード サンプルのダウンロード

開発者またはテスターとして、Visual C++ でアプリケーションをビルドする作業に携わっているとします。開発者として参加していれば、生産性を高め、質の高いコードを作成し、機能しなくなることをを恐れずに必要に応じてコードを書き直し、アーキテクチャを強化できればすばらしいと考えるでしょう。テスターとして参加していれば、テストの作成と保守にかける時間を短縮し、別のテスト作業に時間を割けるようになればすばらしいと考えるでしょう。

今回の記事では、マイクロソフト所属のチームがアプリケーション開発に使用している数多くの手法を紹介します。

チームの規模はかなり小さく、10 人のメンバーが、3 つのプロジェクトに同時にかかわっており、これらのプロジェクトは、C# と C++ の両方で記述しています。C++ のコードは、ほとんどの場合、Windows PE 内で実行する必要があるプログラムに使用しています。Windows PE とは、必要最低限の機能のみを備えたバージョンの Windows で、OS のインストールによく使用します。また、バーチャル ハード ディスク (VHD) ファイルにハードディスクをキャプチャするといった、完全な OS 内では実行できないタスクを行うために、Microsoft System Center Configuration Manager のタスク シーケンスの一部としても使用します。これは、小規模チームにとっては大きすぎるタスクなので、生産性を高めることが求められます。

このチームでは Visual Studio 2010 と Team Foundation Server (TFS) 2010 を使用しています。TFS 2010 は、バージョン管理、作業時間の管理、継続的インテグレーション、コード カバレッジの収集とレポートに使用しています。

テストを作成するタイミングと理由

まず、チームがテストを作成する理由について考えてみましょう (おそらく、答えはチームによって異なります)。具体的な答えは、チームの開発者やテスターによって少しずつ異なりますが、最初に考え付くことはそれほど違いはないはずです。以下に、開発者としての私の目標を示します。

  • ビルドを中断させない
  • バグを再発させない
  • 敢然とリファクタリングに挑む
  • 臆さずアーキテクチャを変更する
  • テスト駆動開発 (TDD) によって設計を助ける

もちろん、こうした目標の背後にある大きな "理由" は、品質です。こうした目標を実現できれば、開発者としての生産性が高まり、生活がもっと楽しくなります。

チームのテスター向けに、今回はアジャイル テスターの 1 つの側面である、自動テストの作成に注目します。自動テストを作成するテスターの目標には、バグの再発の防止、受け入れ駆動型の開発、コード カバレッジの収集とレポートなどがあります。

もちろん、チームのテスターは自動テストの作成だけを行うわけではなく、非常に多くの役割を担っています。テスターは、単体テストを行うだけでなく、すべてのテストからの結果を含むだけのコード カバレッジ数が必要になるため、コード カバレッジの収集も行わなくてはなりません (詳細については、後ほど扱います)。

今回の記事では、ここに掲げた目標を実現するために、チームが使用しているさまざまなツールと手法を紹介します。

ゲート チェックインでビルドの中断をなくす

以前、チームでは、テストするビルドを常に安定した状態に保つために分岐を使用していました。しかし、分岐の維持にはオーバーヘッドが生じます。今では、ゲート チェックインを取り入れるようになり、分岐はリリース用にしか使用しません。これは、すばらしい変更点です。

ゲート チェックインを使用するには、ビルド コントロールと、1 つ以上のビルド エージェントを設定します。詳細についてはここで取り上げませんので、MSDN ライブラリの「Team Foundation のビルドの管理」(bit.ly/jzA8Ff) を参照してください。

ビルド エージェントを設定および実行したら、Visual Studio 内で次の手順を実行し、ゲート チェックイン用に新しいビルド定義を作成します。

  1. メニュー バーの [表示] をクリックして [チーム エクスプローラー] をクリックし、[チーム エクスプローラー] ウィンドウを表示します。

  2. チーム プロジェクトを展開して、[ビルド] を右クリックします。

  3. [新しいビルド定義] をクリックします。

  4. 左側で [トリガー] をクリックし、右側で [ゲート チェックイン] を選択します (図 1 参照)。

    新しいビルド定義に [ゲート チェックイン] オプションを選択する

    図 1 新しいビルド定義に [ゲート チェックイン] オプションを選択する

  5. [ビルドの既定値] をクリックして、ビルド コントローラーを選択します。

  6. [プロセス] をクリックして、ビルドする項目を選択します。

ビルド定義 ("Gated Checkin") を保存したら、チェックインの送信後にダイアログ ボックスが新しく表示されます (図 2 参照)。[ビルドの変更] をクリックすると、シェルブセットが作成され、ビルド サーバーに送信されます。ビルド エラーがなく、すべての単体テストが成功したら、TFS が変更をチェックインします。それ以外の場合は、チェックインが拒否されます。

[ゲート チェックイン] ダイアログ ボックス

図 2 [ゲート チェックイン] ダイアログ ボックス

ゲート チェックインは、ビルド エラーが発生しないことを確認する、とても優れた機能です。また、すべての単体テストに合格することも確認します。開発者は、チェックイン前にすべてのテストを実行することを忘れがちですが、ゲート チェックインがあればそれも過去の話です。

C++ の単体テストを作成する

これで、ゲート チェックインの一環として単体テストを実行する方法がわかりました。ここからは、ネイティブ C++ コードの単体テストを作成できる 1 つの方法について見ていきましょう。

私は TDD の大ファンですが、それにはいくつか理由があります。何よりも、コードの動作に注目するため、テストの設計をシンプルに保つことができます。また、この動作コントラクトを定義するというテスト形式はセーフティ ネットにもなります。つまり、動作コントラクトに誤って違反した結果として生じるバグを引き起こす恐れなく、リファクタリングを実行できます。さらに、自分が知らないうちに必要な動作に悪影響を与えたくないことを理由に挙げている開発者がいることも知っています。

チームの開発者のひとりは、C++ コードのテストに組み込みのテスト実行ツール (mstest) を使用していたため、C++/CLI を使用して、ネイティブ C++ DLL によって公開されるパブリック関数を呼び出す、.NET Framework の単体テストを作成していました。ここでは、この手法をさらに掘り下げ、運用コード内部にあるネイティブ C++ クラスのインスタンスを直接作成できるようにします。つまり、単なるパブリック インターフェイス以上のものをテストすることを可能にします。

このためには、単体テスト DLL だけでなく、運用 EXE や運用 DLL にもリンクできるスタティック ライブラリに運用コードを配置します (図 3 参照)。

テスト コードと運用コードがスタティック ライブラリ経由で同じコードを共有する

図 3 テスト コードと運用コードがスタティック ライブラリ経由で同じコードを共有する

この目的でプロジェクトを設定するのに必要な手順を示します。まず、スタティック ライブラリを作成します。

  1. Visual Studio で [ファイル] をクリックして [新規作成] をポイントし [プロジェクト] をクリックします。
  2. [インストールされたテンプレート] の一覧で [Visual C++] をクリックします ([他の言語] を展開することが必要な場合もあります)。
  3. プロジェクトの種類の一覧で [Win32 プロジェクト] をクリックします。
  4. プロジェクト名を入力して [OK] をクリックします。
  5. [次へ] をクリックして、[スタティック ライブラリ] をクリックし [完了] をクリックします。

ここで、テスト DLL を作成します。テスト プロジェクトの設定にはもう少し手順が必要です。プロジェクトを作成するだけでなく、そのプロジェクトからスタティック ライブラリのコード ファイルとヘッダー ファイルにアクセスできるようにしなくてはなりません。

まず、ソリューション エクスプローラーで、ソリューションを右クリックします。[追加] をクリックして、[新しいプロジェクト] をクリックします。テンプレートの一覧の [Visual C++] ノード下の [テスト] をクリックします。プロジェクト名 (チームではプロジェクト名の最後に "UnitTests" を追加しています) を入力して [OK] をクリックします。

ソリューション エクスプローラーで新しいプロジェクトを右クリックして [プロパティ] をクリックします。左側のツリーで [共通プロパティ] をクリックします。[新しい参照の追加] をクリックします。[プロジェクト] タブをクリックし、スタティック ライブラリを含むプロジェクトを選択し、[OK] をクリックして [参照の追加] ダイアログ ボックスを閉じます。

左側のツリーで [構成プロパティ] ノードを展開して、[C/C++] ノードを展開します。[C/C++] ノード下の [全般] をクリックします。[構成] ボックスをクリックして、[すべての構成] を選択し、[Debug] バージョンと [Release] バージョンの両方を変更するようにします。

[追加のインクルード ディレクトリ] をクリックして、スタティック ライブラリへのパスを入力します。ここでは、スタティック ライブラリ名を MyStaticLib に置き換える必要があります。

$(SolutionDir)\MyStaticLib;%(AdditionalIncludeDirectories)

同じプロパティの一覧で [共通言語ランタイム サポート] プロパティをクリックして、[共通言語ランタイム サポート (/clr)] に変更します。

[構成プロパティ] の [全般] セクションをクリックして、[ターゲット名] プロパティを "$(ProjectName)" に変更します。既定では、これはすべてのテスト プロジェクトで [DefaultTest] に設定されていますが、これを自身のプロジェクト名にします。[OK] をクリックします。

スタティック ライブラリを運用 EXE または運用 DLL に追加するには、この手順の冒頭部分を繰り返します。

最初の単体テストを作成する

新しい単体テストの作成に必要なものはすべて揃いました。テスト メソッドは C++ で記述する .NET メソッドになるため、構文はネイティブ C++ とはやや異なります。C# をご存知であれば、多くの点で C++ と C# の構文を混ぜ合わせたものであることがおわかりになると思います。詳細については、MSDN ライブラリのドキュメント「CLR をターゲットにするための言語機能」(bit.ly/iOKbR0、英語) を参照してください。

テスト対象のクラス定義として、次のような定義があるとします。

#pragma once
class MyClass {
  public:
    MyClass(void);
    ~MyClass(void);

    int SomeValue(int input);
};

ここで、SomeValue メソッドの動作を指定するテストを作成します。図 4 に .cpp ファイル全体を示します。シンプルな単体テストとはどのようなものかがおわかりいただけると思います。

図 4 シンプルな単体テスト

#include "stdafx.h"
#include "MyClass.h"
#include <memory>
using namespace System;
using namespace Microsoft::VisualStudio::TestTools::UnitTesting;

namespace MyCodeTests {
  [TestClass]
  public ref class MyClassFixture {
    public:
      [TestMethod]
      void ShouldReturnOne_WhenSomeValue_GivenZero() {
        // Arrange
        std::unique_ptr<MyClass> pSomething(new MyClass);
 
        // Act
        int actual = pSomething->SomeValue(0);
 
        // Assert
        Assert::AreEqual<int>(1, actual);
      }
  };
}

単体テストの作成に慣れていない方のために説明すると、ここでは Arrange、Act、Asset というパターンを使用しています。Arrange 部分では、テストするシナリオの前提条件を設定します。Act 部分では、テスト対象のメソッドを呼び出します。Assert 部分で、メソッドが想定どおりに動作しているかをチェックします。コードを読みやすくするため、また Act セクションを見つけやすくするために、いつも、それぞれのセクションの冒頭にコメントを追加しています。

図 4 に示すように、テスト メソッドは、TestMethod 属性でマークします。テスト メソッドは、TestClass 属性でマークしたクラスに含めなければなりません。

テスト メソッドの 1 行目で、ネイティブ C++ クラスの新しいインスタンスを作成します。ここでは、このインスタンスがテスト メソッドの最後で自動的に削除されるように、標準 C++ ライブラリ クラスの unique_ptr を使用します。このため、ネイティブ C++ と .NET コードの C++/CLI が混在していることがよくわかると思います。もちろん、これには制約があり、この後その概要について説明します。

また、.NET のテストを初めて作成する方のために、Assert クラスには、さまざまな条件のチェックに使用できる便利なメソッドが数多く含まれています。結果から期待するデータ型について明確にするため、ジェネリック バージョンを使用することをお勧めします。

C++/CLI のテストを最大限に活用する

前述したように、ネイティブ C++ コードと C++/CLI コードを混在させるときは、注意しなくてはならない制限事項があります。2 つのコードの相違点は、2 つのコード ベースのメモリ管理における違いに起因します。ネイティブ C++ は、メモリの確保に C++ new 演算子を使用しますが、確保したメモリは手動で解放しなければなりません。メモリの一部を確保すると、データは常に同じ場所に存在することになります。

一方、C++/CLI コードのポインターの動作は大きく異なります。これは、.NET Framework から継承するガベージ コレクション モデルに原因があります。C++/CLI では、オブジェクトへのポインターではなく、オブジェクトのハンドルを返す gcnew 演算子を new 演算子の代わりに使用して、新しい .NET オブジェクトを作成します。ハンドルは、基本的にはポインターへのポインターです。ガベージ コレクションがメモリ内でマネージ オブジェクトを移動すると、そのハンドルは移動先の新しい場所に更新されます。

マネージ ポインターとネイティブ ポインターを混在させるときは、細心の注意が必要です。ここからは、これらの違いをいくつか取り上げて、ネイティブ C++ オブジェクトに C++/CLI テストを最大限に活用するためのヒントとテクニックを紹介します。

文字列へのポインターを返すメソッドをテストするとします。C++ では、LPCTSTR で文字列ポインターを表します。しかし、C++/CLI では .NET の文字列が String^ によって表されます。クラス名の後ろにあるキャレットは、マネージ オブジェクトへのハンドルであることを表します。

ここで、メソッド呼び出しから返される文字列値をテストする方法の一例を示します。

// Act
LPCTSTR actual = pSomething->GetString(1);
 
// Assert
Assert::AreEqual<String^>("Test", gcnew String(actual));

最後の行には、すべての詳細が含まれています。ここには、マネージ文字列を受け取る AreEqual メソッドがありますが、ネイティブ C++ 文字列に対応するメソッドはありません。このため、マネージ文字列を使用する必要があります。AreEqual メソッドへの最初のパラメーターはマネージ文字列なので、たとえば _T または L を使用して Unicode 文字列としてマークされていなくても、実際には Unicode 文字列です。

String クラスには、C++ 文字列を受け取るコンストラクターがあるため、AreEqual が、それらが同じ値であることを確認した時点で、テスト対象メソッドからの実際の値を含む新しいマネージ文字列を作成することができます。

Assert クラスには、非常に魅力的に見える 2 つのメソッド、IsNull および IsNotNull があります。ただし、これらのメソッドのパラメーターはハンドルで、オブジェクト ポインターではありません。つまり、それらはマネージ オブジェクトでしか使えません。そこで、次のようにすれば IsTrue メソッドを使用することができます。

Assert::IsTrue(pSomething != nullptr, "Should not be null");

これで同じことを実現できますが、コードがわずかに増えています。これは、テストの結果ウィンドウに表示されるメッセージで想定結果が明確になるように、コメントを追加したためです (図 5 参照)。

エラー メッセージに追加コメントが表示されている [テスト結果]

図 5 エラー メッセージに追加コメントが表示されている [テスト結果]

設置と片付けのコードを共有する

テスト コードは、運用コードと同等に扱います。つまり、テスト コードの保守を容易にするため、運用コードと同様にテスト コードをリファクタリングします。ある時点で、テスト クラスのすべてのテスト メソッドに共通の設置と片付けのコードが存在するようになります。各テストの前に実行するメソッドや、各テストの後に実行するメソッドを指定できます。どちらか一方、または両方を指定することも、どちらも指定しないことも可能です。

TestInitialize 属性は、テスト クラスの各テスト メソッドの前に実行するメソッドをマークします。同様に、TestCleanup は、テスト クラスの各テスト メソッドの後に実行するメソッドをマークします。以下に例を示します。

[TestInitialize]
void Initialize() {
  m_pInstance = new MyClass;
}
 
[TestCleanup]
void Cleanup() {
  delete m_pInstance;
}

MyClass *m_pInstance;

まず、m_pInstance のクラスへの簡単なポインターを使用しています。なぜ有効期間を管理できる unique_ptr を使用しなかったのでしょう。

その答えは、またもや、ネイティブ C++ と C++/CLI を混在させることに関係します。C++/CLI のインスタンス変数はマネージ オブジェクトの一部なので、マネージ オブジェクトへのハンドル、ネイティブ オブジェクトへのポインター、または値型にしかできません。ネイティブ C++ インスタンスの有効期間を管理するために、new 演算子の基本に立ち返って、削除する必要があります。

インスタンス変数へのポインターを使用する

COM を使用していると、次のようなコードを記述したくなる状況に出くわすことがあります。

[TestMethod]
Void Test() {
  ...
  HRESULT hr = pSomething->GetWidget(&m_pUnk);
  ...
}

IUnknown *m_pUnk;

これはコンパイルされず、次のようなエラー メッセージが表示されます。

1 番目の引数を 'cli::interior_ptr<Type>' から 'IUnknown **' に変換できません

C++/CLI のインスタンス変数のアドレスには、この場合は interior_ptr<IUnknown *> という型が含まれますが、この型はネイティブ C++ コードと互換性がありません。なぜこうしたかですって。単にポインターが欲しかっただけです。

テスト クラスはマネージ クラスになるため、このクラスのインスタンスは、ガベージ コレクターによってメモリ内を移動する可能性があります。そのため、インスタンス変数へのポインターを使用すると、オブジェクトが移動したときに、そのポインターが無効になります。

ネイティブ呼び出しの間、オブジェクトを次のようにロックすることができます。

cli::pin_ptr<IUnknown *> ppUnk = &m_pUnk;
HRESULT hr = pSomething->GetWidget(ppUnk);

最初の行は、変数がスコープ外になるまでインスタンスをロックします。そのため、インスタンス変数がマネージ テスト クラス内に含まれていても、ネイティブ C++ にインスタンス変数へのポインターを渡すことができます。

テストが容易なコードを作成する

この記事の冒頭で、テストが容易なコードを作成することの重要性について触れました。コードのテストを容易にするために TDD を使用しますが、コードの作成直後にテストを作成するのを好む開発者もいます。どちらの場合も、単体テストだけでなく、テスト スタック全体について考えることが重要です。

アジャイルについて多数の書籍を執筆している著名な Mike Cohn は、テストの種類という考え方と、各レベルにいくつテストが必要かということを示す、テスト自動化のピラミッドを描きました。開発者は、すべて、またはほぼすべての単体テストとコンポーネントのテスト、さらにおそらくいくつかの統合テストを作成する必要があります。このテスト ピラミッドの詳細については、bit.ly/eRZU2p で、Cohn のブログ記事「The Forgotten Layer of the Test Automation Pyramid」(テスト自動化ピラミッドの忘れられた層、英語) を参照してください。

テスターは、一般に、受け入れテストと UI テストの作成を担当します。これらは、エンドツーエンド (E2E) テストとも呼ばれます。Cohn のピラミッドにおいて、UI トライアングルは、他のテストの種類の領域と比べて最も小さいものです。これは、自動 UI テストをできる限り少なくするという考えによるものです。自動 UI テストはぜい弱で、作成と保守にコストがかかる傾向があります。UI を少し変更しただけで、簡単に UI テストが破綻します。

コードのテストが容易になるように作成しないと、ピラミッドが逆転し、ほとんどの自動テストが UI テストになってしまいます。これは好ましくない状況ですが、重要なのは、テスターが UI 下で統合テストと受け入れテストを作成できるようにするのは開発者の仕事だということです。

さらに、どういうわけか、これまで遭遇したテスターのほとんどは、C# でテストを作成するのはまったく苦にならないのに、C++ でテストを作成するのは消極的でした。結果として、チームでは、テスト対象の C++ コードと自動テストの間に橋を架ける必要がありました。この橋は、C# コードにとっては他のマネージ クラスのように見える C++/CLI クラスである、フィクスチャの形式を取っています。

C# から C++ へのフィクスチャをビルドする

ここでの手法は、C++/CLI テストを作成するために扱ったものとは大きく異なります。どちらも、同じ種類の混在モードのコードを使用していますが、最終的な使用方法が異なります。

まず、フィクスチャを含む新しいプロジェクトを作成します。

  1. ソリューション エクスプローラーでソリューション ノードを右クリックし、[追加] をポイントして [新しいプロジェクト] をクリックします。
  2. ([他の言語])、[Visual C++]、[CLR] ノードを展開したら、[クラス ライブラリ] をクリックします。
  3. このプロジェクトに使用する名前を入力して [OK] をクリックします。
  4. 参照とインクルード ファイルを追加するために、テスト プロジェクトを作成する手順を繰り返します。

フィクスチャ クラス自体はテスト クラスにやや似ていますが、さまざまな属性がありません (図 6 参照)。

図 6 C# から C++ へのテスト フィクスチャ

#include "stdafx.h"
#include "MyClass.h"
using namespace System;
 
namespace MyCodeFixtures {
  public ref class MyCodeFixture {
    public:
      MyCodeFixture() {
        m_pInstance = new MyClass;
      }
 
      ~MyCodeFixture() {
        delete m_pInstance;
      }
 
      !MyCodeFixture() {
        delete m_pInstance;
      }
 
      int DoSomething(int val) {
        return m_pInstance->SomeValue(val);
      }
 
      MyClass *m_pInstance;
  };
}

ヘッダー ファイルがないことに注目してください。これは、C++/CLI の機能で気に入っている点の 1 つです。このクラス ライブラリはマネージ アセンブリをビルドするため、クラスについての情報は .NET 型情報として格納されます。したがって、ヘッダー ファイルは必要ありません。

このクラスには、デストラクターとファイナライザーもあります。デストラクターは、実際のデストラクターではありません。コンパイラは、デストラクターを IDisposable インターフェイスの Dispose メソッドの実装に書き換えます。このため、デストラクターを含む C++/CLI クラスはすべて IDisposable インターフェイスを実装します。

!MyCodeFixture メソッドは、ガベージ コレクターがこのオブジェクトを解放すると決定したときに、以前 Dispose メソッドを呼び出していなければ、ガベージ コレクターから呼び出されるファイナライザーです。埋め込みのネイティブ C++ オブジェクトの有効期間を制御するために using ステートメントを使用することもできますし、ガベージ コレクターに有効期間を処理させることもできます。この動作の詳細については、MSDN ライブラリの記事「デストラクターのセマンティクスの変更」(bit.ly/kW8knr) を参照してください。

C++/CLI のフィクスチャ クラスの用意ができたら、図 7 のような C# の単体テストを作成できます。

図 7 C# の単体テスト システム

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyCodeFixtures;
 
namespace MyCodeTests2 {
  [TestClass]
  public class UnitTest1 {
    [TestMethod]
    public void TestMethod1() {
      // Arrange
      using (MyCodeFixture fixture = new MyCodeFixture()) {
        // Act
        int result = fixture.DoSomething(1);
 
        // Assert
        Assert.AreEqual<int>(1, result);
      }
    }
  }
}

ガベージ コレクターを使用する代わりに using ステートメントを使用して、フィクスチャ オブジェクトの有効期限を明示的に制御してもかまいません。これは、テストが他のテストとやり取りしないようにするため、テスト メソッドにおいては特に重要です。

コード カバレッジの取得とレポート

この記事の冒頭で紹介したピックの最後がコード カバレッジです。チームの目標は、ビルド サーバーによってコード カバレッジが自動取得され、TFS に発行されて、だれもが簡単に利用できるようになることです。

まず、実行中のテストから C++ のコード カバレッジを取得する方法を見つけます。Web を検索したところ、Emil Gustafsson による「Native C++ Code Coverage Reports Using Visual Studio 2008 Team System」(Visual Studio 2008 Team System を使用したネイティブの C++ コード カバレッジのレポート、英語) という有益な情報を含むブログ記事 (bit.ly/eJ5cqv) を見つけました。このブログでは、コード カバレッジの情報を取得するのに必要な手順が示されています。今回はこれを開発コンピューターでいつでも実行できる CMD ファイルに変換し、コード カバレッジ情報を取得しました。

"%VSINSTALLDIR%\Team Tools\Performance Tools\vsinstr.exe" Tests.dll /COVERAGE
"%VSINSTALLDIR%\Team Tools\Performance Tools\vsperfcmd.exe" /START:COVERAGE /WaitStart /OUTPUT:coverage
mstest /testcontainer:Tests.dll /resultsfile:Results.trx
"%VSINSTALLDIR%\Team Tools\Performance Tools\vsperfcmd.exe" /SHUTDOWN

"Tests.dll" を、テストを含む DLL の実際の名前に置き換えます。また、DLL のインストルメント化の準備も必要です。

  1. ソリューション エクスプローラーでテスト プロジェクトを右クリックします。
  2. [プロパティ] をクリックします。
  3. 構成に [Debug] を選択します。
  4. [構成プロパティ] を展開して、[リンカー] を展開し、[詳細設定] をクリックします。
  5. [プロファイル] プロパティを [はい (/PROFILE)] に変更します。
  6. [OK] をクリックします。

この手順により、プロファイリングが有効になります。プロファイリングは、コード カバレッジ情報を取得できるように、アセンブリをインストルメント化するために有効にする必要があります。

プロジェクトをリビルドして、CMD ファイルを実行します。これにより、カバレッジ ファイルが作成されます。このカバレッジ ファイルを Visual Studio に読み込んで、テストからコード カバレッジを取得できるようにします。

ビルド サーバーでこれらの手順を実行し、結果を TFS に発行するには、カスタム ビルド テンプレートが必要です。TFS ビルド テンプレートは、バージョン管理に保存され、特定のチーム プロジェクトに属します。各チーム プロジェクト下に BuildProcessTemplates というフォルダーがあり、ここには、多くの場合、いくつかのビルド テンプレートが含まれています。

ダウンロードに含めたカスタム ビルド テンプレートを使用するには、ソース管理エクスプローラー ウィンドウを開きます。チーム プロジェクトの BuildProcessTemplates フォルダーに移動して、コンピューターのディレクトリにマップします。BuildCCTemplate.xaml ファイルを、このマップした場所にコピーします。このテンプレートをソース管理に追加して、チェックインします。

テンプレート ファイルをビルド定義で使用するには、チェックインする必要があります。

ビルド テンプレートをチェックインしたら、コード カバレッジを実行するためのビルド定義を作成できます。C++ のコード カバレッジは、先ほどのように、vsperfmd コマンドを使用して収集します。vsperfmd は、vsperfcmd の実行中に実行される、すべてのインストルメント化された実行可能ファイルのコード カバレッジ情報をリッスンします。このため、別のインストルメント化されたテストを同時に実行しないようにします。また、これらのコード カバレッジの実行を処理するコンピューターでは、ビルド エージェントは 1 つしか実行しないようにします。

今回は、夜間に実行するビルド定義を作成しました。次の手順を実行すると、同じものを作成できます。

  1. チーム エクスプローラーで、チーム プロジェクトのノードを展開します。
  2. チーム プロジェクト下にある [ビルド ノード] を右クリックします。
  3. [新しいビルド定義] をクリックします。
  4. [トリガー] セクションで [スケジュール] をクリックして、コード カバレッジを実行する曜日を選択します。
  5. [プロセス] をクリックし、上部の [ビルド プロセス テンプレート] セクションで [詳細の表示] をクリックして、ソース管理にチェックインしたビルド テンプレートを選択します。
  6. その他必要なセクションに入力して保存します。

テスト設定ファイルを追加する

ビルド定義には、テスト設定ファイルも必要です。これは、結果を取得して発行する必要がある DLL を列挙する XML ファイルです。次に、コード カバレッジのためにこのファイルをセットアップする手順を示します。

  1. Local.testsettings ファイルをダブルクリックして、[テストの設定] ダイアログ ボックスを開きます。
  2. 左側の一覧で [データと診断] をクリックします。
  3. [コード カバレッジ] をクリックして、チェック ボックスをオンにします。
  4. 一覧の上にある [構成] ボタンをクリックします。
  5. テストを含む (また、テストによってテストが実行されているコードを含む) DLL の横にあるチェック ボックスをオンにします。
  6. [同じ場所でアセンブリをインストルメント化する] チェック ボックスをオフにします (ビルド定義がこれに対処するため)。
  7. [OK] をクリックして [適用] をクリックし、[閉じる] をクリックします。

複数のソリューションをビルドする場合や、複数のテスト プロジェクトがある場合、コード カバレッジのために監視する必要があるすべてのアセンブリの名前を含むテスト設定ファイルのコピーが必要になります。

このためには、分岐のルートにテスト設定ファイルをコピーして、CC.testsettings など、内容がわかる名前を付けます。XML を編集します。このファイルには、前の手順から少なくとも 1 つの CodeCoverageItem 要素が含まれます。取得する各 DLL に 1 つのエントリを追加することをお勧めします。パスは、プロジェクト ファイルの場所への相対パスであり、テスト設定ファイルの場所への相対パスではないことに注意してください。このファイルをソース管理にチェックインします。

最後に、このテスト設定ファイルを使用するために、ビルド定義を変更する必要があります。

  1. チーム エクスプローラー ウィンドウで、チーム プロジェクトのノードを展開して、[ビルド] を展開します。
  2. 先ほど作成したビルド定義を右クリックします。
  3. [ビルド定義の編集] をクリックします。
  4. [プロセス] セクションで [自動テスト] を展開して、[1. テスト アセンブリ] を展開し、[テストの設定ファイル] をクリックします。[...] ボタンをクリックして、先ほど作成したテスト設定ファイルを選択します。
  5. 変更を保存します。

このビルド定義は、右クリックして、[新しいビルドをキューに配置] を選択し、新しいビルドをすぐに開始することで、テストできます。

コード カバレッジをレポートする

ここでは、図 8 のように、コード カバレッジを表示するため、SQL Server Reporting Services のカスタム レポートを作成しました (問題を避けるため、実際のプロジェクト名はぼかしました)。このレポートは、TFS ウェアハウスのデータを読み取るのに SQL クエリを使用し、C++ と C# コードの結果を組み合わせて表示しています。

コード カバレッジのレポート

図 8 コード カバレッジのレポート

このレポートのしくみについては詳しく扱いませんが、指摘しておくことがいくつかあります。結果に、テスト メソッド コードと、(ヘッダー ファイルにある) C++ 標準ライブラリが含まれていることが原因で、データベースには C++ のコード カバレッジからの情報が必要以上にたくさん含まれています。

余分なデータをフィルター処理する SQL クエリにコードを追加します。レポート内の SQL は次のようになります。

and CodeElementName not like 'std::%'
and CodeElementName not like 'stdext::%'
and CodeElementName not like '`anonymous namespace'':%'
and CodeElementName not like '_bstr_t%'
and CodeElementName not like '_com_error%'
and CodeElementName not like '%Tests::%'

これらの行は、特定の名前空間 (std、stdext、および anonymous) のコード カバレッジの結果、Visual Studio C++ に付属している 2つのクラス (_bstr_t および _com_error)、および Tests で終わる名前空間内のすべてのコードを除外しています。

Tests で終わる名前空間を除外する最後の行により、テスト クラスのすべてのメソッドが除外されます。新しいテスト プロジェクトを作成するとき、プロジェクト名が Tests で終わるため、すべてのテスト クラスが Tests で終わる名前空間内に既定で存在するようになります。ここに、除外する必要があるそれ以外のクラスや名前空間を追加することができます。

ここでは、実行可能なことの表面をなぞったに過ぎません。プロジェクトの進捗については、私のブログ (blogs.msdn.com/b/jsocha、英語) でぜひ確認してください。

John Socha-Leialoha は、マイクロソフトの管理プラットフォームおよびサービス提供グループの開発者です。彼の経歴には、(C およびアセンブラーでの) Norton Commander の開発や、『Peter Norton's Assembly Language Book』(1987 年、Brady) の執筆などがあります。

この記事のレビューに協力してくれた技術スタッフの Rong Lu に心より感謝いたします。