Share via


Esercitazione: Testare un'attività personalizzata

È possibile usare la funzionalità di unit test in Visual Studio per testare un'attività personalizzata di MSBuild prima della distribuzione per garantire la correttezza del codice. Per informazioni sui vantaggi dell'esecuzione di test e degli strumenti di test di base, vedere Nozioni di base sugli unit test. In questa esercitazione vengono usati gli esempi di codice usati in altre esercitazioni sulle attività personalizzate di MSBuild. I progetti seguenti usati in queste esercitazioni sono disponibili in GitHub e includono unit test e test di integrazione per le attività personalizzate di MSBuild:

Unit test

Un'attività personalizzata di MSBuild è una classe che eredita da Task (direttamente o indirettamente, perché ToolTask eredita da Task). Il metodo che esegue le azioni associate all'attività è Execute(). Questo metodo accetta alcuni valori di input (parametri) e include parametri di output che è possibile usare assert per testare la validità. In questo caso, alcuni parametri di input sono percorsi ai file, quindi in questo esempio sono presenti file di input di test in una cartella denominata Resources. Questa attività MSBuild genera anche file, quindi il test asserisce i file generati.

È necessario un motore di compilazione, ovvero una classe che implementa IBuildEngine. In questo esempio è presente una simulazione usando Moq, ma è possibile usare altri strumenti fittizi. Nell'esempio vengono raccolti gli errori, ma è possibile raccogliere altre informazioni e quindi asserirla.

La Engine simulazione è necessaria in tutti i test, quindi viene inclusa come TestInitialize (viene eseguita prima di ogni test e ogni test ha un proprio motore di compilazione).

Per il codice completo, vedere AppSettingStronglyTypedTest.cs nel repository di esempi .NET in GitHub.

  1. Creare l'attività e impostare i parametri come parte della disposizione dei test:

        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. Creare il ITaskItem parametro mock (usando Moq) e puntare al file da analizzare. Creare quindi l'attività AppSettingStronglyTyped personalizzata con i relativi parametri. Impostare infine il motore di compilazione sull'attività personalizzata 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;
    

    Eseguire quindi il codice dell'attività per eseguire l'azione effettiva dell'attività:

     //Act
     var success = appSettingStronglyTyped.Execute();
    
  3. Infine, asserire il risultato previsto del test:

    //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. Gli altri test seguono questo modello ed espandono tutte le possibilità.

Nota

Quando sono presenti file generati, è necessario usare un nome di file diverso per ogni test per evitare conflitti. Ricordarsi di eliminare i file generati come pulizia dei test.

Test di integrazione

Gli unit test sono importanti, ma è anche necessario testare l'attività MSBuild personalizzata in un contesto di compilazione realistico.

La classe System.Diagnostics.Process fornisce l'accesso ai processi locali e remoti e consente di avviare e arrestare i processi di sistema locali. Questo esempio esegue una compilazione su uno unit test usando file MSBuild di test.

  1. Il codice di test deve inizializzare il contesto di esecuzione per ogni test. Prestare attenzione per assicurarsi che il percorso del comando sia accurato per l'ambiente dotnet in uso. L'esempio completo è riportato qui.

         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. Al termine della pulizia, il test deve completare il processo:

        [TestCleanup()]
         public void Cleanup()
         {
             buildProcess.Close();
         }
    
  3. Creare ora ogni test. Per ogni test sarà necessaria una propria definizione di file MSBuild da eseguire. Ad esempio testscript-success.msbuild. Per comprendere il file, vedere Esercitazione: Creta un'attività personalizzata.

    <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. L'argomento test fornisce le istruzioni per compilare questo file MSBuild:

     //Arrange
     buildProcess.StartInfo.Arguments = "build .\\Resources\\testscript-success.msbuild /t:generateSettingClass";
    
  5. Eseguire e ottenere l'output:

    //Act
    ExecuteCommandAndCollectResults();
    

    Dove ExecuteCommandAndCollectResults() è definito come:

    private void ExecuteCommandAndCollectResults()
    {
         buildProcess.Start();
         while (!buildProcess.StandardOutput.EndOfStream)
         {
             output.Add(buildProcess.StandardOutput.ReadLine() ?? string.Empty);
         }
         buildProcess.WaitForExit();
    }
    
  6. Infine, valutare il risultato previsto:

    //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
    

Conclusione

Gli unit test sono utili perché è possibile testare ed eseguire il debug del codice per garantire la correttezza di ogni parte di codice specifica, ma è importante disporre di test di integrazione per garantire che l'attività venga eseguita in un contesto di compilazione realistico. In questa esercitazione si è appreso come testare un'attività personalizzata di MSBuild.

Passaggi successivi

Creare un'attività personalizzata più complessa che esegue la generazione di codice API REST.