ALM 開発者の 1 日: ユーザー ストーリーの新しいコードを作成する
発行: 2016年4月
Visual Studio アプリケーション ライフサイクル管理 (ALM) と Team Foundation Server (TFS) を使うのは初めてでしょうか。 これらのツールの最新バージョンを個人とチームが最大限に活用してアプリケーションをビルドする方法をお知りになりたいでしょうか。
それでは、少し時間をさいて、この 2 章に分かれたチュートリアルを一歩一歩読み進め、ケーブル テレビと関連サービスを提供する Fabrikam Fiber 社という架空の企業に勤めるピーターとジュリアという 2 人の開発者の 1 日を追ってください。 Visual Studio と TFS を使って、コードのチェックアウトと更新、割り込みが入ったときの作業中断、コード レビューの依頼、変更箇所のチェックインなどの作業を実行する例が紹介されています。
ここまでのあらすじ
チームは最近、アプリケーション ライフサイクル管理 (ALM) のために Visual Studio と Team Foundation Server の導入に着手しました。 サーバーとクライアント コンピューターのセットアップ、バックログの作成、イテレーションの計画など、アプリケーションの開発に取りかかるのに必要な計画を完了しました。
この章の概要
ピーターは自分のバックログを簡単に確認し、今日実行するタスクを選択します。 開発予定のコードの単体テストを作成することにします。 通常は、単体テストを 1 時間に数回実行し、徐々に詳細なテストにしあげて、それに合格するようにコードを記述します。 しばしば、自分が作成するメソッドを使う同僚と自分のコードのインターフェイスについて話し合います。
注意
このトピックで取り上げる担当作業機能とコード カバレッジ機能は Visual Studio Premium と Visual Studio Ultimate でのみ使用できます。
このトピックの内容
個人のバックログを確認して作業を開始するタスクを準備する
最初の単体テストを作成する
新しいコード用のスタブを作成する
最初のテストを実行する
API に合意する
赤、緑、リファクタリング…
コード カバレッジ
作業を完了する条件
変更内容をチェックインする
個人のバックログを確認して作業を開始するタスクを準備する
チーム エクスプローラーで、ピーターは [担当作業] ページを開きます。 チーム内では、現在のスプリント中に、製品バックログで優先順位の高い「請求書の状態の評価」タスクにピーターが取り組むことで合意が得られています。 ピーターは、この優先順位の高いバックログ項目の子タスクである「数学関数の実装」から作業を始めることにします。 そこで、[使用できる作業項目] ボックスにあるこのタスクを [処理中の作業項目および変更] ボックスにドラッグします。
個人のバックログを確認して作業を開始するタスクを準備するには
チーム エクスプローラーで、次の作業を行います。
作業するチーム プロジェクトにまだ接続されていない場合は、チーム プロジェクトに接続します。
[ホーム] をクリックし、[担当作業] をクリックします。
[担当作業] ページで、[使用できる作業項目] ボックスにあるタスクを [処理中の作業項目] セクションにドラッグします。
また [使用できる作業項目] ボックスでタスクをクリックして、[開始] をクリックすることもできます。
インクリメンタル作業計画案の作成
ピーターは、通常、一連の小さな手順を踏んでコードを開発します。 各手順は、通常、1 時間かからず、わずか 10 分で済むこともあります。 各手順で、ピーターは新しい単体テストを記述し、作成済みのテストに加えて、新しい単体テストにも合格するように開発中のコードを変更します。 コードを変更する前に新しいテストを記述することも、テストを記述する前にコードを変更することもあります。 リファクタリングすることもあります。 つまり、新しいテストを追加しないでコードの手直しだけを実行します。 テストが要件を正しく表現していないと判断した場合を除いて、合格したテストを変更することはありません。
すべての小さな手順の最後に、コードのその部分に関連するすべての単体テストを実行します。 すべてのテストに合格するまで手順が完了したと見なすことはありません。
ただし、タスク全体が完了するまで、Team Foundation Server にコードをチェックインすることはありません。
ピーターは、この一連の小さな手順の大まかな計画を書き留めます。 正確な詳細と後の手順の順序は、作業を進めるうちにおそらく変わることがわかっています。 この特定のタスクについて、手順の最初の一覧を次に示します。
テスト メソッドのスタブ、つまり、メソッドのシグネチャだけを作成します。
典型的な 1 つの具体例がテストに合格するようにします。
テスト範囲を広げます。 コードが多種多様な値に正しく応答することを確認します。
負の数の例外処理。 パラメーターが正しくない場合でも適切に処理します。
コード カバレッジ。 コードの少なくとも 80% は、単体テストで実行されることを確認します。
同僚には、この種の計画をテスト コードのコメントに書き込む人もいます。 頭の中だけで計画する人もいます。 ピーターは、タスク作業項目の説明フィールドに手順の一覧を記述すると便利であるため、そうしています。 緊急のタスクが割り込んできた場合でも、元のタスクに戻ったときにすぐに一覧を見つけることができます。
最初の単体テストを作成する
ピーターはまず単体テストを作成します。 単体テストから始めるのは、新しいクラスを使用するコードの例を記述するためです。
これはテストするクラス ライブラリの最初の単体テストであるため、新しい単体テスト プロジェクトを作成します。 [新しいプロジェクト] ダイアログ ボックスを開き、[Visual C#]、[テスト]、[単体テスト プロジェクト] の順にクリックします。
単体テスト プロジェクトでは、例を書き込むことのできる C# ファイルが用意されます。 この段階では、新しいメソッドのうち 1 つの呼び出し方法を例示するだけです。
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Fabrikam.Math.UnitTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
// Demonstrates how to call the method.
public void SignatureTest()
{
// Create an instance:
var math = new Fabrikam.Math.LocalMath();
// Get a value to calculate:
double input = 0.0;
// Call the method:
double actualResult = math.SquareRoot(input);
// Use the result:
Assert.AreEqual(0.0, actualResult);
}
}
}
テスト メソッドで例を記述するのは、コードを書き終える前に、例が動作するようにするためです。
単体テスト プロジェクトとメソッドを作成するには
通常、テスト対象のプロジェクトごとに新しいテスト プロジェクトを作成します。 テスト プロジェクトが既に存在する場合は、新しいテスト メソッドとテスト クラスを追加します。
ここでは、Visual Studio の単体テスト フレームワークを使用しますが、他社のフレームワークを使用することもできます。 テスト エクスプローラーは、適切なアダプターをインストールすれば、他社のフレームワークでも同様に動作します。
テスト プロジェクトがまだ存在していない場合は、テスト プロジェクトを作成します。
- [新しいプロジェクト] ダイアログ ボックスで、プログラム言語を選択します ([Visual Basic]、[Visual C++]、[Visual C#] など)。 [テスト]、[単体テスト プロジェクト] の順に選択します。
用意されているテスト クラスにテストを追加します。 単体テストごとに 1 つのメソッドです。
各単体テストは TestMethod 属性を前に付ける必要があり、単体テスト メソッドはパラメーターなしにする必要があります。 単体テスト メソッドには任意の名前を付けることができます。
[TestMethod] public void SignatureTest() {...}
<TestMethod()> Public Sub SignatureTest() ... End Sub
各テスト メソッドは、成功したか失敗したかを示すために Assert クラスのメソッドを呼び出す必要があります。 通常は、操作の予期された結果と実際の結果が等しいことを確認します。
Assert.AreEqual(expectedResult, actualResult);
Assert.AreEqual(expectedResult, actualResult)
テスト メソッドでは TestMethod 属性を持たない他の通常のメソッドを呼び出すことができます。
テストを複数のクラスで構成することができます。 各クラスは TestClass 属性を前に付ける必要があります。
[TestClass] public class UnitTest1 { ... }
<TestClass()> Public Class UnitTest1 ... End Class
C++ で単体テストを作成する方法の詳細については、「C++ 用の Microsoft 単体テスト フレームワークを使用した C++ 用単体テストの記述」を参照してください。
新しいコード用のスタブを作成する
次に、ピーターは、新しいコード用のクラス ライブラリ プロジェクトを作成します。 これで、開発中のコード用のプロジェクトと単体テスト用のプロジェクトができました。 テスト プロジェクトから開発中コードに対するプロジェクト参照を追加します。
新しいプロジェクトで、少なくともテストを正常にビルドできるように、新しいクラスと最小バージョンのメソッドを追加します。 テストでの呼び出しからクラスおよびメソッドのスタブを生成するのが最も簡単な方法です。
public double SquareRoot(double p)
{
throw new NotImplementedException();
}
テストからクラスとメソッドを生成するには
最初に、まだない場合は、新しいクラスを追加するプロジェクトを作成します。
クラスを生成するには
生成するクラスの例、たとえば LocalMath の上にカーソルを置きます。 ショートカット メニューで、[コードの生成]、[新しい型] の順にクリックします。
[新しい型] ダイアログ ボックスで、[プロジェクト] をクラス ライブラリ プロジェクトに設定します。 この例の場合、プロジェクトは Fabrikam.Math です。
メソッドを生成するには
- メソッド、たとえば SquareRoot を呼び出している箇所の上にカーソルを置きます。 ショートカット メニューで、[コードの生成]、[メソッド スタブ] の順にクリックします。
最初のテストを実行する
ピーターは、Ctrl キーを押しながら R キーを押し、続いて T キーを押して、テストをビルドし実行します。テスト結果には失敗を示す赤いインジケーターが表示され、[失敗したテスト] ボックスにテストが表示されます。
次のようにコードに簡単な変更を加えます。
public double SquareRoot(double p)
{
return 0.0;
}
テストを再実行すると、今度は成功します。
単体テストを実行するには
[テスト] メニューで [実行]、[すべてのテスト] の順にクリックします。
または
テスト エクスプローラーが開いている場合は、[すべて実行] をクリックします。
または
テスト コード ファイルにカーソルを置いた状態で、Ctrl キーを押しながら R キーを押し、続いて T キーを押します。
テストが [失敗したテスト] ボックスに表示された場合は、次のようにします。
たとえば名前をダブルクリックして、テストを開きます。
テストが失敗した場所が表示されます。
テストの一覧を表示するには、[すべて表示] をクリックします。 概要に戻るには、[ホーム] ビューをクリックします。
テスト結果の詳細を表示するには、テスト エクスプローラーでテストを選択します。
テストのコードに移動するには、テスト エクスプローラーでテストをダブルクリックするか、ショートカット メニューの [テストを開く] をクリックします。
テストをデバッグするには、1 つ以上のテストを選択し、ショートカット メニューの [選択したテストのデバッグ] をクリックします。
ソリューションをビルドするたびにバックグラウンドでテストを実行するには、[ビルド後にテストを実行] を選択します。 以前に失敗したテストが最初に実行されます。
インターフェイスに合意する
ピーターは同僚のジュリアを Lync で呼び出し、自分の画面を共有します。 ジュリアはピーターのコンポーネントを使用する予定です。 ピーターは最初の例を示します。
ジュリアはその例は OK だと思いますが、「そのテストは多くの関数が合格する」とコメントします。
ピーターは返答します。「最初のテストは、関数の名前とパラメーターが正しいことを確認するだけです。 これで、この関数の主要な要件を把握したテストを記述できます」。
2 人は協力して次のテストを作成します。
[TestMethod]
public void QuickNonZero()
{
// Create an instance to test:
LocalMath math = new LocalMath();
// Create a test input and expected value:
var expectedResult = 4.0;
var inputValue = expectedResult * expectedResult;
// Run the method:
var actualResult = math.SquareRoot(inputValue);
// Validate the result:
var allowableError = expectedResult/1e6;
Assert.AreEqual(expectedResult, actualResult, allowableError,
"{0} is not within {1} of {2}", actualResult, allowableError, expectedResult);
}
ヒント
この関数の場合、ピーターはテスト ファースト開発手法を採用しています。つまり、まず機能の単体テストを作成し、次にテストに合格するコードを作成します。別の関数の場合、この手法は現実的でないため、コードを作成した後、テストを作成します。しかし、安定したコードを作成するには、コード開発の前か後かを問わず、単体テストを作成することが非常に重要だとピーターは考えています。
赤、緑、リファクタリング…
ピーターは、繰り返しテストを書き、失敗しないことを確認し、テストに合格するコードを書き、リファクタリングを検討するという開発サイクルに従っています。リファクタリングとは、テストを変更しないでコードを改良することです。
赤 (失敗)
ピーターは、Ctrl キーを押しながら R キーを押し、続いて T キーを押して、ジュリアと一緒に作成した新しいテストを実行します。 テストを作成した後は、常にテストを実行して、合格するコードを書く前にテストが失敗することを確認します。 これは、自分が作成したテストにアサーションを入れ忘れた経験から学んだ方法です。 テストが失敗するのを確認しておくと、テストに合格したときに、要件を満たしていることをテスト結果が正しく示していると自信を持って言うことができます。
もう一つの便利な方法は、[ビルド後にテストを実行] を設定することです。 このオプションを設定すると、ソリューションをビルドするたびにバックグラウンドでテストが実行され、コードのテスト状態が絶えずレポートされます。 最初、ピーターはこうすると Visual Studio の反応が遅くなるのではないかと疑っていましたが、それはめったに起きないことがわかりました。
緑 (成功)
ピーターは、開発中のメソッドのコードで、最初の操作を記述します。
public class LocalMath
{
public double SquareRoot(double x)
{
double estimate = x;
double previousEstimate = -x;
while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
{
previousEstimate = estimate;
estimate = (estimate * estimate - x) / (2 * estimate);
}
return estimate;
}
ピーターはテストを再実行し、すべてのテストに合格します。
リファクタリング
これでコードがメイン関数を実行するようになったため、ピーターはコードを見直して、性能を高める方法がないか、または、将来変更しやすくできないかを検討します。 そして、ループ内で実行する計算の数を減らせることに気が付きました。
public class LocalMath
{
public double SquareRoot(double x)
{
double estimate = x;
double previousEstimate = -x;
while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
{
previousEstimate = estimate;
estimate = (estimate + x / estimate) / 2;
//was: estimate = (estimate * estimate - x) / (2 * estimate);
}
return estimate;
}
これでもテストに合格することを確認します。
ヒント
コードの開発中に行うあらゆる変更は、リファクタリングまたは拡張のどちらかになります。
-
リファクタリングの場合、新しい機能を追加しないため、テストは変更しません。
-
拡張の場合、テストを追加し、既存のテストと新しいテストの両方に合格するのに必要なコード変更を実行します。
変更された要件に合わせて既存のコードを更新する場合、もはや現在の要件を表さない古いテストも削除します。
既に合格しているテストは変更しないようにします。代わりに、新しいテストを追加します。実際の要件を表すテストのみを記述してください。
すべての変更後、テストを実行します。
… そして繰り返す
ピーターは、小さな手順の一覧を大まかな指針として、一連の拡張とリファクタリングの手順を続けます。 拡張するたびに常にリファクタリング手順を実行するわけではなく、また、ときには複数のリファクタリング手順を続けて実行します。 ただし、単体テストはコードを変更するたびに常に実行します。
コードを変更する必要のないテストを追加することもありますが、これは自分のコードが正しく動作するという自信を深めることになります。 たとえば、値の範囲を広げて入力しても関数が動作することを確認する場合は、 次のような追加テストを作成します。
[TestMethod]
public void SqRtValueRange()
{
LocalMath math = new LocalMath();
for (double expectedResult = 1e-8;
expectedResult < 1e+8;
expectedResult = expectedResult * 3.2)
{
VerifyOneRootValue(math, expectedResult);
}
}
private void VerifyOneRootValue(LocalMath math, double expectedResult)
{
double input = expectedResult * expectedResult;
double actualResult = math.SquareRoot(input);
Assert.AreEqual(expectedResult, actualResult, expectedResult / 1e6);
}
このテストは最初に実行したときは合格します。
この結果が間違っていないことを確認するために、一時的にテストに小さなエラーを紛れ込ませて、テストが失敗するようにします。 失敗することを確認した後、再び修正します。
ヒント
テストは常に、最初は失敗し、修正すると合格するようにしておきます。
例外
ピーターは、今度は例外的な入力のためにテストを記述します。
[TestMethod]
public void RootTestNegativeInput()
{
LocalMath math = new LocalMath();
try
{
math.SquareRoot(-10.0);
}
catch (ArgumentOutOfRangeException)
{
return;
}
catch
{
Assert.Fail("Wrong exception on negative input");
return;
}
Assert.Fail("No exception on negative input");
}
このテストを実行すると、コードがループします。 テスト エクスプローラーで [キャンセル] をクリックする必要があります。 すると 10 秒以内にコードが終了します。
ピーターは、無限ループがビルド サーバーで発生しないことを確認しようと考えます。 サーバーでは実行完了までのタイムアウトが設定されていますが、非常に長いタイムアウトであるため、大幅な遅延が発生します。 そのため、次のように、このテストに明示的なタイムアウトを追加します。
[TestMethod, Timeout(1000)]
public void RootTestNegativeInput()
{...
明示的なタイムアウトによりテストは失敗します。
ピーターは続いて、この例外的なケースを処理するためにコードを次のように更新します。
public double SquareRoot(double x)
{
if (x <= 0.0)
{
throw new ArgumentOutOfRangeException();
}
回帰
新しいテストには合格しますが、後戻りが発生しています。 以前は合格していたテストが今度は失敗するようになりました。
ピーターはミスを発見して修正します。
public double SquareRoot(double x)
{
if (x < 0.0) // not <=
{
throw new ArgumentOutOfRangeException();
}
これを修正すると、すべてのテストに合格します。
ヒント
コードに変更を加えるたびに、すべてのテストに合格することを確認します。
コード カバレッジ
作業の合間に、そしてコードを最後にチェックインする前に、ピーターはコード カバレッジ レポートを入手します。 これは、テストによってコードがどの程度実行されたかを示しています。
ピーターのチームは、少なくとも 80% のカバレッジを目標としています。 この種のコードで高いカバレッジを実現するのは困難な場合があるため、生成されたコードに対してはこの要件が緩和されています。
カバレッジ率が高いからと言って、コンポーネントのすべての機能がテストされたという保証にはなりませんし、あらゆる範囲の入力値に対してコードが動作することも保証されません。 とはいえ、コード行のカバレッジとコンポーネントの動作空間のカバレッジにはかなり密接な相関関係があります。 したがって、カバレッジ率が高ければ、テスト対象のほとんどの動作をテストしているとチームは自信を深めることができます。
コード カバレッジ レポートを取得するには、[テスト]、[実行]、[すべてのテストのコード カバレッジの分析] の順にクリックします。 次に、すべてのテストを再実行します。
ピーターは全体のカバレッジが 86% でした。 レポートの合計を展開すると、自分が開発中のコードはカバレッジが 100% であることがわかります。 重要なのはテスト対象のコードのスコアであるため、これは非常に満足のいく数字です。 カバーされていない部分は、実際にはテスト自体の中にあります。 [コード カバレッジの色分けを表示] を選択すると、テスト コードのどの部分が実行されなかったか確認できます。 ただし、実行されなかった部分はテスト コード内にあり、エラーが検出された場合にのみ使用されるため、カバレッジという点では重要でないと判断できます。
特定のテストがコードの特定の分岐に達することを確認するには、[コード カバレッジの色分けを表示] を選択し、ショートカット メニューの [実行] を使用して、1 つのテストを実行します。
作業を完了する条件
ピーターは、以下の条件が満たされるまで、小さな手順に分けてコードの更新を続けます。
使用可能なすべての単体テストに合格します。
単体テストの数が非常に多いプロジェクトの場合、そのすべてが実行されるのを待つのは現実的ではありません。 代わりに、プロジェクトでゲート チェックイン サービスが実行され、シェルブセットがチェックインされるたびに、それをソース ツリーにマージする前に、すべての自動テストが実行されます。 テストに失敗した場合、チェックインは拒否されます。 これにより、開発者は自分のコンピューター上で最小限の単体テストを実行し、ビルドを中断するリスクを冒すことなく、他の作業に進むことができます。 詳細については、「変更内容を検証するためのゲート チェックイン ビルド プロセスの定義」を参照してください。
コード カバレッジがチームの標準に達します。 75% が一般的なプロジェクトの要件です。
通常の入力と例外的な入力の両方を含めて、自分の単体テストが要求される動作のあらゆる側面をシミュレートしています。
自分のコードが理解しやすく拡張しやすくなっています。
これらの条件がすべて満たされたときに、自分のコードをソース管理システムにチェックインする準備が整います。
単体テストを使ったコード開発の原則
ピーターがコード開発中に適用する原則は次のとおりです。
単体テストをコードと共に開発し、開発中に単体テストを頻繁に実行します。 単体テストは、コンポーネントの仕様を表しています。
要求が変更された場合、またはテストが誤っている場合を除いて、単体テストは変更しません。 コードの機能を拡張するのに合わせて、新しいテストを徐々に追加します。
テストでコードの少なくとも 75% をカバーすることを目指します。 定期的に、そしてソース コードをチェックインする前に、コード カバレッジ結果を確認します。
連続的または定期的サーバー ビルドで実行されるように、単体テストをコードと共にチェックインします。
可能な限り、機能ごとに最初に単体テストを作成します。 これは、テストに合格するコードを開発する前に作成します。
変更内容をチェックインする
変更箇所をチェックインする前に、ピーターは再び Lync を使って同僚のジュリアと画面を共有し、自分が作成したコードを気軽にかつ対話的に 2 人で確認できるようにします。 2 人の話し合いでは、ジュリアが主にコードの機能に関心があり、コードの動作方法には関心がないため、引き続きテストが焦点になります。 ジュリアは、ピーターの書いたコードが自分のニーズを満たしていることに同意します。
ピーターは、テストとコードの両方を含めて、自分が行ったすべての変更をチェックインし、それを自分の完了したタスクに関連付けます。 チェックインは、チームの自動化されたチーム ビルド システムのキューに格納され、チームの CI ビルド プロセスを使って変更が検証されます。 このビルド プロセスは、チームによるあらゆる変更を、開発用コンピューターとは独立したクリーン環境でビルドしテストすることで、チームがコードベースのエラーを最小限に抑えるのに役立ちます。
ビルドが完了するとピーターに通知されます。 ビルド結果ウィンドウに、ビルドが成功し、すべてのテストに合格したことが表示されます。
変更内容をチェックインするには
メニュー バーの [表示]、[チーム エクスプローラー] を選択します。
チーム エクスプローラーで、[ホーム]、[担当作業] の順にクリックします。
[担当作業] ページで、[チェックイン] をクリックします。
[保留中の変更] ページで以下を確認します。
[含まれる変更] ボックスにすべての該当する変更が表示されていること。
[関連作業項目] ボックスにすべての該当する作業項目が表示されていること。
変更されたファイルとフォルダーのバージョン管理履歴をみたときにチームがこの変更の目的を理解できるように、[コメント] ボックスに説明を入力します。
[チェックイン] を選択します。
コードを継続的に統合するには
継続的統合ビルド プロセスの定義方法の詳細については、「CI ビルドのセットアップ」を参照してください。 このビルド プロセスを設定した後、チーム ビルド結果に関する通知を受け取るよう設定することができます。
詳細については、「ビルドの実行、監視、管理」を参照してください。