Testing-APIs
End-2-End-Tests (insbesondere Geschäftsprozess- und/oder Verbundprozesstests), die in der Testpyramide wie in folgender Abbildung angesiedelt sind,

birgen mehr Herausforderungen, also beispielsweise UnitTests:
- Um parallel Tests ausführen zu können, müssen Daten, die durch die Testausführung geändert werden, vor dem Test angelegt werden.
- Sind die End-2-End-Tests funktionaler Natur, so muss an einigen Stellen (z.B. Jobs oder Benutzerinteraktion) die Fortführung forciert werden können.
- Zwischenergebnisse müssen geprüft werden. Auch hier ist die Kapselung notwendig.
- Der Implementierer kann nicht alle Implementierungsdetails kennen (-> Kapselung). Das betrifft die Erstellung von Daten, aber auch die Validierung der Ergebnisse durch sogenannte Prüftpunkte.
Genau hier setzen die Testing-APIs an:
Testing-APIs stellen eine Vereinfachung zur Umsetzung von End-2-End-Tests dar, so dass der oder die Ersteller solcher Tests z.B. von fachlichen Details zur Validierung und der Erstellung von fachlich sinnvollen Daten befreit werden (durch Kapselung).
Dabei stellen sie entsprechend nachstehender schematischer Darstellung eines Pfades eines Tests folgende Funktionalitäten bereit:

- Hilfsroutinen für den Testaufbau (Arrange). Dies ist in der Regel die Möglichkeit, Testdaten auf einfache Art und Weise, d.h. ohne Kenntnis von Expertenwissen, anzulegen.
- Implementierung von Prüfpunkten, um das fachliche Ergebnis sicherzustellen.
- Hilfroutinen, um den Test schneller durchführen zu können (z.B. Anstarten von Jobs).
- Emulieren von Benutzerverhalten durch automatisierte Bedienung von Benutzeroberflächen.
Zum Anlegen von Testdaten sollten keine Benutzeroberflächen, sondern in der Implementierung Services verwendet werden, da dies nicht Teil der Testdurchführung ist!
Eine Testing-API pro Bounded Context
Da End-2-End-Tests viele Bounded Contexts nutzen, wird pro Bounded Context genau eine Testing-API erstellt, um eine möglichst einfache und gleichartiger Strukturierung zu haben.

Eine Hierarchisierung von Testing-APIs ist derzeit nicht vorgesehen!
In der Konsequenz werden diese dann - wie schematisch in nachfolgender Abbildung angedeutet - im Test verwendet.

Zur einfacheren und besseren Implementierung sollte die Bibliothek "Schleupen.XS.QS.GP.Testing" verwendet werden. Da dies keine Testing-API ist, klammern wir dies hier aus.
Technische Umsetzung
Aus technischer Sicht werden End-2-End-Tests mithilfe von "Schleupen.XS.QS.GP.Testing" und Testing-APIs in C# implementiert. Hierbei sind häufig viele BoundContexts involviert, sodass potenzielle viele Testing-APIs verwendet werden. Da Testing-APIs wiederum als Nuget-Packages und somit als Bibliotheken angebunden werden, die wiederum weitere Biblitheken verwenden usf, kann diese Hierarchisierung bei einfacher Bereitstellung zu Problemen führen.

So kann es zum einen wie in der Abbildung beispielsweise schwierig sein, für "lib2" eine Version auszuwählen, die für alle (NUnit-Test, "MyTestingLib 1" und "MyTestingLib 2") genutzt werden kann (-> es kann keine passende Version gefunden werden). Oder zum anderen wird zur Laufzeit initial eine Version von "lib2" geladen, die Funktionalität nicht beinhaltet, die aber von "MyTestingLib 2" benötigt wird (ältere Version).
=> Wir benötigen zur Lösung eine geeignete Isolation!
IsolationContext
Diese Probleme lösen wir mithilfe des sogenannten IsolationContexts, der auf Microsofts ApplicationLoadContext basiert.
Exkurs: AssemblyLoadContext
Der AssemblyLoadContext ist zentrales Konzept in .NET Core / .NET 5+, das die Lade- und Isolationsgrenze für Assemblies definiert. Er ersetzt funktional das frühere AppDomain-Modell aus dem .NET Framework.
In .NET müssen Assemblies (DLLs) zur Laufzeit geladen werden. Dabei gibt es folgende Herausforderungen:
- Unterschiedliche Versionen derselben Assembly
- Plugin-Architekturen
- Dynamisches Nachladen / Entladen
- Vermeidung von Type Identity Konflikten
Im .NET Framework wurde das primär über die sogenannte AppDomain gelöst. In .NET Core wurde das durch AssemblyLoadContext ersetzt.
Der sogenannte IsolationContext (Schleupen) erweitert den AssemblyLoadContext um einen Lademechanismus von Assemblies, sodass gleiches Versionen einer Assembly in den Prozess eingeladen werden können und diese in spezifischen Verzeichnissen abgelegt sind.
Dabei nutzt der UnitTest zur Laufzeit eine sogenannte "Satelliten"-Assembly - im Beispiel sind das die Assemblies "MyTestingLib 1.dll" und "MyTestingLib 2.dll". Diese referenzieren jeweils in einem separierten AssemblyLoadContext eine interne Assembly, die alle notwendigen Abhängigkeiten hat. Somit erhalten wir zur Laufzeit die notwendige Isolation.

Damit dies funktioniert, werden die Assemblies der Testing-APIs durch einen MSBuild-Schritt an definierte Stellen im Dateisystem abgelegt. Von diesen Stellen werden die Assemblies zur Laufzeit geladen.

Die Implementierungssicht stellt sich das beispielsweise wie folgt dar. Hier wird ein sogenanntes IBuchFixture für den Nutzer bereitgestellt. Die Implementierung dieser Schnittstelle liegt dann in der internen Assembly.

Unter der Haube ...
Für Problemanalysen ist es hilfreich zu verstehen, wie ein nuget-Package einer Testing-API aufgebaut ist.

Wir sehen, dass die "Satelliten"-Assembly als "normale" .net-Assembly abgelegt ist - die internen Assembly sind im Verzeichnis "IsolationContext" zu finden.
Im Dateisystem finden sich die Inhalten, wie in folgender Abbildung dargestellt, wieder (hierbei ist der "IntegrativeTest" der Nutzer:


Host bei Testausführung übersteuern
Damit in der FixtureFactory der Host angegeben werden kann, auf dem der Test ausgeführt wird, ist folgendes zu tun.
SOAP-Services
Mit Hilfe des Interface IRemoteBrokerClientConfiguration aus dem Package Schleupen.CS.PI.SB.Wcf.BrokerClient.Testing.Net
kann der Host, auf dem die Tests ausgeführt werden, übersteuert werden.
Hierzu ist der BrokerClientTestingInstaller aus dem Package
Schleupen.CS.PI.SB.Wcf.BrokerClient.Testing.Castle6.Net
im ContainerConfigurator der internen Assembly anzuschließen: windsorContainer.Install(new BrokerClientTestingInstaller());
WebAPI-Services
Mit Hilfe des Interface IRemoteConsulEndpointLocator aus dem Package Schleupen.CS.PI.SB.WebApi.Client.Testing.Net
kann der Host, auf dem die Tests ausgeführt werden, übersteuert werden.
Hierzu ist der WebApiClientTestingInstaller aus dem Package
Schleupen.CS.PI.SB.WebApi.Client.Testing.Castle6.Net
im ContainerConfigurator der internen Assembly anzuschließen: windsorContainer.Install(new WebApiClientTestingInstaller());
Aktuell unterstützt dies noch keine Testing-API.
Emulierung von Benutzerverhalten
Insbesondere das Benutzerverhalten durch Emulieren des Durchlaufens von Dialogabläufen, ist Teil von Testing-APIs. Somit wird erreicht, so nahe wie möglich an der Realität zu testen. Hierzu verwenden wir Playwright.
Beachte hier unbedingt das Muster: Implementierung Testing-API-übergreifender UIs, wenn Bounded Context-übergreifende Dialogabläufe implementiert werden!
Architektur-Hinweise
- Testing-APIs sollten orchestrierend sein und möglichst die fachliche Logik per Service anbinden.
- Testing-APIs steuern beispielsweise in Verbundprozesstests ggf. verschiedene Rechner an und müssen somit pro angebotenem Aufruf Remote-fähig sein.
- Zur möglichst guten Nutzung sollte ein Builder (im Sinne der Gang of Four) samt sogenanntem Director implementiert werden.
Pitfall WCF-ServiceClient
Zur Implementierung von Testing-APIs darf der WCF-ServiceClient (-> System.ServiceModel.ClientBase<>) nicht verwendet werden. Hierdurch können schwerwiegende und schwer zu analysierende Probleme entstehen!
Zur Lösung ist "Sigento.NET" als benutzerdefiniertes Tool für WSDLs zu verwenden!
