Aus äh… historischen Gründen ist es so, dass eines unserer C#-Projekte sowohl NUnit als auch xUnit für die Unittests nutzt. Über Sinn und Zweck dieser Konfiguration brauchen wir nicht zu diskutieren – Fakt ist, dass sich das nicht auf die Schnelle ändern lässt.
Ich wollte nun dieses Projekt in TeamCity integrieren, so dass sowohl die Tests von NUnit als auch von xUnit ausgeführt werden. xUnit muss in TeamCity zwangsweise über ein MSBuild-Script integriert werden, was aber einfach zu bewerkstelligen ist. NUnit wird hingegen von TeamCity nativ unterstützt.
Mein erster Ansatz war nun, für den Buildprozess zwei Buildsteps anzulegen. Einmal für NUnit (nativ), einmal für xUnit (msbuild). Problem dabei: wenn einer der beiden Buildsteps fehl schlägt, wird der nächste Buildstep ebenfalls nicht ausgeführt. Das bedeutet also, dass man mit einem Buildlauf nicht die komplette Fehlerliste enthält, sondern immer nur die, des gerade laufenden Unittest-Frameworks (treten Fehler erst im zweiten Buildstep auf, ist dies natürlich egal).
“Schuld” an diesem Verhalten ist die Option Fail build if: at least one test failed. Ist diese nämlich aktiviert (Standardeinstellung), werden nachfolgende Buildsteps beim ersten auftretenden Fehler nicht mehr ausgeführt – was ja auch durchaus Sinn macht. Deaktiviert man die Option hingegen, werden alle Buildsteps ausgeführt, auch wenn in den Unittests Fehler aufgetreten sind. Problem dabei: Der Build wird auch bei aufgetretenen Fehlern in den Unittests grün mit “OK” markiert. Somit bekommt bei fehlgeschlagenen Unittests keiner eine Benachrichtigung über etwaige Fehler.
Ich entschloss mich zu folgendem Ansatz, der mit (N)Ant auf jeden Fall funktioniert hätte: Unser Test-Teilprojekt bekommt ein eigenes MSBuild-File, dass nur die Targets für das Ausführen der NUnit- bzw. xUnit-Tests enthält. Glücklicherweise gibt es von der .NET-Community bereits einige vorgefertigte Tasks, die das bewerkstelligen.
Zuerst müssen die MSBuild Community Tasks installiert werden (letztes Release). Dass die Dependencies zu den Unittest-Frameworks (xunit.runner.msbuild.dll) im Projektverzeichnis vorhanden sind, setze ich einfach mal voraus. Im Projekt mit den ganzen Unittests wird nun eine neue .msbuild-Datei mit folgendem Inhalt erstellt:
<Project DefaultTargets="Test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask AssemblyFile="$(MSBuildProjectDirectory)..DependenciesLibsxunit.runner.msbuild.dll" TaskName="Xunit.Runner.MSBuild.xunit" /> <Import Project="$(MSBuildExtensionsPath)MSBuildCommunityTasksMSBuild.Community.Tasks.Targets"/> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> </PropertyGroup> <ItemGroup> <CompiledAssemblies Include="$(MSBuildProjectDirectory)bin$(Configuration)YourTestProject.dll" /> </ItemGroup> <Target Name="Build"> <MSBuild Projects="YourTestProject.csproj" Targets="Build" Properties="Configuration=$(Configuration)"></MSBuild> </Target> <Target Name="Test-xUnit"> <xunit assembly="@(CompiledAssemblies)" ContinueOnError="true" /> </Target> <Target Name="Test-NUnit"> <NUnit Assemblies="@(CompiledAssemblies)" ContinueOnError="true" /> </Target> <Target Name="TestOnly" DependsOnTargets="Test-xUnit;Test-NUnit"> </Target> <Target Name="Test" DependsOnTargets="Build;Test-xUnit;Test-NUnit"> </Target> </Project>
Das gesamte Projekt ist so strukturiert, dass verschiedene Unterprojekte in die Solution integriert sind. Das Test-Projekt ist abhängig von den anderen Projekten. Diese Informationen liegen in YourTestProject.csproj und hängen natürlich von den Projektgegebenheiten ab.
Im UsingTask-Tag muss noch der korrekte Pfad zur xunit.runner.msbuild.dll hinterlegt werden.
Wenn nun das Target Test bzw. TestOnly aufgerufen wird, werden beide Unittesting-Frameworks aufgerufen. Wenn eines von beiden fehl schlägt, wird trotzdem weitergermacht (ContinueOnError=”true”).
Weiter geht es: Als erster bzw. zweiter Buildstep wird nun Test bzw. TestOnly als Target aufgerufen. Sieht dann so aus:
Sobald nun der Build ausgeführt wird, werden die Ergebnisse von xUnit und NUnit aggregiert und als ein Ergebnis dargestellt. Das bedeutet also, dass es keinen Unterschied mehr macht, aus welchem Framework die Tests stammen. Sollte nun ein Test fehlschlagen, werden alle anderen Tests trotzdem ausgeführt und es findet kein Abbruch statt. Mission accomplished!
0 Comments