Share via


チュートリアル: カスタム タスクをテストする

コードの正確性を確認するために、Visual Studio の単体テスト機能を使って、配布前に MSBuild カスタム タスクをテストできます。 テストを行う利点と基本的なテスト ツールの詳細については、単体テストの基本に関する記事を参照してください。 このチュートリアルでは、他の MSBuild カスタム タスク チュートリアルで使われたコード例を使います。 それらのチュートリアルで使われているプロジェクトは GitHub で公開されており、MSBuild カスタム タスクの単体テストと統合テストが含まれています。

単体テスト

MSBuild カスタム タスクは (ToolTaskTaskを継承しているため、直接的または間接的に) Task を継承したクラスです。 タスクに関連付けられているアクションを実行するメソッドは Execute() です。 このメソッドはいくつかの入力値 (パラメーター) を受け取り、assert を使って有効性をテストできる出力パラメーターを持っています。 この場合、一部の入力パラメーターはファイルへのパスなので、この例では Resources という名前のフォルダーにテスト入力ファイルが含まれています。 この MSBuild タスクはファイルの生成も行いますので、テストでは生成されたファイルをアサートします。

ビルド エンジンが必要で、これは IBuildEngine を実装したクラスです。 この例では Moq を使ったモックがありますが、他のモック ツールを使うこともできます。 この例ではエラーを収集していますが、他の情報を収集し、それをアサートすることもできます。

Engine モックはすべてのテストで必要なので、TestInitialize として含まれています。(各テストの前に実行され、各テストには独自のビルド エンジンがあります)。

完全なコードは、GitHub の .NET サンプル リポジトリにある AppSettingStronglyTypedTest.cs を参照してください。

  1. タスクを作成し、テストの配置の一部としてパラメーターを設定します。

        private Mock<IBuildEngine> buildEngine;
        private List<BuildErrorEventArgs> errors;
    
         [TestInitialize()]
         public void Startup()
         {
             buildEngine = new Mock<IBuildEngine>();
             errors = new List<BuildErrorEventArgs>();
             buildEngine.Setup(x => x.LogErrorEvent(It.IsAny<BuildErrorEventArgs>())).Callback<BuildErrorEventArgs>(e => errors.Add(e));
         }
    
  2. ITaskItem パラメーター モックを作成し (Moq を使用)、解析するファイルを指定します。 次に、AppSettingStronglyTyped カスタム タスクとそのパラメーターを作成します。 それから、ビルド エンジンを MSBuild カスタム タスクに設定します。

    //Arrange
    var item = new Mock<ITaskItem>();
    item.Setup(x => x.GetMetadata("Identity")).Returns($".\\Resources\\complete-prop.setting");
    
    var appSettingStronglyTyped = new AppSettingStronglyTyped { SettingClassName = "MyCompletePropSetting", SettingNamespaceName = "MyNamespace", SettingFiles = new[] { item.Object } };
    
    appSettingStronglyTyped.BuildEngine = buildEngine.Object;
    

    次に、タスク コードを実行して、実際のタスク アクションを実行します。

     //Act
     var success = appSettingStronglyTyped.Execute();
    
  3. 最後に、テストの予期される結果をアサートします。

    //Assert
    Assert.IsTrue(success); // The execution was success
    Assert.AreEqual(errors.Count, 0); //Not error were found
    Assert.AreEqual($"MyCompletePropSetting.generated.cs", appSettingStronglyTyped.ClassNameFile); // The Task expected output
    Assert.AreEqual(true, File.Exists(appSettingStronglyTyped.ClassNameFile)); // The file was generated
    Assert.IsTrue(File.ReadLines(appSettingStronglyTyped.ClassNameFile).SequenceEqual(File.ReadLines(".\\Resources\\complete-prop-class.txt"))); // Assenting the file content
    
  4. 他のテストもこのパターンに従い、あり得ることすべてに対応します。

Note

生成されたファイルがある場合は、衝突を避けるため、テストごとに異なるファイル名を使う必要があります。 生成されたファイルは、必ずテストのクリーンアップとして削除してください。

統合テスト

単体テストは重要ですが、現実的なビルド コンテキストでカスタム MSBuild タスクをテストする必要もあります。

System.Diagnostics.Process Class はローカル プロセスとリモート プロセスへのアクセスを提供して、ローカル システム プロセスの起動と停止ができるようにします。 この例では、テスト MSBuild ファイルを使って単体テストでビルドを実行します。

  1. テスト コードでは、各テストの実行コンテキストを初期化する必要があります。 dotnet コマンドのパスが利用する環境に適したものであるように、注意を払ってください。 完全な例は、ここにあります。

         public const string MSBUILD = "C:\\Program Files\\dotnet\\dotnet.exe";
    
         private Process buildProcess;
         private List<string> output;
    
         [TestInitialize()]
         public void Startup()
         {
             output = new List<string>();
             buildProcess = new Process();
             buildProcess.StartInfo.FileName = MSBUILD;
             buildProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
             buildProcess.StartInfo.CreateNoWindow = true;
             buildProcess.StartInfo.RedirectStandardOutput = true;
         }
    
  2. クリーンアップの際には、テストでプロセスを完了する必要があります。

        [TestCleanup()]
         public void Cleanup()
         {
             buildProcess.Close();
         }
    
  3. ここで、各テストを作成します。 各テストの実行には、独自の MSBuild ファイル定義が必要です。 たとえば、testscript-success.msbuild のようになります。 このファイルを理解するには、「チュートリアル: カスタム タスクを作成する」を参照してください。

    <Project Sdk="Microsoft.NET.Sdk">
        <UsingTask TaskName="AppSettingStronglyTyped.AppSettingStronglyTyped" AssemblyFile="..\AppSettingStronglyTyped.dll" />
        <PropertyGroup>
            <TargetFramework>netstandard2.1</TargetFramework>
        </PropertyGroup>
    
        <PropertyGroup>
            <SettingClass>MySettingSuccess</SettingClass>
            <SettingNamespace>example</SettingNamespace>
        </PropertyGroup>
    
        <ItemGroup>
            <SettingFiles Include="complete-prop.setting" />
        </ItemGroup>
    
        <Target Name="generateSettingClass">
            <AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)">
                <Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" />
            </AppSettingStronglyTyped>
        </Target>
    </Project>
    
  4. test の引数には、この MSBuild ファイルをビルドするための命令を指定します。

     //Arrange
     buildProcess.StartInfo.Arguments = "build .\\Resources\\testscript-success.msbuild /t:generateSettingClass";
    
  5. 実行して、出力を表示させます。

    //Act
    ExecuteCommandAndCollectResults();
    

    ExecuteCommandAndCollectResults() は次のように定義されます。

    private void ExecuteCommandAndCollectResults()
    {
         buildProcess.Start();
         while (!buildProcess.StandardOutput.EndOfStream)
         {
             output.Add(buildProcess.StandardOutput.ReadLine() ?? string.Empty);
         }
         buildProcess.WaitForExit();
    }
    
  6. 最後に、結果が予期したものか評価します。

    //Assert
    Assert.AreEqual(0, buildProcess.ExitCode); //Finished success
    Assert.IsTrue(File.Exists(".\\Resources\\MySettingSuccess.generated.cs")); // the expected resource was generated
    Assert.IsTrue(File.ReadLines(".\\Resources\\MySettingSuccess.generated.cs").SequenceEqual(File.ReadLines(".\\Resources\\testscript-success-class.txt"))); // asserting the file content
    

まとめ

単体テストは、コードのそれぞれの特定部分の正しさを確保するためにコードをテストしデバッグできるので便利ですが、現実的なビルド コンテキストでタスクが実行されることを確実にするために、統合テストを行うことが重要です。 このチュートリアルでは、MSBuild カスタム タスクをテストする方法について説明しました。

次のステップ

REST API コード生成を行う、より複雑なカスタム タスクを作成します。