Учебник. Проверка настраиваемой задачи

Вы можете использовать функциональные возможности модульного тестирования в Visual Studio, чтобы проверить настраиваемую задачу MSBuild перед выпуском и обеспечить правильность кода. Сведения о преимуществах выполнения тестов и основных средствах для проверки см. в статье Основные сведения о модульных тестах. Этот учебник включает примеры кода, которые используются в других учебниках по настраиваемым задачам MSBuild. Следующие проекты, используемые в этих учебниках, доступны в GitHub и включают модульные и интеграционные тесты для настраиваемых задач MSBuild:

Модульный тест

Настраиваемая задача MSBuild — это класс, который наследуется от Task (прямо или косвенно, потому что ToolTask наследует от Task). Метод, выполняющий действия, связанные с задачей, — Execute(). Этот метод принимает некоторые входные значения (параметры) и имеет выходные параметры, которые можно использовать для проверочного утверждения, чтобы проверить допустимость. В этом случае некоторые входные параметры являются путями к файлам, поэтому в этом примере входные файлы для теста находятся в папке с именем Resources. Эта задача MSBuild также создает файлы, поэтому тест выполняет проверяет созданные файлы.

Требуется подсистема сборки, которая является классом, реализующим IBuildEngine. В этом примере есть макет, который использует Moq, но можно использовать и другие средства макетирования. В примере собираются ошибки, но можно собрать другие сведения и затем проверить их.

Макет Engine необходим для всех тестов, поэтому он включается как TestInitialize (выполняется перед каждым тестом, и в каждом тесте есть собственная подсистема сборки).

Полный код см. в разделе AppSettingStronglyTypedTest.cs в репозитории примеров .NET на GitHub.

  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. Другие тесты следуют этому шаблону.

Примечание.

При создании файлов необходимо использовать разные имена файлов для каждого теста, чтобы избежать конфликтов. Не забудьте удалить созданные файлы в ходе очистки теста.

Интеграционные тесты

Модульные тесты важны, но настраиваемую задачу MSBuild также необходимо протестировать в реалистичном контексте сборки.

Класс System.Diagnostics.Process предоставляет доступ к локальным и удаленным процессам и позволяет запускать и останавливать локальные системные процессы. В этом примере выполняется сборка для модульного теста с помощью файлов теста 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. Аргумент теста содержит инструкции по сборке этого файла 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.