DevOps 開発者の 1 日: ユーザー ストーリーの新しいコードを作成する

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

Visual Studio 2019 | Visual Studio 2022

このチュートリアルでは、自分とチームが最新バージョンの Team Foundation バージョン管理 (TFVC) と Visual Studio を最大限に活用してアプリをビルドする方法を順に説明します。 このチュートリアルでは、Visual Studio と TFVC を使って、コードのチェックアウトと更新、割り込みが入ったときの作業中断、コード レビューの依頼、変更箇所のチェックインなどのタスクを実行する例が紹介されています。

チームがコードを管理するために Visual Studio と TFVC を採用する場合、サーバーとクライアント コンピューターのセットアップ、バックログの作成、イテレーションの計画など、アプリケーションの開発に取りかかるのに必要な計画を完了します。

開発者はバックログを検討して、作業するタスクを選択します。 開発する予定のコードの単体テストを記述します。 通常は、単体テストを 1 時間に数回実行し、徐々に詳細なテストにしあげて、それに合格するようにコードを記述します。 多くの場合、開発者は、作成しているメソッドを使用する同僚とコード インターフェイスについて話し合います。

Visual Studio の [担当作業] および [コード レビュー] ツールは、開発者が作業を管理し、同僚と共同作業するのに役立ちます。

注意

Visual Studio の [担当作業] および [コード レビュー] 機能は、次のエディションで利用できます。

  • Visual Studio 2022: Visual Studio Community、Visual Studio Professional、Visual Studio Enterprise
  • Visual Studio 2019: Visual Studio Professional、Visual Studio Enterprise

作業項目を確認し、作業開始の準備をする

チーム内では、現在のスプリント中に、製品バックログで優先順位の高い請求書の状態の評価タスクに取り組むことで合意が得られています。 この優先順位の高いバックログ項目の子タスクである数学関数の実装から作業を始めることにします。

Visual Studio チーム エクスプローラー[担当作業] ページで、このタスクを [使用できる作業項目] リストから [処理中の作業] リストにドラッグします。

バックログをレビューして作業を開始するタスクを準備するには

[担当作業] ページのスクリーンショット。

  1. チーム エクスプローラーで、作業するプロジェクトにまだ接続されていない場合は、プロジェクトに接続します

  2. [ホーム] ページから、[担当作業] を選択します。

  3. [担当作業] ページで、[使用できる作業項目] ボックスにあるタスクを [処理中の作業] セクションにドラッグします。

    また [使用できる作業項目] ボックスでタスクをクリックして、[開始] をクリックすることもできます。

インクリメンタル作業計画案の作成

一連の小さな手順を踏んでコードを開発します。 各手順は、通常、1 時間かからず、わずか 10 分で済むこともあります。 各手順で、新しい単体テストを記述し、作成済みのテストに加えて、新しい単体テストにも合格するように開発中のコードを変更します。 コードを変更する前に新しいテストを記述することも、テストを記述する前にコードを変更することもあります。 リファクタリングすることもあります。 つまり、新しいテストを追加しないでコードの手直しだけを実行します。 テストが要件を正しく表現していないと判断した場合を除いて、合格したテストを変更することはありません。

すべての小さな手順の最後に、コードのその部分に関連するすべての単体テストを実行します。 すべてのテストに合格するまで手順が完了したと見なすことはありません。

Azure DevOps Server にコードをチェックインするのは、タスク全体を完了した後です。

この一連の小さな手順の大まかな計画を書き留めることができます。 正確な詳細と後の手順の順序は、作業を進めるうちにおそらく変わることがわかっています。 この特定のタスクについて、手順の最初の一覧を次に示します。

  1. テスト メソッドのスタブ、つまり、メソッドのシグネチャだけを作成します。
  2. 典型的な 1 つの具体例がテストに合格するようにします。
  3. テスト範囲を広げます。 コードが多種多様な値に正しく応答することを確認します。
  4. 負の数の例外処理。 パラメーターが正しくない場合でも適切に処理します。
  5. コード カバレッジ。 コードの少なくとも 80% は、単体テストで実行されることを確認します。

開発者の中には、この種の計画をテスト コードのコメントに書き込む人もいます。 頭の中だけで計画する人もいます。 タスク作業項目の [説明] フィールドに手順の一覧を記述すると便利な場合があります。 緊急のタスクが割り込んできた場合でも、元のタスクに戻ったときにすぐに一覧を見つけることができます。

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

まず単体テストを作成します。 単体テストから始めるのは、新しいクラスを使用するコードの例を記述するためです。

これはテストするクラス ライブラリの最初の単体テストであるため、新しい単体テスト プロジェクトを作成します。

  1. [ファイル]>[新しいプロジェクト] の順に選択します。
  2. [新しいプロジェクトの作成] ダイアログ ボックスで、[すべての言語] の横にある矢印を選択して [C#] を選択し、[すべてのプロジェクトの種類] の横にある矢印を選択して [テスト] を選択してから、[MSTest テスト プロジェクト] を選択します。
  3. [次へ] を選択してから、[作成] を選択します。

[新しいプロジェクトの作成] ダイアログ ボックスで選択されている単体テストのスクリーンショット。

コード エディターで、UnitTest1.cs の内容を次のコードに置き換えます。 この段階では、新しいメソッドのうち 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 の単体テスト フレームワークを使用しますが、他社のフレームワークを使用することもできます。 テスト エクスプローラーは、適切なアダプターをインストールすれば、他社のフレームワークでも同様に動作します。

  1. 上記の手順を使用して、テスト プロジェクトを作成します。 C#F#Visual Basic などの言語を選択できます。

  2. 用意されているテスト クラスにテストを追加します。 単体テストごとに 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();
}

テストからクラスとメソッドを生成するには

最初に、まだない場合は、新しいクラスを追加するプロジェクトを作成します。

クラスを生成するには

  1. 生成するクラスの例、たとえば LocalMath の上にカーソルを置き、[クイック アクションとリファクタリング] を選択します。
  2. ショートカット メニューで、[新しい型の生成] を選択します。
  3. [型の生成] ダイアログ ボックスで、[プロジェクト] をクラス ライブラリ プロジェクトに設定します。 この例では、Fabrikam.Math です。

メソッドを生成するには

  1. メソッド (例: SquareRoot) の呼び出しにカーソルを置き、[クイック アクションとリファクタリング] を選択します。
  2. ショートカット メニューで、[Generate method 'SquareRoot'] (メソッド 'SquareRoot' の生成) を選択します。

最初のテストを実行する

テストをビルドして実行します。 テスト結果には [失敗] を示す赤いインジケーターが表示され、[失敗したテスト] ボックスにテストが表示されます。

1 つのテストが失敗したことを示すテスト エクスプローラーのスクリーンショット。

次のようにコードに簡単な変更を加えます。

public double SquareRoot(double p)
{
    return 0.0;
}

テストを再実行すると、今度は成功します。

1 つのテストが成功したことを示す単体テスト エクスプローラーのスクリーンショット。

単体テストを実行するには

単体テストを実行するには、次の手順を実行します。

  • [テスト]>[すべてのテストを実行する] を選択します。
  • または、テスト エクスプローラーが開いている場合は、[実行] または [ビュー内のすべてのテストを実行] を選択します。

[すべて実行] ボタンを示すテスト エクスプローラーのスクリーンショット。

[失敗したテスト] の下にテストが表示される場合は、たとえば名前をダブルクリックしてテストを開きます。 テストが失敗した場所が、コード エディターに表示されます。

  • テストの一覧を表示するには、[すべて表示] を選択します。

  • テスト結果の詳細を表示するには、テスト エクスプローラーでテストを選択します。

  • テストのコードに移動するには、テスト エクスプローラーでテストをダブルクリックするか、ショートカット メニューの [テストを開く] を選択します。

  • テストをデバッグするには、1 つ以上のテストを選択し、ショートカット メニューの [デバッグ] をクリックします。

  • ソリューションをビルドするたびにバックグラウンドでテストを実行するには、[設定] アイコンの横にある矢印を選択し、[ビルド後にテストを実行する] を選択します。 以前に失敗したテストが最初に実行されます。

インターフェイスに合意する

画面を共有することで、コンポーネントを使用する同僚と共同作業できます。 同僚が、多くの関数が上記のテストに合格するとコメントする場合があります。 このテストは、関数の名前とパラメーターが正しいことを確認するだけであることを説明してください。この関数の主要な要件を把握したテストを記述できるようになりました。

同僚と共同作業して、次のテストを記述します。

[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);
}

ヒント

この関数の場合、"テスト ファースト開発手法" を採用しています。つまり、まず機能の単体テストを作成し、次にテストに合格するコードを作成します。 それ以外の場合、この方法は現実的ではないので、コードを記述した後にテストを記述します。 しかし、安定したコードを作成するには、コード開発の前か後かを問わず、単体テストを作成することが非常に重要です。

赤、緑、リファクタリング…

繰り返しテストを書き、そのテストが失敗することを確認し、テストに合格するコードを書き、リファクタリングを検討するという開発サイクルに従います。リファクタリングとは、テストを変更しないでコードを改良することです。

[赤]

作成した新しいテストを含む、すべてのテストを実行します。 テストを作成した後は、常にテストを実行して、テストが失敗することを確認してから、合格するコードを書きます。 たとえば、作成したテストにアサーションを入れ忘れた場合、失敗の結果が表示されると、合格したときにテスト結果は要件が満たされていることを正しく示していると確信できます。

もう一つの便利な方法は、[ビルド後にテストを実行する] を設定することです。 このオプションを設定すると、ソリューションをビルドするたびにバックグラウンドでテストが実行され、コードのテスト状態が絶えずレポートされます。 この方法によって Visual Studio の反応が遅くなることを心配するかもしれませんが、このようなことはめったに起きません。

1 つのテストが失敗したことを示すテスト エクスプローラーのスクリーンショット。

[緑]

開発中のメソッドのコードで、最初の操作を記述します。

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;
    }

テストを再実行し、すべてのテストに合格します。

2 つのテストが成功したことを示す単体テスト エクスプローラーのスクリーンショット。

リファクター

これでコードがメイン関数を実行するようになったため、コードを見直して、性能を高める方法がないか、または、将来変更しやすくできないかを検討します。 ループ内で実行する計算の数を減らすことができます。

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);
}

このテストは最初に実行したときは合格します。

3 つのテストが成功したことを示すテスト エクスプローラーのスクリーンショット。

この結果が間違っていないことを確認するために、一時的にテストに小さなエラーを紛れ込ませて、テストが失敗するようにすることができます。 失敗することを確認した後、再び修正できます。

ヒント

テストは常に、最初は失敗し、修正すると合格するようにしておきます。

例外

今度は例外的な入力のためにテストを記述します。

[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();
    }

これを修正すると、すべてのテストに合格します。

4 つのテストが成功したことを示す単体テスト エクスプローラーのスクリーンショット。

ヒント

コードに変更を加えるたびに、すべてのテストに合格することを確認します。

コード カバレッジ

作業の合間に、そしてコードを最後にチェックインする前に、コード カバレッジ レポートを入手します。 これは、テストによってコードがどの程度実行されたかを示しています。

チームは、少なくとも 80% のカバレッジを目標としています。 この種のコードで高いカバレッジを実現するのは困難な場合があるため、生成されたコードに対してはこの要件が緩和されています。

カバレッジ率が高いからと言って、コンポーネントのすべての機能がテストされたという保証にはなりませんし、あらゆる範囲の入力値に対してコードが動作することも保証されません。 とはいえ、コード行のカバレッジとコンポーネントの動作空間のカバレッジにはかなり密接な相関関係があります。 したがって、カバレッジ率が高ければ、テスト対象のほとんどの動作をテストしているとチームは自信を深めることができます。

コード カバレッジ レポートを取得するには、Visual Studio [テスト] メニューで [すべてのテストのコード カバレッジの分析] を選択します。 すべてのテストが再度実行されます。

コード カバレッジの結果と色の設定ボタンのスクリーンショット。

レポートの合計を展開すると、開発中のコードには完全なカバレッジがあることがわかります。 重要なのはテスト対象のコードのスコアであるため、これは非常に満足のいく数字です。 カバーされていない部分は、実際にはテスト自体の中にあります。

[コード カバレッジの色分けを表示] を選択すると、テスト コードのどの部分が実行されなかったかを確認できます。 テストで使用されなかったコードは、オレンジ色で強調表示されます。 ただし、実行されなかった部分はテスト コード内にあり、エラーが検出された場合にのみ使用されるため、カバレッジという点では重要ではありません。

特定のテストがコードの特定の分岐に達することを確認するには、[コード カバレッジの色分けを表示] を選択し、ショートカット メニューの [実行] を使用して、1 つのテストを実行します。

終了する時点

以下の条件が満たされるまで、小さな手順に分けてコードの更新を続けます。

  • 使用可能なすべての単体テストに合格します。

    単体テストの数が非常に多いプロジェクトの場合、そのすべてが実行されるのを待つのは現実的ではありません。 代わりに、プロジェクトでゲート チェックイン サービスが実行され、シェルブセットがチェックインされるたびに、それをソース ツリーにマージする前に、すべての自動テストが実行されます。 テストに失敗した場合、チェックインは拒否されます。 これにより、開発者は自分のコンピューター上で最小限の単体テストを実行し、ビルドを中断するリスクを冒すことなく、他の作業に進むことができます。 詳しくは、変更内容を検証するためのゲート チェックイン ビルド プロセスの定義に関するページを参照してください。

  • コード カバレッジがチームの標準に達します。 75% が一般的なプロジェクトの要件です。

  • 通常の入力と例外的な入力の両方を含めて、単体テストが要求される動作のあらゆる側面をシミュレートしています。

  • コードが理解しやすく拡張しやすくなっています。

これらの条件がすべて満たされたときに、コードをソース管理システムにチェックインする準備が整います。

単体テストを使ったコード開発の原則

コード開発中に適用する原則は次のとおりです。

  • 単体テストをコードと共に開発し、開発中に単体テストを頻繁に実行します。 単体テストは、コンポーネントの仕様を表しています。
  • 要求が変更された場合、またはテストが誤っている場合を除いて、単体テストは変更しません。 コードの機能を拡張するのに合わせて、新しいテストを徐々に追加します。
  • テストでコードの少なくとも 75% をカバーすることを目指します。 定期的に、そしてソース コードをチェックインする前に、コード カバレッジ結果を確認します。
  • 連続的または定期的サーバー ビルドで実行されるように、単体テストをコードと共にチェックインします。
  • 可能な限り、機能ごとに最初に単体テストを作成します。 これは、テストに合格するコードを開発する前に作成します。

変更内容をチェックインする

変更箇所をチェックインする前に、同僚と画面をもう一度共有して、作成したコードを気軽にかつ対話的に確認できるようにします。 主にコードの機能に関心があり、コードの動作方法には関心がない同僚との話し合いの焦点は、引き続きテストになります。 これらの同僚が、記述したコードがニーズを満たしていることに同意する必要があります。

テストとコードの両方を含め、すべての変更をチェックインし、完了したタスクに関連付けます。 チェックインは、チームの自動化されたチーム ビルド システムのキューに格納され、チームの CI ビルド プロセスを使って変更が検証されます。 このビルド プロセスは、チームによるあらゆる変更を、開発用コンピューターとは独立したクリーン環境でビルドしテストすることで、チームがコードベースのエラーを最小限に抑えるのに役立ちます。

ビルドが完了すると通知されます。 ビルド結果ウィンドウに、ビルドが成功し、すべてのテストに合格したことが表示されます。

変更内容をチェックインするには

  1. チーム エクスプローラー[担当作業] ページで、[チェックイン] を選択します。

    [担当作業] のチェックインのスクリーンショット。

  2. [保留中の変更] ページで、以下のことを確認します。

    • [含まれる変更] ボックスにすべての該当する変更が表示されていること。
    • [関連作業項目] ボックスにすべての該当する作業項目が表示されていること。
  3. 変更されたファイルとフォルダーのバージョン管理履歴をみたときにチームがこの変更の目的を理解できるように、[コメント] ボックスに説明を入力します。

  4. [チェックイン] を選択します。

    [保留中の変更] のチェックインのスクリーンショット。

コードを継続的に統合するには

継続的統合ビルド プロセスの定義方法の詳細については、CI ビルドを設定するに関連するページを参照してください。 このビルド プロセスを設定した後、チーム ビルド結果に関する通知を受け取るよう設定することができます。

ビルドが成功した [マイ ビルド] ページのスクリーンショット。

詳細については、ビルドの実行、監視、管理に関するページを参照してください。

次のステップ