次の方法で共有


.NET の単体テストを作成して実行する

この記事では、マネージド コード用の Microsoft 単体テスト フレームワークと Visual Studio テスト エクスプローラーを使用して、一連の単体テストを作成、実行、およびカスタマイズする手順について説明します。 開発中の C# プロジェクトから始めて、コードを実行するテストを作成し、テストを実行して、結果を確認します。 次に、プロジェクト コードを変更し、テストを再実行します。 これらの手順を実行する前に、これらのタスクの概念の概要を確認する場合は、「単体テストの基本 参照してください。

この記事では、単体テストを手動で作成する方法について説明します。 既存のコードからテストを自動的に生成する場合は、次の記事を参照してください。

[前提条件]

Visual Studio は 、.NET デスクトップ開発 ワークロードと共にインストールする必要があります。

テストするプロジェクトを作成する

  1. Visual Studio を開きます。

  2. スタート ウィンドウで、[新しいプロジェクト 作成] を選択します。

  3. .NET 用の C# コンソール アプリ プロジェクト テンプレートを検索して選択し、[次 ] をクリックします。

    コンソール アプリ テンプレートが表示されない場合は、Visual Studio インストーラーを使用して .NET デスクトップ開発ワークロードをインストールします。

  4. プロジェクトに Bankと名前を付け、[次へ] をクリックします。

  5. 推奨されるターゲット フレームワークまたは .NET 8 を選択し、[作成] を選択します。

    Bank プロジェクトが作成され、ソリューション エクスプローラー に表示され、コード エディターで Program.cs ファイルが開きます。

    手記

    Program.cs エディターで開かない場合は、ソリューション エクスプローラー でファイル Program.cs ダブルクリックして開きます。

  6. Program.cs の内容を、BankAccount クラスを定義する次の C# コードに置き換えます。

    using System;
    
    namespace BankAccountNS
    {
        /// <summary>
        /// Bank account demo class.
        /// </summary>
        public class BankAccount
        {
            private readonly string m_customerName;
            private double m_balance;
    
            private BankAccount() { }
    
            public BankAccount(string customerName, double balance)
            {
                m_customerName = customerName;
                m_balance = balance;
            }
    
            public string CustomerName
            {
                get { return m_customerName; }
            }
    
            public double Balance
            {
                get { return m_balance; }
            }
    
            public void Debit(double amount)
            {
                if (amount > m_balance)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount; // intentionally incorrect code
            }
    
            public void Credit(double amount)
            {
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount;
            }
    
            public static void Main()
            {
                BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99);
    
                ba.Credit(5.77);
                ba.Debit(11.22);
                Console.WriteLine("Current balance is ${0}", ba.Balance);
            }
        }
    }
    
  7. ソリューション エクスプローラーで右クリックし、[名前の変更] を選択して、ファイルの名前を BankAccount.cs に変更します。

  8. [ビルド] メニューで [ソリューションのビルド] をクリックします (または、Ctrl キー と Shift キー + を押しながら B + を押します)。

これで、テストできるメソッドを含むプロジェクトが作成されました。 この記事では、テストで Debit メソッドに焦点を当てます。 Debit メソッドは、口座から入金が取り消されたときに呼び出されます。

単体テスト プロジェクトを作成する

  1. [ファイル] メニューで [追加]>[新しいプロジェクト] の順に選択します。

    ヒント

    ソリューション エクスプローラーの ソリューション を右クリックし、[追加] を選択して、[新しいプロジェクト]>を追加することもできます。

  2. 検索ボックスに テスト を入力し、言語として C# を選択し、C# MSTest Test Project for .NET テンプレートを選択し、[次へ] をクリックしてください。

    手記

    Visual Studio 2019 バージョン 16.9 では、MSTest プロジェクト テンプレートは単体テスト プロジェクト です。

  3. プロジェクトに「BankTests」という名前を指定し、[次へ] をクリックします。

  4. 推奨されるターゲット フレームワークまたは .NET 8 を選択し、[作成] を選択します。

    Visual Studio 2022 バージョン 17.10 以降では、テスト ランナーを選択することもできます。 テストランナーとしては、VSTest を選択するか、MSTestを選択できます。 テスト ランナーの違いの詳細については、「Microsoft.Testing.Platform と VSTest の比較を参照してください。

    BankTests プロジェクトは、Bank ソリューションに追加されます。

  5. BankTests プロジェクトで、Bank プロジェクトへの参照を追加します。

    ソリューション エクスプローラーで、BankTests プロジェクトの [依存関係] を選択し、右クリック メニューの [参照の追加] (または [プロジェクト参照の追加]) を選択します。

  6. [参照マネージャーの] ダイアログ ボックスで、[プロジェクト] 展開し、[ソリューション 選択し、Bank 項目を確認します。

  7. [OK]を選択します。

テスト クラスを作成する

BankAccount クラスを確認するテスト クラスを作成します。 プロジェクト テンプレートによって生成された UnitTest1.cs ファイルを使用できますが、ファイルとクラスにわかりやすい名前を付けます。

ファイルとクラスの名前を変更する

  1. ファイルの名前を変更するには、ソリューション エクスプローラー で、BankTests プロジェクトの UnitTest1.cs ファイルを選択します。 右クリック メニューから、[の名前 変更] を選択し (または F2 キー押して)、ファイルの名前を BankAccountTests.csに変更します。

  2. クラスの名前を変更するには、コード エディターで UnitTest1 にカーソルを置き、右クリックして [の名前 変更] を選択します (または F2 キー押します)。 「BankAccountTests」と入力し、Enter を押します。

BankAccountTests.cs ファイルに次のコードが含まれるようになりました。

// The 'using' statement for Test Tools is in GlobalUsings.cs
// using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

using ステートメントを追加する

using ステートメント をテスト クラスに追加して、完全修飾名を使用せずに、テスト対象のプロジェクトを呼び出すことができるようにします。 クラス ファイルの先頭に、次を追加します。

using BankAccountNS;

テスト クラスの要件

テスト クラスの最小要件は次のとおりです。

  • [TestClass] 属性は、テスト エクスプローラーで実行する単体テスト メソッドを含むクラスで必要です。

  • テスト エクスプローラーで認識する各テスト メソッドには、[TestMethod] 属性が必要です。

単体テスト プロジェクトには、[TestClass] 属性を持たない他のクラスを含めることができます。また、[TestMethod] 属性を持たない他のメソッドをテスト クラスに含めることができます。 これらの他のクラスとメソッドは、テスト メソッドから呼び出すことができます。

最初のテスト メソッドを作成する

この手順では、Debit クラスの BankAccount メソッドの動作を確認する単体テスト メソッドを記述します。

少なくとも 3 つの動作を確認する必要があります。

  • 借方金額が残高より大きい場合、メソッドは ArgumentOutOfRangeException を発生させます。

  • 借方金額が 0 未満の場合、メソッドは ArgumentOutOfRangeException を投げます。

  • メソッドは、借方金額が有効な場合、口座残高からその借方金額を差し引きます。

ヒント

この記事では使用しないため、既定の TestMethod1 メソッドを削除できます。

テスト メソッドを作成するには

最初のテストでは、有効な金額 (つまり、口座残高より小さく、0 より大きい金額) が口座から正しい金額を引き出すことが確認されます。 その BankAccountTests クラスに次のメソッドを追加します。

[TestMethod]
public void Debit_WithValidAmount_UpdatesBalance()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 4.55;
    double expected = 7.44;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    account.Debit(debitAmount);

    // Assert
    double actual = account.Balance;
    Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
}

この方法は簡単です。開始残高を使用して新しい BankAccount オブジェクトを設定し、有効な金額を引き出します。 Assert.AreEqual メソッドを使用して、終了残高が想定どおりであることを確認します。 単体テストでは、Assert.AreEqualAssert.IsTrueなどのメソッドが頻繁に使用されます。 単体テストの記述に関する概念の詳細については、「テストを記述する」を参照してください。

テスト メソッドの要件

テスト メソッドは、次の要件を満たしている必要があります。

  • [TestMethod] 属性で修飾されています。

  • voidを返します。

  • パラメーターを指定することはできません。

テストをビルドして実行する

  1. [ビルド] メニューの [ソリューションのビルド]を選択します (または Ctrl + Shift + B キーを押します)。

  2. テスト エクスプローラーが開いていない場合、それを開くには、上部のメニュー バーで [テスト]>[テスト エクスプローラー] (または [テスト]>[Windows]>[テスト エクスプローラー]) の順に選択します (または、Ctrl + ET キーの順に押します)。

  3. すべてのテストを実行するには、すべて実行 を選択します(または、Ctrl + RVを押します)。

    テストの実行中、テスト エクスプローラーの ウィンドウの上部にあるステータス バーがアニメーション化されます。 テストの実行の最後に、すべてのテスト メソッドに合格するとバーが緑色に変わり、いずれかのテストが失敗した場合は赤色に変わります。

    この場合、テストは失敗します。

  4. テスト エクスプローラーの でメソッドを選択すると、ウィンドウの下部に詳細が表示されます。

コードを修正してテストを再実行する

テスト結果には、エラーを説明するメッセージが含まれています。 このメッセージを表示するには、ドリルダウンが必要な場合があります。 AreEqual メソッドの場合、メッセージには、想定された内容と実際に受信された内容が表示されます。 残高が減少することが予想されましたが、代わりに引き出しの量によって増加しました。

単体テストにより、引き出し額を "減算" する必要があるときに口座残高に "加算" されるというバグが見つかりました。

バグを修正する

エラーを修正するには、BankAccount.cs ファイルで次の行を置き換えます。

m_balance += amount;

次の内容に置き換えます。

m_balance -= amount;

テストを再実行する

テスト エクスプローラーで、[すべて実行] を選択してテストを再実行します (または、Ctrl キー + 押しながら RVを押します)。 赤/緑のバーが緑色に変わり、テストが成功したことを示します。

成功したテストTest Explorer in Visual Studio 2019 showing passed testTest Explorer in Visual Studio 2019 showing passed testを示す Visual Studio 2019 のテスト エクスプローラー

成功したテストTest Explorer in Visual Studio 2019 showing passed testTest Explorer in Visual Studio 2019 showing passed testを示す Visual Studio 2019 のテスト エクスプローラー

単体テストを使用してコードを改善する

このセクションでは、分析、単体テストの開発、リファクタリングの反復プロセスを使用して、運用コードの堅牢性と効果を高める方法について説明します。

問題を分析する

Debit メソッドで有効な金額が正しく差し引かれていることを確認するテスト メソッドを作成しました。 引き落とし金額が次のいずれかである場合、メソッドが ArgumentOutOfRangeException をスローすることを確認します。

  • 残高よりも大きい、または
  • ゼロ未満。

新しいテスト メソッドを作成して実行する

借方金額が 0 未満の場合に正しい動作を確認するテスト メソッドを作成します。

[TestMethod]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act and assert
    Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => account.Debit(debitAmount));
}

ThrowsException メソッドを使用して、正しい例外がスローされたことをアサートします。 このメソッドを使用した場合、ArgumentOutOfRangeException がスローされない限り、テストは失敗します。 引き落とし金額が 0 未満の場合、より汎用的な ApplicationException をスローするようにテスト対象のメソッドを一時的に変更すると、テストは正しく動作します。つまり失敗します。

引き出された金額が残高より大きい場合にケースをテストするには、次の手順を実行します。

  1. Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRangeという名前の新しいテスト メソッドを作成します。

  2. メソッド本体を Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange から新しいメソッドにコピーします。

  3. debitAmount を残高より大きい数値に設定します。

2 つのテストを実行し、合格したことを確認します。

分析を続行する

テスト中の方法をさらに改善できます。 現在の実装では、テスト中に例外がスローされた原因となった条件 (amount > m_balance または amount < 0) を把握する方法はありません。 メソッドのどこかで ArgumentOutOfRangeException がスローされたことしかわかりません。 BankAccount.Debit のどの条件によって例外がスローされるか確認できれば (amount > m_balance または amount < 0)、メソッドが引数を正しくチェックしていることを確信できます。

テスト中のメソッド (BankAccount.Debit) をもう一度見て、両方の条件付きステートメントで、引数の名前をパラメーターとして受け取る ArgumentOutOfRangeException コンストラクターが使用されていることに注意してください。

throw new ArgumentOutOfRangeException("amount");

より豊富な情報を報告するコンストラクターがあります。ArgumentOutOfRangeException(String, Object, String) には、引数の名前、引数の値、およびユーザー定義メッセージが含まれます。 テスト対象のメソッドをリファクタリングして、このコンストラクターを使用できます。 さらに、公開されている型メンバーを使用してエラーを指定することもできます。

テスト対象のコードをリファクタリングする

まず、クラス スコープでエラー メッセージの 2 つの定数を定義します。 テスト対象のクラス、BankAccountに定義を配置します。

public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";

次に、Debit メソッドの 2 つの条件付きステートメントを変更します。

if (amount > m_balance)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}

if (amount < 0)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}

テスト メソッドをリファクタリングする

Assert.ThrowsExceptionの呼び出しを削除して、テスト メソッドをリファクタリングします。 Debit() ブロックで try/catch の呼び出しをラップし、予期される特定の例外をキャッチし、関連するメッセージを確認します。 Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert.Contains メソッドは、2 つの文字列を比較する機能を提供します。

これで、Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange は次のようになります。

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
    }
}

再テスト、書き換え、再分析

現時点では、テスト メソッドは必要なすべてのケースを処理するわけではありません。 テスト対象のメソッドである Debit メソッドが、ArgumentOutOfRangeException が残高より大きい (または 0 未満) 場合に debitAmount をスローできなかった場合、テスト メソッドは合格します。 例外がスローされない場合にテスト メソッドを失敗させる必要があるため、このシナリオは適していません。

この結果は、テスト メソッドのバグです。 この問題を解決するには、テスト メソッドの最後に Assert.Fail アサートを追加して、例外がスローされないケースを処理するようにします。

テストを再実行すると、正しい例外がキャッチされた場合にテストが失敗したことが示されます。 catch ブロックは例外をキャッチしますが、メソッドは引き続き実行され、新しい Assert.Fail アサートで失敗します。 この問題を解決するには、return ブロックの StringAssert の後に catch ステートメントを追加します。 テストを再実行すると、この問題が修正されたことを確認できます。 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange の最終バージョンは次のようになります。

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
        return;
    }

    Assert.Fail("The expected exception was not thrown.");
}

結論

テスト コードの改善により、より堅牢で有益なテスト メソッドが得られた。 しかし、さらに重要なのは、テスト対象のコードも改善された点です。

ヒント

この記事では、マネージド コード用の Microsoft 単体テスト フレームワークを使用します。 テスト エクスプローラー は、テスト エクスプローラー用のアダプターを持つサードパーティの単体テスト フレームワークからテストを実行することもできます。 詳細については、「サードパーティの単体テスト フレームワークインストールする」を参照してください。

コマンド ラインからテストを実行する方法については、「コマンド ライン オプション VSTest.Console.exe 参照してください。