BTS ALM Tests – Testen von Maps & Schemas

28.08.2018

Nachdem letzten Artikel zum Thema „Unit-Tests für Pipeline Komponente” haben wir uns entschieden, das Thema „Application Lifecycle Management” ein wenig ausgiebiger zu betrachten. Hierfür nutzen wir den ersten Artikel als Start für eine kleine Artikelserie zum Thema BizTalk Server 2016 & Application Lifecycle Management (BTS 2016 ALM).

Die Artikelserie wird sich in grob drei Abschnitte unterteilen. Der erste Abschnitt wird aus zwei bis drei Artikeln bestehen und sich mit dem Thema Testen von BizTalk Schnittstellen beschäftigen. Im zweiten Abschnitt der Serie werden wir auf die Themen „Continues Integration“ (CI) und „Continues Delivery“ (CD) eingehen. Und in unserem letzten Teil schreiben wir mögliche Anwendungsszenarien für das BTS ALM.


Ziel Solution Struktur nach diesem Artikel




 

Das Projekt PipelineComponents sowie „Tests.PipelineComponents“ sind die beiden, aus dem letzten Artikel entstandenen Projekte, allerdings umbenannt. Der QuiTecBtsAlm.snk liegt als file im Solution Ordner, wird in den Projekten nur als Link hinzugefügt und für das Signieren der Assembly verwendet.

 

Warum  sollte man Maps und Schemas überhaupt testen?

 

Als das Thema „Testen von BTS Maps & Schemas“ bei uns zur Sprache kam, stellte sich sofort die Frage, wieso man dies überhaupt tun sollte, wenn man die Maps & Schemas doch auch im VS mit rechter Maustaste -> Test Map / Test Schema auch testen kann. Die Antwort darauf ist simpel und untergliedert sich in zwei Bereiche:

 

1. Wenn man Unit-Tests für Maps & Schemas schreibt, macht man dies in der Regel wie bei „Test Map“ auch mit einer Eingangsdatei (XML oder TXT). Dann schaut man sich manuell das Ergebnis an und verifiziert es. Bei einem Unit-Test gibt es allerdings den Vorteil, dass die erstellten Eingangsdateien zentral im Source-Control mit abgelegt werden. Somit muss jeder Entwickler bei Änderungen nur die Tests ausführen, um festzustellen, ob er die bisherige Funktionalität der Map oder des Schemas kaputt gemacht hat. Dabei muss er nicht über das Fenster Eigenschaften erst die Eingangsdatei auswählen und eine Zieldatei vorgeben. Dies wurde im Unit-Test ja schon definiert. Außerdem ist dann auch zentral festgelegt, wie das Ergebnis auszusehen hat, beschrieben durch die Asserts in den Tests.

 

2. Bei existierenden Mapping- und Schema-Tests kann man ganz beruhigt Änderungen am bestehenden Code vornehmen und bekommt dann über wenige Klicks das Ergebnis der Tests (ein Klick im „Test-Explorer“ vom Visual Studio).

 

Daher ist es sinnvoll auch Maps & Schemas zu testen.


Unit-Tests für Schemas & Maps schreiben

Um unser BizTalk Projekt Unit Testen zu können, müssen wir bei den Projekteigenschaften noch eine Option einschalten.

Rechte Maustaste auf das Projekt -> „Properties“ -> links auf „Deployment“ -> „Enable Unit Testing“ auf „true“ stellen.

Nun muss man ein neues Projekt vom Typ „Unit Test“ hinzufügen:

 

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. „Tests.SimpleTransfer“ und bestätigt den Dialog mit „OK“.

 

 

 

Wir wollen unser Projekt „SimpleTransfer“ testen, welches noch die Projektordner „Mappings“, „Schemas“ & „Pipelines“ beinhaltet. In ihnen liegen autark die entsprechenden Sourcen. So gestaltet man die Struktur des Test Projektes ähnlich. Hierfür legt man nun zwei neue Projektordner in dem Unit-Test Projekt an:

„Schemas“ & „Mappings“.

 

Die automatisch erstellte „UnitTest.cs“ Klasse schieben wir im „Solution Explorer“ einfach in den Projektordner „Schemas“. Danach benennt man die .cs Datei über das Kontextmenü in „SourceSchemaTests.cs“ um. Die nun folgende Frage, ob man die Klasse auch umbenennen möchte, bestätigt man einfach mit 'Ja'.

 

Hinzufügen des Nuget Pakets „FluenAssertions“

Wie schon im ersten Artikel, wird hier das Nuget Paket „FluentAssertions“ von Dennis Doomen (
http://fluentassertions.com/) verwendet. Um nun das benötigte Nuget Paket hinzuzufügen, wird der Nuget Paketmanager auf dem Unit Test Projekt verwendet:







Somit kann in dem Unit Test Projekt auf die Funktionalität von „FluentAssertions“ zugegriffen werden.

 

Testklassen und Methoden

 

Da hier zwei Schemas und eine Map getestet werden, benötigt man zusätzlich zu der oben schon erzeugten Testklasse noch eine weitere. Sie wird mit dem Namen „TargetSchemaTests“ wie folgt angelegt:

Rechte Maustaste auf den „Schemas“ Ordner in unserem Testprojekt „Tests.SimpleTransfer\Schemas“ -> „Add“ -> „Unit Test…“

 

Für die Mapping Tests, legt man noch einen weiteren Projektordner mit dem Namen „Mappings“ an und erstellt innerhalb diesen eine Unit Testklasse mit dem Namen „Source2TargetTests.cs“

Hier verwendet man wieder das schon aus dem ersten Artikel bekannte „DeploymentItem“ in Verbindung mit dem „SimpleTransferXmlFiles“ Ordner innerhalb des Unit Test Projektes.

 

Diesmal wird hier für jedes Schema ein „Gut“ Test geschrieben und ein Test, bei dem erwartet wird, dass ein Fehler auftritt. Somit werden am Ende 4 „XML-Dateien“ für die Schema Tests und eine zusätzliche für die Map Tests in diesem Ordner liegen. Auch hier ist wieder darauf zu achten, dass in den Eigenschaften der erstellten XML-Dateien „Build Action“ auf „None“ und „Copy to Output Directory“ auf „Copy if newer“ oder „Copy always“ konfiguriert ist.

Die App.config, welche beim Erstellen des Unit Test Projektes mit generiert wird, lässt man wieder stehen.


Wichtig für die Map Tests:

 

Da die Unit Tests für Maps unbedingt in x86 ausgeführt werden müssen, benötigt man noch eine “TestSettings” Datei, welche der Solution mit “Add New Item” hinzugefügt werden kann. Zusätzlich muss die “Microsoft.BizTalk.TOM.dll” für die Tests in den entsprechenden Order deployed werden, diese liegt hier in dem Ordner „libRepository“ und wird in den TestSettings als DeploymentItem definiert.



4.1 Klassenköpfe

Die Klassenköpfe für die die Unit Testklassen sehen wie folgt aus:

01        [TestClass]

02        [DeploymentItem("SimpleTransferXmlFiles", "SimpleTransferXmlFiles")]

03        public class SourceSchemaTests

04        {

05        public TestContext TestContext { get; set; }

06        private static string testFilesDirectoryPath;

07        private SourceSchema schemaToTest;

08        [ClassInitialize]

09        public static void TestClassInitialize(TestContext testContext)

10        {

11            testFilesDirectoryPath = Path.Combine(testContext.TestDeploymentDir,

    "SimpleTransferXmlFiles");

12        }

13        [TestInitialize]

14        public void TestInitialize()

15        {

16            this.schemaToTest = new SourceSchema();

 

17        }


Je nach Testklasse ändern sich folgende Zeilen:

 

  • 03 Der Klassenname
  • 07 Hier muss jeweils die richtige Klasse verwendet werden, also „SourceSchema“, „TargetScheme“ oder „Source_2_Target“. Für die Mapping Testklasse ändern wir den Variablennamen in „MapToTest“.
  • 16 Auch hier muss dann die richtige Instanz erzeugt werden wie schon in Zeile 07


Schema Tests

 

Die zu testenden Schemas sehen wie folgt aus:

SourceSchema.xsd:

 

 

 

<?xml version="1.0" encoding="utf-16"?>

<xs:schema
xmlns="Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.SourceSchema"
xmlns:b="schemas.microsoft.com/BizTalk/2003"
targetNamespace="Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.SourceSchema"
xmlns:xs="www.w3.org/2001/XMLSchema">

  <xs:element name="SourcePerson">

    <xs:complexType>

      <xs:sequence maxOccurs="unbounded">

        <xs:element minOccurs="1" maxOccurs="1" name="SirName" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="LastName" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="ZipCode" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="City" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="Strret" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="Country" type="xs:string" />

      </xs:sequence>

    </xs:complexType>

  </xs:element>

 

</xs:schema>

 

TargetSchema.xsd

<?xml version="1.0" encoding="utf-16"?>

<xs:schema xmlns="Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.TargetSchema"
xmlns:b="schemas.microsoft.com/BizTalk/2003"
targetNamespace="Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.TargetSchema"
xmlns:xs="www.w3.org/2001/XMLSchema">

  <xs:element name="TargetPerson">

    <xs:complexType>

      <xs:sequence>

        <xs:element minOccurs="1" maxOccurs="1" name="SirName" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="LastName" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="FullName" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="1" name="Initials" type="xs:string" />

        <xs:element minOccurs="1" maxOccurs="unbounded" name="Adress">

          <xs:complexType>

            <xs:sequence>

              <xs:element minOccurs="1" maxOccurs="1" name="Street" type="xs:string" />

              <xs:element minOccurs="1" maxOccurs="1" name="ZipCode" type="xs:string" />

              <xs:element minOccurs="1" maxOccurs="1" name="City" type="xs:string" />

              <xs:element minOccurs="1" maxOccurs="1" name="Country" type="xs:string" />

            </xs:sequence>

          </xs:complexType>

        </xs:element>

      </xs:sequence>

    </xs:complexType>

  </xs:element>

 

</xs:schema>


Testmethoden für SourceSchema

Hierfür gibt es zwei Test XML-Dateien, eine für einen erfolgreichen Test und einen für den Fehlerfall Test: SourceValid.xml

<ns0:SourcePerson xmlns:ns0="http://Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.SourceSchema">

  <SirName>Edi</SirName>

  <LastName>Thor</LastName>

  <ZipCode>10111</ZipCode>

  <City>Berlin</City>

  <Street>Muster Str. 4</Street>

  <Country>Germany</Country>

 

</ns0:SourcePerson>

Und MissinCountrySource.xml

<ns0:SourcePerson
xmlns:ns0="http://Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.SourceSchema">

  <SirName>Edi</SirName>

  <LastName>Thor</LastName>

  <ZipCode>10111</ZipCode>

  <City>Berlin</City>

  <Street>Muster Str. 4</Street>

 

</ns0:SourcePerson>

In der Testklasse „SourceSchemaTests“ schriebt man nun für den „Gut Fall“ folgende Methode:

[TestMethod]

public void SourceSchemaSuccessValidationTest()

{

    string xmlFilePath = Path.Combine(testFilesDirectoryPath, "SourceValid.xml");

    File.Exists(xmlFilePath).Should().BeTrue();

    this.schemaToTest.ValidateInstance(xmlFilePath, OutputInstanceType.XML).Should().BeTrue();

}

Diese Methode überprüft zuerst, ob die zu testende XML Datei vorhanden ist und ruft dann von der zu testenden Schema Instanz die „ValidateInstance“ Methode auf. Diese Methode liefert dann einen Boolean zurück, ob die XML-Datei valide ist. Für den „Nicht Valide Fall“, schreiben wir eine zweite Methode, welche dann den „xmlFilePath“ mit „MissingCountrySource.xml“ erstellt und in der letzten Zeile statt „.BeTrue()“ „.BeFalse()“ steht. Microsoft bietet bei seiner Unit Test Lösung leider nicht an, den genauen Fehler in Unit Tests prüfen zu können. Um nun aber wirklich sicherstellen zu können, dass alle Fälle die nicht Valide sind abdecken zu können, muss man nun noch weitere XML-Dateien in „SimpleTransferXmlFiles“ ablegen, die dann die genauen Einschränkungen des Schemas abdecken, wie z.B. mehrfach vorkommende „SirName“ Elemente oder andere Fehlende Elemente.

 


Testmethoden für TargeSchema

Und die beiden XML-Dateien für die Tests:

 

TargetValid.xml


<ns0:TargetPerson
xmlns:ns0="http://Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.TargetSchema">

  <SirName>Edi</SirName>

  <LastName>Thor</LastName>

  <FullName>Edi Thor</FullName>

  <Initials>ET</Initials>

  <Adress>

    <Street>Muster Str. 4</Street>

    <ZipCode>10111</ZipCode>

    <City>Berlin</City>

    <Country>Germany</Country>

  </Adress>

  <Adress>

    <Street>Muster Allee. 284</Street>

    <ZipCode>10555</ZipCode>

    <City>Berlin</City>

    <Country>Germany</Country>

  </Adress>

</ns0:TargetPerson>

MissingInitalsTarget.xml

<ns0:TargetPerson
xmlns:ns0="http://Quibiq.QuiTec.BtsAlm.SimpleTransfer.Schemas.TargetSchema">

  <SirName>Edi</SirName>

  <LastName>Thor</LastName>

  <FullName>Edi Thor</FullName>

  <Adress>

    <Street>Muster Str. 4</Street>

    <ZipCode>10111</ZipCode>

    <City>Berlin</City>

    <Country>Germany</Country>

  </Adress>

  <Adress>

    <Street>Muster Allee. 284</Street>

    <ZipCode>10555</ZipCode>

    <City>Berlin</City>

    <Country>Germany</Country>

  </Adress>

</ns0:TargetPerson>

Die Testmethoden sehen hier ähnlich aus, wie die Testmethoden bei der „SourceSchemaTests“ Klasse. Beide Klassen unterscheiden sich nur bei den Schema Instanz Typen  und den XML-Dateinamen für die Tests.

 

Mapping Tests

Die zu testenden Map sieht wie folgt aus:






Bei den Funktoiden wird folgendes sichergestellt:

 

1. SirName, LastName, FullName und Initals werden nur erzeugt, wenn in dem Eingangs-XML auch ein String ist „LocicalString“ + „LogicalAnd“ jeweils in die „ValueMapper“

2. SirName und LastName werden als Text in die beiden ValueMapper für SireName und LastName verwendet

3. Mithilfe von „StringConcatinate“ wird aus „SirName“ und „LastName“ „SirName LastName“
4. Mit einen „StringExtract“ und einem „ToUpper“ holt man jeweils den ersten Buchstaben von „SirName“ und „LastName“, macht sie zu Großbuchstaben und verknüpft sie wiederum mit einem „StringConcatinate"


Testmethoden
 

Für den „Gut-Fall“ verwendet man die schon oben verwendete „SourceValid.xml“. Für den Fehlerfall verwendet man eine XML-Datei, bei der das „LastName“ Element entfernt wurde und den Namen „MissingLastNameSource.xml“ heißt.

Anders  als bei den Schematests  muss man hier zwei XML Dateipfade erzeugen, welche man dann der „TestMap“ Methode von der „mapToTest“ Instanz übergeben muss. Zusätzlich setzt man die Attribute („ValidateInput“ und „ValidateOutout“) von „mapToTest“  auf „true“.

 

Nach dem Aufruf von „TestMap“ überprüfen wir, ob die  „target.xml“ Datei tatsächlich erzeugt worden ist. Somit  ergibt sich folgende Testmethode:

[TestMethod]

 public void Source2TargetValidTest()

 {

     string sourceXmlPath = Path.Combine(testFilesDirectoryPath, "SourceValid.xml");

     string targetXmlPath = Path.Combine(TestContext.TestDeploymentDir, "target.xml");

     mapToTest.ValidateInput = true;

     mapToTest.ValidateOutput = true;

     mapToTest.TestMap(sourceXmlPath, InputInstanceType.Xml, targetXmlPath, OutputInstanceType.XML);

     File.Exists(targetXmlPath).Should().BeTrue();

 }

Falls beim Ausführen der Map ein Fehler auftreten sollte, schlägt der Test mit einer „BizTalkTestAssertFailException“ fehl.

Diese Information nutzt man für den Fehlerfall Test. MSTest bietet einem hier zwar die Möglichkeit des „ExpectedExceptionAttribute". Diese ist allerdings sehr ungenau.
Mal angenommen, wir erwarten, dass die „TestMap“ Methode eine „ArgumentNullException“ aufwirft. Versucht man dann schon vor dem Aufruf von „TestMap“ eine „ArgumentNullException“, wird der Test erfolgreich sein – auch wenn man gar nicht sicher sein kann, dass auch die gewollte Methode diese wirft. Hierfür kann man ein einfaches Konstrukt verwenden:

try

{

    mapToTest.TestMap(sourceXmlPath, InputInstanceType.Xml, targetXmlPath,

    OutputInstanceType.XML);

    Assert.Fail("Map did not throw expected exception!");

}

catch (BizTalkTestAssertFailException ex)

{

    ex.Message.Should().Be("Mapper Compiler: Map output validation failure");

}

catch (Exception e)

{

    Assert.Fail($"The type of exception '{e.GetType()}' with message '{e.Message}',

    is not the expected type.");

}

Nun kann man sicher sein, dass der Test nur erfolgreich ist, wenn auch die gewollte Funktion die zu erwartende Exception wirft. Zur Vollständigkeit hier die komplette Testmethode:

[TestMethod]

public void Source2TargetWithValidateOutputFailTest()

{

    string sourceXmlPath = Path.Combine(testFilesDirectoryPath,

    "MissingLastNameSource.xml");

    string targetXmlPath = Path.Combine(TestContext.TestDeploymentDir,

    "target.xml");

    mapToTest.ValidateOutput = true;

    try

    {

        mapToTest.TestMap(sourceXmlPath, InputInstanceType.Xml, targetXmlPath,

        OutputInstanceType.XML);

        Assert.Fail("Map did not throw expected exception!");

    }

    catch (BizTalkTestAssertFailException ex)

    {

        ex.Message.Should().Be("Mapper Compiler: Map output validation failure");

    }

    catch (Exception e)

    {

        Assert.Fail($"The type of exception '{e.GetType()}' with message

        '{e.Message}', is not the expected type.");

    }

 

}

Das Ergebnis dieses Artikels gibt es hier zum Download

 

Viel Erfolg!

 

Stephan Kelso, Senior Developer

 

 

 

 

 

 

 

 

 

 

 

Dateien:
Quibiq.QuiTec.BtsAlm_01.zip310 K

zurück