Unit Tests für Pipeline Komponente mit Hilfe von FakeItEasy & FluentAssertion

01.03.2018

1. Einleitung

In diesem Artikel veranschaulichen wir, wie mit Hilfe der Nuget-Pakete “FakeItEasy” und “FluentAssertion” einfache und gut lesbare Unit Tests geschrieben werden können. Als Grundlage wird das Beispielprojekt einer Pipelinekomponente verwendet, die den Namespace eines eingehenden XML Dokumentes verändert. Zu finden ist dieses Beispiel, ohne die hier entwickelten Unit Tests, hier:

https://blogs.msdn.microsoft.com/brajens/2006/11/25/how-to-develop-biztalk-custom-pipeline-components-part1/

 

Dieser Artikel beschreibt ausschließlich die Entwicklung lesbarer Unit Tests und geht nicht auf deren Durchführung ein. Für diesen Artikel wurde „Visual Studio 2015“ (im weiteren Verlauf des Artikels kurz VS) verwendet.

 

2. Was ist „FakeItEasy“?

 

FakeItEasy ist ein Nuget Paket, womit man abstrakte Klassen oder Interface „faken“ kann, indem man die benötigten Methodenrückgaben, bzw. Attributrückgaben im Unit Test definiert. Weitere Informationen und die Dokumentation zu FakeItEasy findet gibt es hier: https://fakeiteasy.github.io/

 

3. Was ist „FluentAssertion“?

 

„FluentAssertion“ ist ein weiteres Nuget Paket, womit man die Asserts in einem UnitTest lesbar gestalten kann, z.B. kann man schreiben:

myString.Should().Be(„Hello World!“);

 

anstelle von:

Assert.Equals(„Hello World!“, myString);

 

Somit sind die Testmethoden im Allgemeinen besser zu lesen.

Hierfür gibt es recht viele verschiedene Pakete. In diesem Beispiel wurde das FluentAssertions Paket von Dennis Doomen verwendet. Alle weiteren Informationen und die Dokumentation dazu findet ihr hier: http://fluentassertions.com/

 

4. Erstellen eines Unit Test Projektes mit benötigten Referenzen

 

Nachdem das Beispiel Projekt herunter geladen, entpackt und mit VS geöffnet wurde, sollte man das .NET Framework auf Version 4.61 ändern und die Solution einmal vollständig bauen. Eventuell muss man die Referenz auf „Microsoft.BizTalk.Pipeline“ nochmal neu setzen. Nachdem die Solution einmal vollständig erfolgreich gebaut wurde, fügt man der Solution ein neues Projekt hinzu:

 

Rechte Maustaste auf die Solution und dann wie im Bild zu sehen auf „New Project…“ klicken:

 

 

 

Im darauf folgenden Dialog wählt man links unter Visual C# „Test“ aus und rechts dann „Unit Test Project“. Unten gibt man dem Projekt noch einen sprechenden Namen, z.B. „SampleGenPipelineComponent.Tests“ und bestätigt den Dialog mit „OK“.

 

 

In dem hier entstandenen Projekt sollte man die Erzeugte „UnitTest.cs“ umbenennen in z.B. „SampleGenPipelineComponentTests.cs“ und die Klasse ebenso. Hinzufügen der beiden Nuget Paket Referenzen. 

Um nun die beiden benötigten Nuget Paket hinzuzufügen, wird der Nuget Paketmanager auf dem Unit Test Projekt verwendet:

 

 

 

 

Somit können wir in unserem Unit Test Projekt auf die Funktionalitäten der beiden zusätzlichen Bibliotheken zugreifen.

 

5. Testklasse und Testmethode

 

Für einen ganz einfachen Test benötigt man zwei XML Dateien, eine Eingangs XML und eine XML Datei, welche das erwartete Ergebnis repräsentiert. Hierfür verwenden wir die MS Test „DeploymentItem“ Funktionalität, welche sicherstellt, dass in der abgekapselten Unit Tests Umgebung, die beiden XML Dateien an einen fest definierten Ort sind, auf dem der Unit Test auch die benötigten Rechte hat.

 

Zur Verwendung dieses Mechanismus, erzeugt man im Testprojekt einen neuen Ordner, in den man die beiden Dateien hinterlegt. Hier wird der Name „TestFiles“ für diesen Ordner verwendet:

 

 

 

Wichtig sind, wie auf dem Bild zusehen, die Eigenschaften der XML Datei. Die „Build Action“ muss auf „None“ stehen und „Copy to Output Directory“ muss „Copy if newer“ sein oder „Copy always“, damit die zwei Dateien in der Testausführung auch tatsächlich am gewünschten Ort zur Verfügung stehen. Die Struktur des Testprojektes sieht nun wie folgt aus:

 

 

 

Die app.config Datei sollte man nicht löschen, da diese recht wichtig ist wenn man mehrere Testdurchläufe auf eine Testmethode mit verschiedenen Testdaten haben möchte (Data driven unit tests).

 

6. Klassenkopf und Testmethodenkopf

 

Aus den oben genannten Informationen ergibt sich nun folgender Klassenkopf mit dem benötigten „TestContext“ und dem Methodenkopf der Test Methode:

   

[TestClass]

[DeploymentItem("TestFiles", "TestFiles")]

public class SampleGenPipelineComponentTests

{

    public TestContext TestContext { get; set; }

 

    [TestMethod]

    public void SimpleNamespaceChangeTest()

    {

 

Um nun den Zugriff auf den richtigen Pfad zu vereinfachen, wurde hier noch eine statische String Variable hinterlegt, die in der Klasseninitialisierung gefüllt wird. Somit sieht der Kopf dann wie folgt aus:

 

[TestClass]

[DeploymentItem("TestFiles", "TestFiles")]

public class SampleGenPipelineComponentTests

{

    public TestContext TestContext { get; set; }

 

    private static string TestFilesDirectoryPath;

 

    [ClassInitialize]

    public static void ClassInitialize(TestContext testContext)

    {

        TestFilesDirectoryPath = Path.Combine(testContext.TestDeploymentDir, "TestFiles");

    }

 

    [TestMethod]

 

    public void SimpleNamespaceChangeTest()

 

 

7. Unit Test Code

 

Die folgende Testmethode soll überprüfen, ob der Namespace der Eingangs XML Datei wie vorgegeben in der Ziel XML Datei geändert wurde. Der Code für die Testmethode :

Im ersten Teil des Codes deklarieren wir die benötigten Fake Variablen, für die wir unser verhalten bestimmen können. Dies wird mit „var variable =  A.Fake<Type>();“ getan.

Für die zu testende Pipeline Komponente brauchen wir einen Fake für IPipelineContext und IBaseMessage, welches die Parameter der Methode „Execute“ sind.

Danach wird eine Instanz der zu testenden Komponente erstellt und eine String Variable, welches unseren neuen Namespace beinhaltet:

 

    IBaseMessage fakeBaseMsg = A.Fake<IBaseMessage>();

    IPipelineContext fakePipelineContext = A.Fake<IPipelineContext>();

    SampleGenComponent sampleGenComponent = new SampleGenComponent();

    string newNamespace = "http://TheCorrectNewNamespace";

 

Die „Execute“ Methode braucht für die Verarbeitung einen gültigen Stream der „Source.xml“ von der Methode „GetOriginalDataStream()“. Diesen erzeugt man mit der privaten Methode getStream(string xmlFilePath):

 

private Stream getStream(string xmlFilePath)

{

    XDocument xDoc = XDocument.Load(xmlFilePath);

    MemoryStream stream = new MemoryStream();

    xDoc.Save(stream);

    stream.Position = 0L;

    stream.Seek(0L, System.IO.SeekOrigin.Begin);

    return stream;

}

 

Damit die Methode den benötigten Stream innerhalb der execute Methode bekommt, müssen wir dem Fake-Objekt hinterlegen, dass es diesen Stream zurückgeben soll, wenn sie aufgerufen wird:

 

    A.CallTo(() => fakeBaseMsg.BodyPart.GetOriginalDataStream())

        .Returns(getStream(Path.Combine(TestFilesDirectoryPath, "Source.xml")));

 

Zusätzlich müssen wir die Konfiguration der Komponente vornehmen:

 

sampleGenComponent.NewNameSpace = newNamespace;

 

Und bevor wir die Auswertungen fahren, die Methode mit unseren fake Objekten aufrufen:

 

IBaseMessage result = sampleGenComponent.Execute(fakePipelineContext, fakeBaseMsg);

 

Bei der Überprüfung der Ergebnisse kommt nun die Magie von FakeItEasy und FluentAssertions zu tragen. Mit Hilfe der FakeItEasy Ausdrücke können wir sicher gehen, dass die von uns gewünschten Methoden und Attribute aufgerufen wurden:

 

    A.CallTo(() => fakeBaseMsg.BodyPart.GetOriginalDataStream()).MustHaveHappened();

    A.CallTo(() => fakePipelineContext.ResourceTracker.AddResource(A<Stream>.Ignored)).MustHaveHappened();

    A.CallTo(() => fakeBaseMsg.Context.Promote(

        A<string>.That.Matches(s => s.Equals("MessageType")),

        A<string>.That.Matches(s => s.Equals(@"http://schemas.microsoft.com/BizTalk/2003/system-properties")),

        A<Object>.That.Matches(o => ((string)o).Equals("http://TheCorrectNewNamespace#Input"))))

        .MustHaveHappened();

 

FakeItEasy bringt bei der Schreibweise genauso gute Lesbarkeit mit, wie FluentAssertions. Für die normalen Asserts kann man FakeItEasy nicht verwenden, daher kommt hier Fluent-Assertions ins Spiel:

 

    result.Should().NotBeNull();

    result.BodyPart.Data.Should().BeOfType(typeof(MemoryStream));

    XDocument resultDoc = XDocument.Load(result.BodyPart.Data);

 

    resultDoc.Root.FirstAttribute.Value.Should().Be(newNamespace);

 

 

Fazit

 

Mit den hier gezeigten Methoden und der Dokumentationen kann man seine Pipeline Komponente nun ausgiebig Unit testen. Da man bei FakeItEasy bei Methoden-Aufrufen auch Exceptions werfen kann, ist es sogar einfach machbar, die Schlecht-Fälle zu testen. 

Und all dies mit einer sehr guten Lesbarkeit, wenig Aufwand und vor allem auch ohne einen echten BizTalk Server zu verwenden.

 

Stephan Kelso

Senior Developer 

 

 

zurück