Tutorial: Testen eines benutzerdefinierten Tasks
Sie können Komponententestfunktionen in Visual Studio verwenden, um einen benutzerdefinierten MSBuild-Task vor dem Verteilen zu testen, um die Richtigkeit des Codes sicherzustellen. Informationen zu den Vorteilen von Tests und grundlegenden Testtools finden Sie unter Grundlagen zu Komponententests. In diesem Tutorial verwenden Sie die Codebeispiele, die in anderen Tutorials zu benutzerdefinierten MSBuild-Tasks verwendet werden. Die folgenden Projekte, die in diesen Tutorials verwendet werden, sind auf GitHub verfügbar und enthalten Komponenten- und Integrationstests für MSBuild-Tasks:
Komponententest
Ein benutzerdefinierter MSBuild-Task ist eine Klasse, die von Task erbt (direkt oder indirekt, weil ToolTask von Task erbt). Die Methode, die die mit dem Task verbundenen Aktionen durchführt, ist Execute()
. Diese Methode nimmt einige Eingabewerte (Parameter) an und verfügt über Ausgabeparameter, die Sie mit „assert“ auf ihre Gültigkeit prüfen können. In diesem Fall sind einige Eingabeparameter Pfade zu Dateien, sodass in diesem Beispiel Testeingabedateien in einem Ordner namens Resources enthalten sind. Dieser MSBuild-Task generiert auch Dateien, daher bestätigt der Test die generierten Dateien.
Eine Build-Engine ist erforderlich, bei der es sich um eine Klasse handelt, die IBuildEngine implementiert. In diesem Beispiel wird Mocking (Moq) verwendet, aber Sie können auch andere Mockingtools verwenden. Das Beispiel erfasst die Fehler, aber Sie können auch andere Informationen erfassen und diese dann bestätigen.
Das Pseudoobjekt Engine
wird für alle Tests benötigt, daher ist es als TestInitialize
eingebunden (es wird vor jedem Test ausgeführt, und jeder Test hat eine eigene Build-Engine).
Den vollständigen Code finden Sie unter AppSettingStronglyTypedTest.cs im .NET-Beispielrepository auf GitHub.
Erstellen Sie den Task, und legen Sie die Parameter als Teil der Testanordnung fest:
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)); }
Erstellen Sie das ITaskItem-Parameterpseudoobjekt (mit Moq), und verweisen Sie auf die zu analysierende Datei. Erstellen Sie dann den benutzerdefinierten
AppSettingStronglyTyped
-Task mit seinen Parametern. Legen Sie abschließend die Build-Engine auf den benutzerdefinierten MSBuild-Task fest://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;
Führen Sie dann den Taskcode aus, um die eigentliche Taskaktion auszuführen:
//Act var success = appSettingStronglyTyped.Execute();
Abschließend können Sie das erwartete Ergebnis des Tests bestätigen:
//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
Die anderen Tests folgen diesem Muster und erweitern alle Möglichkeiten.
Hinweis
Wenn Dateien generiert werden, müssen Sie für jeden Test einen anderen Dateinamen verwenden, um Konflikte zu vermeiden. Denken Sie daran, die generierten Dateien bei der Testbereinigung zu löschen.
Integrationstests
Komponententests sind wichtig, aber Sie müssen auch den benutzerdefinierten MSBuild-Task in einem realistischen Buildkontext testen.
System.Diagnostics.Process Class ermöglicht Zugriff auf lokale Prozesse und Remoteprozesse und das Starten und Beenden lokaler Systemprozesse. In diesem Beispiel wird ein Build für einen Komponententest mithilfe von MSBuild-Testdateien ausgeführt.
Der Testcode muss den Ausführungskontext für jeden Test initialisieren. Achten Sie darauf, dass der Pfad zum
dotnet
-Befehl für Ihre Umgebung richtig ist. Das vollständige Beispiel finden Sie hier.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; }
Bei der Bereinigung muss der Test den Prozess abschließen:
[TestCleanup()] public void Cleanup() { buildProcess.Close(); }
Erstellen Sie nun jeden einzelnen Test. Jeder Test benötigt eine eigene MSBuild-Dateidefinition, um ausgeführt zu werden. Beispiel: testscript-success.msbuild. Informationen zur Datei finden Sie unter Tutorial: Erstellen eines benutzerdefinierten Tasks.
<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>
Das Testargument enthält die Anweisungen zum Erstellen dieser MSBuild-Datei:
//Arrange buildProcess.StartInfo.Arguments = "build .\\Resources\\testscript-success.msbuild /t:generateSettingClass";
Führen Sie die Datei aus, und rufen Sie die Ausgabe ab:
//Act ExecuteCommandAndCollectResults();
Dabei wird
ExecuteCommandAndCollectResults()
wie folgt definiert:private void ExecuteCommandAndCollectResults() { buildProcess.Start(); while (!buildProcess.StandardOutput.EndOfStream) { output.Add(buildProcess.StandardOutput.ReadLine() ?? string.Empty); } buildProcess.WaitForExit(); }
Bestätigen Sie abschließend das erwartete Ergebnis:
//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
Schlussbemerkung
Komponententests sind nützlich, da Sie den Code testen und debuggen können, um die Richtigkeit jedes einzelnen Codeabschnitts sicherzustellen. Integrationstests sind jedoch wichtig, um sicherzustellen, dass der Task in einem realistischen Buildkontext ausgeführt wird. In diesem Tutorial haben Sie gelernt, wie Sie einen benutzerdefinierten MSBuild-Task testen.
Nächste Schritte
Erstellen Sie einen komplexeren benutzerdefinierten Task, der die REST-API-Codegenerierung übernimmt.