Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Microsoft.Testing.Platform besteht aus einem Testframework und einer beliebigen Anzahl von Erweiterungen , die im Prozess oder außerhalb des Prozesses ausgeführt werden können.
Wie im Abschnitt "Architektur " beschrieben, ist Microsoft.Testing.Platform für eine Vielzahl von Szenarien und Erweiterbarkeitspunkten konzipiert. Die primäre und wesentliche Erweiterung ist zweifellos das Testframework, das von Ihren Tests genutzt wird. Wenn Sie es nicht registrieren, tritt ein Startfehler auf. Das Testframework ist die einzige Erweiterung, die zum Ausführen einer Testsitzung zwingend notwendig ist.
Um Szenarien wie das Generieren von Testberichten, Code Coverage, das Wiederholen fehlgeschlagener Tests und andere mögliche Features zu unterstützen, die das Testframework selbst nicht bietet, müssen Sie einen Mechanismus bereitstellen, der die Verwendung anderer Erweiterungen in Verbindung mit dem Testframework zum Bereitstellen dieser Features ermöglicht.
Im Wesentlichen ist das Testframework die primäre Erweiterung, die Informationen zu den einzelnen Tests bereitstellt, aus denen die Testsammlung besteht. Es meldet, ob ein bestimmter Test erfolgreich war, fehlgeschlagen ist oder übersprungen wurde und kann zusätzliche Informationen zu jedem Test liefern, z. B. einen lesbaren Namen (als Anzeigename bezeichnet), die Quelldatei, die Zeile, in der der Test beginnt usw.
Der Erweiterungspunkt ermöglicht die Verwendung von Informationen, die vom Testframework bereitgestellt werden, um neue Artefakte zu generieren oder vorhandene Artefakte mit zusätzlichen Features zu verbessern. Eine häufig verwendete Erweiterung ist der TRX-Bericht-Generator, der TestNodeUpdateMessage abonniert und daraus eine XML-Berichtsdatei generiert.
Wie im Artikel zur Architektur erläutert, gibt es bestimmte Erweiterungspunkte, die nicht im selben Prozess wie das Testframework ausgeführt werden können. In der Regel ist dies aus den folgenden Gründen nicht möglich:
- Die Umgebungsvariablen des Testhosts müssen geändert werden. Im Prozess des Testhosts selbst Änderungen vorzunehmen, wäre zu spät.
- Der Prozess muss von außen überwacht werden, da der Testhost, auf dem Tests und Benutzercode ausgeführt werden, Fehler im Benutzercode aufweisen kann, die den Prozess selbst instabil machen und zu Hängenbleiben oder Abstürzen führen können. In solchen Fällen würde die Erweiterung zusammen mit dem Testhostprozess abstürzen oder hängenbleiben.
Aus diesen Gründen werden die Erweiterungspunkte in zwei Typen unterteilt:
Prozessinterne Erweiterungen: Diese Erweiterungen arbeiten im selben Prozess wie das Testframework (In-Process).
Sie können prozessinterne Erweiterungen über die
ITestApplicationBuilder.TestHost
-Eigenschaft registrieren:// ... var builder = await TestApplication.CreateBuilderAsync(args); builder.TestHost.AddXXX(...); // ...
Prozessexterne Erweiterungen: Diese Erweiterungen arbeiten in einem separaten Prozess (Out-of-Process), sodass sie den Testhost überwachen können, ohne vom Testhost selbst beeinflusst zu werden.
Sie können prozessexterne Erweiterungen über die
ITestApplicationBuilder.TestHostControllers
-Eigenschaft registrieren.var builder = await TestApplication.CreateBuilderAsync(args); builder.TestHostControllers.AddXXX(...);
Es gibt auch einige Erweiterungen, die in beiden Szenarien funktionieren. Diese allgemeinen Erweiterungen verhalten sich in beiden Hosts identisch. Sie können diese Erweiterungen entweder über die TestHost- und TestHostController-Schnittstellen oder direkt auf der
ITestApplicationBuilder
-Ebene registrieren. Ein Beispiel für eine solche Erweiterung ist ICommandLineOptionsProvider.
Die IExtension
-Schnittstelle
Die IExtension
-Schnittstelle dient als grundlegende Schnittstelle für alle Erweiterungspunkte innerhalb der Testplattform. Sie wird in erster Linie verwendet, um beschreibende Informationen zur Erweiterung abzurufen und die Erweiterung selbst zu aktivieren oder zu deaktivieren.
Sehen Sie sich die folgende IExtension
-Schnittstelle an:
public interface IExtension
{
string Uid { get; }
string Version { get; }
string DisplayName { get; }
string Description { get; }
Task<bool> IsEnabledAsync();
}
Uid
: Stellt den eindeutigen Bezeichner für die Erweiterung dar. Es ist wichtig, einen eindeutigen Wert für diese Zeichenfolge auszuwählen, um Konflikte mit anderen Erweiterungen zu vermeiden.Version
: Stellt die Version der Schnittstelle dar. Die Angabe der Version erfordert die Verwendung der semantischen Versionierung.DisplayName
: Eine benutzerfreundliche Darstellung des Namens (Anzeigename), die in Protokollen und beim Anfordern von Informationen mithilfe der Befehlszeilenoption--info
angezeigt wird.Description
: Die Beschreibung der Erweiterung, die angezeigt wird, wenn Sie mithilfe der Befehlszeilenoption--info
Informationen anfordern.IsEnabledAsync()
: Diese Methode wird beim Instanziieren der Erweiterung von der Testplattform aufgerufen. Wenn die Methodefalse
zurückgibt, wird die Erweiterung ausgeschlossen. Diese Methode trifft Entscheidungen in der Regel basierend auf der Konfigurationsdatei oder einigen benutzerdefinierten Befehlszeilenoptionen. Benutzer geben häufig--customExtensionOption
in der Befehlszeile an, um die Erweiterung selbst zu aktivieren.
Testframeworkerweiterung
Das Testframework ist die primäre Erweiterung, die der Testplattform die Fähigkeit verleiht, Tests zu ermitteln und auszuführen. Das Testframework ist dafür verantwortlich, die Ergebnisse der Tests an die Testplattform zu übermitteln. Das Testframework ist die einzige Erweiterung, die zum Ausführen einer Testsitzung zwingend notwendig ist.
Registrieren eines Testframeworks
In diesem Abschnitt wird erläutert, wie Sie das Testframework bei der Testplattform registrieren. Sie registrieren nur ein Testframework pro Testanwendungs-Generator mithilfe der API, wie in der TestApplication.RegisterTestFramework
Dokumentation zur Microsoft.Testing.Platform-Architektur dargestellt.
Die Registrierungs-API wird wie folgt definiert:
ITestApplicationBuilder RegisterTestFramework(
Func<IServiceProvider, ITestFrameworkCapabilities> capabilitiesFactory,
Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework> adapterFactory);
Die RegisterTestFramework
-API erwartet zwei Factorys:
Func<IServiceProvider, ITestFrameworkCapabilities>
: Dies ist ein Delegat, der ein Objekt akzeptiert, das dieIServiceProvider
-Schnittstelle implementiert, und ein Objekt zurückgibt, das dieITestFrameworkCapabilities
-Schnittstelle implementiert.IServiceProvider
bietet Zugriff auf Plattformdienste wie Konfigurationen, Protokollierungen (Logger) und Befehlszeilenargumente.Die
ITestFrameworkCapabilities
-Schnittstelle wird verwendet, um die vom Testframework unterstützten Funktionen für die Plattform und Erweiterungen anzukündigen. Durch die Implementierung und Unterstützung bestimmter Verhaltensweisen ermöglicht sie der Plattform und den Erweiterungen die korrekte Interaktion. Weitere Informationen zum Konzept von Funktionen finden Sie im entsprechenden Abschnitt.Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework>
: Dies ist ein Delegat, der ein ITestFrameworkCapabilities-Objekt, bei dem es sich um die vonFunc<IServiceProvider, ITestFrameworkCapabilities>
zurückgegebene Instanz handelt, und ein IServiceProvider-Objekt akzeptiert, um den Zugriff auf Plattformdienste bereitzustellen. Das erwartete Rückgabeobjekt ist ein Objekt, das die ITestFramework-Schnittstelle implementiert.ITestFramework
dient als Ausführungs-Engine, die Tests ermittelt und ausführt und anschließend die Ergebnisse an die Testplattform übermittelt.
Die Notwendigkeit für die Plattform, die Erstellung von ITestFrameworkCapabilities
und ITestFramework zu trennen, ist eine Optimierung, die verhindert, dass das Testframework erstellt wird, wenn die unterstützten Funktionen nicht zum Ausführen der aktuellen Testsitzung ausreichen.
Das folgende Benutzercodebeispiel zeigt eine Testframeworkregistrierung, für die ein leerer Funktionssatz zurückgegeben wird:
internal class TestingFrameworkCapabilities : ITestFrameworkCapabilities
{
public IReadOnlyCollection<ITestFrameworkCapability> Capabilities => [];
}
internal class TestingFramework : ITestFramework
{
public TestingFramework(ITestFrameworkCapabilities capabilities, IServiceProvider serviceProvider)
{
// ...
}
// Omitted for brevity...
}
public static class TestingFrameworkExtensions
{
public static void AddTestingFramework(this ITestApplicationBuilder builder)
{
builder.RegisterTestFramework(
_ => new TestingFrameworkCapabilities(),
(capabilities, serviceProvider) => new TestingFramework(capabilities, serviceProvider));
}
}
// ...
Sehen Sie sich nun den entsprechenden Einstiegspunkt in diesem Beispiel mit dem Registrierungscode an:
var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args);
// Register the testing framework
testApplicationBuilder.AddTestingFramework();
using var testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();
Hinweis
Die Rückgabe eines leeren ITestFrameworkCapabilities-Objekts sollte die Ausführung der Testsitzung nicht verhindern. Alle Testframeworks sollten in der Lage sein, Tests zu ermitteln und auszuführen. Die Auswirkungen sollten sich auf Erweiterungen beschränken, die deaktiviert werden können, wenn ein bestimmtes Feature nicht im Testframework verfügbar ist.
Erstellen eines Testframeworks
Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework
wird durch Erweiterungen implementiert, die ein Testframework bereitstellen:
public interface ITestFramework : IExtension
{
Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context);
Task ExecuteRequestAsync(ExecuteRequestContext context);
Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context);
}
Die ITestFramework
-Schnittstelle erbt von der IExtension-Schnittstelle, einer Schnittstelle, von der alle Erweiterungspunkte erben.
IExtension
wird verwendet, um den Namen und die Beschreibung der Erweiterung abzurufen.
IExtension
bietet auch die Möglichkeit, die Erweiterung im Setup über Task<bool> IsEnabledAsync()
dynamisch zu aktivieren oder zu deaktivieren. Stellen Sie sicher, dass diese Methode true
zurückgibt, wenn Sie keine speziellen Anforderungen haben.
Die CreateTestSessionAsync
-Methode
Die CreateTestSessionAsync
-Methode wird zu Beginn der Testsitzung aufgerufen und zum Initialisieren des Testframeworks verwendet. Die API akzeptiert ein CloseTestSessionContext
-Objekt und gibt ein CloseTestSessionResult
-Objekt zurück.
public sealed class CreateTestSessionContext : TestSessionContext
{
public SessionUid SessionUid { get; }
public ClientInfo Client { get; }
public CancellationToken CancellationToken { get; }
}
public readonly struct SessionUid
{
public string Value { get; }
}
public sealed class ClientInfo
{
public string Id { get; }
public string Version { get; }
}
Die SessionUid
dient als eindeutiger Bezeichner für die aktuelle Testsitzung und stellt eine logische Verbindung mit den Ergebnissen der Sitzung her.
Das ClientInfo
-Objekt enthält Details zur Entität, die das Testframework aufruft. Mithilfe dieser Informationen kann das Testframework sein Verhalten ändern. Zum Zeitpunkt der Erstellung dieses Dokuments würde bei einer Konsolenausführung beispielsweise ein Clientname wie „testingplatform-console“ gemeldet werden.
Das CancellationToken
wird verwendet, um die Ausführung von CreateTestSessionAsync
anzuhalten.
Das Rückgabeobjekt ist ein CloseTestSessionResult
:
public sealed class CreateTestSessionResult
{
public string? WarningMessage { get; set; }
public string? ErrorMessage { get; set; }
public bool IsSuccess { get; set; }
}
Mit der IsSuccess
-Eigenschaft wird angegeben, ob die Sitzung erfolgreich erstellt wurde. Wenn sie false
zurückgibt, wird die Testausführung angehalten.
Die CloseTestSessionAsync
-Methode
Der CloseTestSessionAsync
-Methode steht hinsichtlich der Funktionalität CreateTestSessionAsync
gegenüber, wobei der einzige Unterschied die Objektnamen sind. Weitere Informationen finden Sie im Abschnitt CreateTestSessionAsync
.
Die ExecuteRequestAsync
-Methode
Die ExecuteRequestAsync
-Methode akzeptiert ein Objekt vom Typ ExecuteRequestContext
. Wie der Name schon sagt, enthält dieses Objekt die Details zu der Aktion, die das Testframework ausführen soll.
Die ExecuteRequestContext
-Definition lautet wie folgt:
public sealed class ExecuteRequestContext
{
public IRequest Request { get; }
public IMessageBus MessageBus { get; }
public CancellationToken CancellationToken { get; }
public void Complete();
}
IRequest
: Dies ist die Basisschnittstelle für alle Anforderungstypen. Stellen Sie sich das Testframework als einen prozessinternen zustandsbehafteten Server vor, auf dem der Lebenszyklus stattfindet:
Das obige Diagramm zeigt, dass die Testplattform nach dem Erstellen der Testframeworkinstanz drei Anforderungen ausgibt. Das Testframework verarbeitet diese Anforderungen und verwendet den IMessageBus
-Dienst, der in der Anforderung selbst enthalten ist, um das Ergebnis für jede Anforderung zu übermitteln. Sobald eine bestimmte Anforderung verarbeitet wurde, ruft das Testframework die Complete()
-Methode für die Anforderung auf, um anzugeben, dass sie von der Testplattform erfüllt wurde.
Die Testplattform überwacht alle gesendeten Anforderungen. Nachdem alle Anforderungen erfüllt wurden, ruft sie CloseTestSessionAsync
auf und verwirft die Instanz (sofern IDisposable/IAsyncDisposable
implementiert ist).
Da sich die Anforderungen und ihr Abschluss offensichtlich überlappen können, ist die parallele und asynchrone Ausführung von Anforderungen möglich.
Hinweis
Derzeit sendet die Testplattform keine überlappenden Anforderungen. Sie wartet auf den Abschluss einer Anforderung >>, bevor die nächste Anforderung gesendet wird. Dieses Verhalten kann sich in Zukunft jedoch ändern. Die Unterstützung für gleichzeitige Anforderungen wird durch das Funktionssystem bestimmt.
Die IRequest
-Implementierung gibt die genaue Anforderung an, die erfüllt werden muss. Das Testframework identifiziert den Anforderungstyp und behandelt die Anforderung entsprechend. Wenn der Anforderungstyp nicht erkannt wird, sollte eine Ausnahme ausgelöst werden.
Ausführliche Informationen zu den verfügbaren Anforderungen finden Sie im Abschnitt IRequest.
IMessageBus
: Dieser Dienst, der mit der Anforderung verknüpft ist, ermöglicht dem Testframework die asynchrone Veröffentlichung von Informationen über die laufende Anforderung auf der Testplattform.
Der Nachrichtenbus dient als zentraler Hub für die Plattform und erleichtert die asynchrone Kommunikation zwischen allen Plattformkomponenten und Erweiterungen.
Eine umfassende Liste der Informationen, die auf der Testplattform veröffentlicht werden können, finden Sie im Abschnitt IMessageBus.
CancellationToken
: Dieses Token wird verwendet, um die Verarbeitung einer bestimmten Anforderung zu unterbrechen.
Complete()
: Wie in der obigen Sequenz dargestellt, benachrichtigt die Complete
-Methode die Plattform, dass die Anforderung erfolgreich verarbeitet wurde und alle relevanten Informationen an den IMessageBus übermittelt wurden.
Warnung
Wird Complete()
nicht für die Anforderung aufgerufen, reagiert die Testanwendung nicht mehr.
Um das Testframework entsprechend Ihren Anforderungen oder den Anforderungen Ihrer Benutzer anzupassen, können Sie einen personalisierten Abschnitt in der Konfigurationsdatei oder benutzerdefinierte Befehlszeilenoptionen verwenden.
Behandeln von Anforderungen
Der folgende Abschnitt enthält eine detaillierte Beschreibung der verschiedenen Anforderungen, die ein Testframework empfangen und verarbeiten kann.
Bevor Sie mit dem nächsten Abschnitt fortfahren, müssen Sie das Konzept von IMessageBus verstehen, dem wesentlichen Dienst für die Übermittlung von Testausführungsinformationen an die Testplattform.
TestSessionContext
TestSessionContext
ist eine freigegebene Eigenschaft, die für alle Anforderungen verwendet wird und Informationen zur laufenden Testsitzung bereitstellt:
public class TestSessionContext
{
public SessionUid SessionUid { get; }
public ClientInfo Client { get; }
}
public readonly struct SessionUid(string value)
{
public string Value { get; }
}
public sealed class ClientInfo
{
public string Id { get; }
public string Version { get; }
}
Der TestSessionContext
besteht aus der SessionUid
, einem eindeutigen Bezeichner für die laufende Testsitzung, der zum Protokollieren und Korrelieren von Testsitzungsdaten verwendet wird. Er enthält auch den ClientInfo
-Typ, der Details zum Initiator der Testsitzung bereitstellt. Das Testframework kann basierend auf der Identität des Initiators der Testsitzung verschiedene Routen auswählen oder unterschiedliche Informationen veröffentlichen.
Discover-Testausführungsanforderung
public class DiscoverTestExecutionRequest
{
// Detailed in the custom section below
public TestSessionContext Session { get; }
// This is experimental and intended for future use, please disregard for now.
public ITestExecutionFilter Filter { get; }
}
Die DiscoverTestExecutionRequest
weist das Testframework an, die Tests zu ermitteln und diese Informationen an den IMessageBus zu übermitteln.
Wie im vorherigen Abschnitt beschrieben, ist DiscoveredTestNodeStateProperty
die Eigenschaft für einen ermittelten Test. Im Folgenden sehen Sie zur Referenz einen generischen Codeschnipsel:
var testNode = new TestNode
{
Uid = GenerateUniqueStableId(),
DisplayName = GetDisplayName(),
Properties = new PropertyBag(
DiscoveredTestNodeStateProperty.CachedInstance),
};
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
discoverTestExecutionRequest.Session.SessionUid,
testNode));
// ...
Ausführungstestanforderung starten
public class RunTestExecutionRequest
{
// Detailed in the custom section below
public TestSessionContext Session { get; }
// This is experimental and intended for future use, please disregard for now.
public ITestExecutionFilter Filter { get; }
}
Die RunTestExecutionRequest
weist das Testframework an, die Tests auszuführen und diese Informationen an den IMessageBus zu übermitteln.
Im Folgenden sehen Sie zur Referenz einen generischen Codeschnipsel:
var skippedTestNode = new TestNode()
{
Uid = GenerateUniqueStableId(),
DisplayName = GetDisplayName(),
Properties = new PropertyBag(
SkippedTestNodeStateProperty.CachedInstance),
};
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
runTestExecutionRequest.Session.SessionUid,
skippedTestNode));
// ...
var successfulTestNode = new TestNode()
{
Uid = GenerateUniqueStableId(),
DisplayName = GetDisplayName(),
Properties = new PropertyBag(
PassedTestNodeStateProperty.CachedInstance),
};
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
runTestExecutionRequest.Session.SessionUid,
successfulTestNode));
// ...
var assertionFailedTestNode = new TestNode()
{
Uid = GenerateUniqueStableId(),
DisplayName = GetDisplayName(),
Properties = new PropertyBag(
new FailedTestNodeStateProperty(assertionException)),
};
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
runTestExecutionRequest.Session.SessionUid,
assertionFailedTestNode));
// ...
var failedTestNode = new TestNode()
{
Uid = GenerateUniqueStableId(),
DisplayName = GetDisplayName(),
Properties = new PropertyBag(
new ErrorTestNodeStateProperty(ex.InnerException!)),
};
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
runTestExecutionRequest.Session.SessionUid,
failedTestNode));
TestNodeUpdateMessage
-Daten
Wie im Abschnitt IMessageBus erwähnt, müssen Sie vor der Verwendung des Nachrichtenbusses den Datentyp angeben, den Sie bereitstellen möchten. Die Testplattform hat einen bekannten Typ TestNodeUpdateMessage
definiert, der das Konzept der Testaktualisierungsinformationen darstellt.
In diesem Teil des Dokuments wird erläutert, wie Sie diese Nutzlastdaten verwenden. Sehen wir uns die Oberfläche genauer an:
public sealed class TestNodeUpdateMessage(
SessionUid sessionUid,
TestNode testNode,
TestNodeUid? parentTestNodeUid = null)
{
public TestNode TestNode { get; }
public TestNodeUid? ParentTestNodeUid { get; }
}
public class TestNode
{
public required TestNodeUid Uid { get; init; }
public required string DisplayName { get; init; }
public PropertyBag Properties { get; init; } = new();
}
public sealed class TestNodeUid(string value)
public sealed partial class PropertyBag
{
public PropertyBag();
public PropertyBag(params IProperty[] properties);
public PropertyBag(IEnumerable<IProperty> properties);
public int Count { get; }
public void Add(IProperty property);
public bool Any<TProperty>();
public TProperty? SingleOrDefault<TProperty>();
public TProperty Single<TProperty>();
public TProperty[] OfType<TProperty>();
public IEnumerable<IProperty> AsEnumerable();
public IEnumerator<IProperty> GetEnumerator();
...
}
public interface IProperty
{
}
TestNodeUpdateMessage
: DieTestNodeUpdateMessage
besteht aus zwei Eigenschaften: einemTestNode
und einerParentTestNodeUid
. DieParentTestNodeUid
-Eigenschaft gibt an, dass ein Test möglicherweise über einen übergeordneten Test verfügt, wodurch das Konzept einer Teststruktur eingeführt wird, in derTestNode
-Instanzen in Bezug zueinander angeordnet werden können. Diese Struktur ermöglicht basierend auf der Strukturbeziehung zwischen den Knoten zukünftige Verbesserungen und Features. Wenn Ihr Testframework keine Teststruktur erfordert, können Sie die Eigenschaft einfach auf NULL festlegen, sodass Sie eine einfache flache Liste vonTestNode
-Instanzen erhalten.TestNode
: DerTestNode
(Testknoten) besteht aus drei Eigenschaften, von denen eine dieUid
vom TypTestNodeUid
ist. DieseUid
dient als EINDEUTIGE STABILE ID für den Knoten. Der Begriff EINDEUTIGE STABILE ID impliziert, dass derselbeTestNode
bei unterschiedlichen Ausführungen und auf verschiedenen Betriebssystemen eine IDENTISCHEUid
beibehalten sollte. DieTestNodeUid
ist eine beliebige nicht transparente Zeichenfolge, die von der Testplattform unverändert akzeptiert wird.
Von Bedeutung
Die Stabilität und Eindeutigkeit der ID sind in der Testdomäne von entscheidender Bedeutung. Sie ermöglichen die genaue Ausrichtung der Ausführung auf einen einzelnen Test und die Verwendung der ID als persistenten Bezeichner für einen Test, wodurch leistungsstarke Erweiterungen und Features ermöglicht werden.
Die zweite Eigenschaft ist DisplayName
, der lesbare Name bzw. Anzeigename für den Test. Dieser Name wird beispielsweise angezeigt, wenn Sie die Befehlszeile --list-tests
ausführen.
Das dritte Attribut ist Properties
, ein PropertyBag
-Typ. Wie im Code gezeigt, ist dies ein spezieller Eigenschaftenbehälter, der generische Eigenschaften in Bezug auf die TestNodeUpdateMessage
enthält. Dies bedeutet, dass Sie eine beliebige Eigenschaft an den Knoten anfügen können, der die Platzhalterschnittstelle IProperty
implementiert.
Die Testplattform identifiziert bestimmte Eigenschaften, die TestNode.Properties
hinzugefügt wurden, um zu bestimmen, ob ein Test erfolgreich war, fehlgeschlagen ist oder übersprungen wurde.
Sie finden die aktuelle Liste der verfügbaren Eigenschaften mit der relativen Beschreibung im Abschnitt TestNodeUpdateMessage.TestNode.
Auf den PropertyBag
-Typ kann in der Regel in jeder IData
-Schnittstelle zugegriffen werden. Er wird verwendet, um verschiedene Eigenschaften zu speichern, die von der Plattform und den Erweiterungen abgefragt werden können. Dieser Mechanismus ermöglicht es, die Plattform mit neuen Informationen zu verbessern, ohne Breaking Changes einzuführen. Wenn eine Komponente die Eigenschaft erkennt, kann sie sie abfragen. Andernfalls ignoriert sie die Eigenschaft.
Abschließend wird in diesem Abschnitt verdeutlicht, dass Ihre Testframeworkimplementierung wie im folgenden Beispiel gezeigt die IDataProducer
-Schnittstelle implementieren muss, die TestNodeUpdateMessage
s erzeugt:
internal sealed class TestingFramework
: ITestFramework, IDataProducer
{
// ...
public Type[] DataTypesProduced =>
[
typeof(TestNodeUpdateMessage)
];
// ...
}
Wenn Ihr Testadapter die Veröffentlichung von Dateien während der Ausführung erfordert, finden Sie die erkannten Eigenschaften in dieser Quelldatei: https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs. Wie Sie sehen, können Sie Dateiressourcen auf allgemeine Weise bereitstellen oder sie einem bestimmten TestNode
zuordnen. Wenn Sie vorhaben, ein SessionFileArtifact
zu pushen, müssen Sie daran denken, dieses wie unten dargestellt vorab gegenüber der Plattform zu deklarieren:
internal sealed class TestingFramework
: ITestFramework, IDataProducer
{
// ...
public Type[] DataTypesProduced =>
[
typeof(TestNodeUpdateMessage),
typeof(SessionFileArtifact)
];
// ...
}
Bekannte Eigenschaften
Wie im Anfrageabschnitt beschrieben, identifiziert die Testplattform bestimmte Eigenschaften, die dem TestNodeUpdateMessage
hinzugefügt werden, um den Status eines TestNode
zu bestimmen (z. B. erfolgreich, fehlgeschlagen, übersprungen usw.). Auf diese Weise kann die Runtime eine genaue Liste der fehlgeschlagenen Tests mit den entsprechenden Informationen in der Konsole anzeigen und den entsprechenden Exitcode für den Testprozess festlegen.
In diesem Abschnitt erläutern wir die verschiedenen bekannten IProperty
-Optionen und ihre jeweiligen Auswirkungen.
Eine umfassende Liste bekannter Eigenschaften finden Sie unter TestNodeProperties.cs. Wenn Sie feststellen, dass eine Eigenschaftsbeschreibung fehlt, geben Sie bitte ein Problem an.
Diese Eigenschaften lassen sich in folgende Kategorien unterteilen:
- Allgemeine Informationen: Eigenschaften, die in jede Anforderung eingeschlossen werden können.
-
Ermittlungsinformationen: Eigenschaften, die während einer Ermittlungsanforderung
DiscoverTestExecutionRequest
bereitgestellt werden. -
Ausführungsinformationen: Eigenschaften, die während einer Testausführungsanforderung
RunTestExecutionRequest
bereitgestellt werden.
Bestimmte Eigenschaften sind erforderlich und andere optional. Die obligatorischen Eigenschaften sind erforderlich, um grundlegende Testfunktionen bereitzustellen, z. B. das Melden fehlgeschlagener Tests und die Angabe, ob die gesamte Testsitzung erfolgreich war oder nicht.
Optionale Eigenschaften verbessern hingegen die Testerfahrung, indem sie zusätzliche Informationen bereitstellen. Sie sind besonders nützlich in IDE-Szenarien (Integrated Development Environment, integrierte Entwicklungsumgebung) wie z. B. Visual Studio und Visual Studio Code, bei Konsolenausführungen oder bei der Unterstützung bestimmter Erweiterungen, die detailliertere Informationen erfordern, damit sie korrekt funktionieren. Diese optionalen Eigenschaften haben jedoch keine Auswirkung auf die Ausführung der Tests.
Hinweis
Erweiterungen müssen Warnungen generieren und Ausnahmen verwalten, wenn für ihre ordnungsgemäße Funktion bestimmte Informationen erforderlich sind. Wenn eine Erweiterung nicht über die erforderlichen Informationen verfügt, sollte sie keinen Fehler der Testausführung auslösen, sondern sich einfach deaktivieren.
Allgemeine Informationen
public record KeyValuePairStringProperty(
string Key,
string Value)
: IProperty;
Die KeyValuePairStringProperty
steht für allgemeine Daten in Form von Schlüssel-Wert-Paaren.
public record struct LinePosition(
int Line,
int Column);
public record struct LinePositionSpan(
LinePosition Start,
LinePosition End);
public abstract record FileLocationProperty(
string FilePath,
LinePositionSpan LineSpan)
: IProperty;
public sealed record TestFileLocationProperty(
string FilePath,
LinePositionSpan LineSpan)
: FileLocationProperty(FilePath, LineSpan);
TestFileLocationProperty
wird verwendet, um die Position des Tests innerhalb der Quelldatei genau zu bestimmen. Dies ist besonders hilfreich, wenn der Initiator eine IDE wie Visual Studio oder Visual Studio Code ist.
public sealed record TestMethodIdentifierProperty(
string AssemblyFullName,
string Namespace,
string TypeName,
string MethodName,
string[] ParameterTypeFullNames,
string ReturnTypeFullName)
TestMethodIdentifierProperty
ist ein eindeutiger Bezeichner für eine Testmethode, die dem ECMA-335-Standard entspricht.
Hinweis
Die zum Erstellen dieser Eigenschaft erforderlichen Daten können bequem mithilfe des .NET-Reflexionsfeatures mit Typen aus dem System.Reflection
-Namespace abgerufen werden.
public sealed record TestMetadataProperty(
string Key,
string Value)
TestMetadataProperty
wird verwendet, um die Eigenschaften oder Merkmale eines TestNode
zu übermitteln.
Erkennungsinformationen
public sealed record DiscoveredTestNodeStateProperty(
string? Explanation = null)
{
public static DiscoveredTestNodeStateProperty CachedInstance { get; }
}
Die DiscoveredTestNodeStateProperty
gibt an, dass dieser Testknoten (TestNode) erkannt wurde. Sie wird verwendet, wenn eine DiscoverTestExecutionRequest
an das Testframework gesendet wird.
Notieren Sie sich den praktischen zwischengespeicherten Wert, den die CachedInstance
-Eigenschaft bereitstellt.
Diese Eigenschaft ist erforderlich.
Ausführungsinformationen
public sealed record InProgressTestNodeStateProperty(
string? Explanation = null)
{
public static InProgressTestNodeStateProperty CachedInstance { get; }
}
Die InProgressTestNodeStateProperty
informiert die Testplattform, dass der TestNode
zur Ausführung geplant wurde und derzeit ausgeführt wird.
Notieren Sie sich den praktischen zwischengespeicherten Wert, den die CachedInstance
-Eigenschaft bereitstellt.
public readonly record struct TimingInfo(
DateTimeOffset StartTime,
DateTimeOffset EndTime,
TimeSpan Duration);
public sealed record StepTimingInfo(
string Id,
string Description,
TimingInfo Timing);
public sealed record TimingProperty : IProperty
{
public TimingProperty(TimingInfo globalTiming)
: this(globalTiming, [])
{
}
public TimingProperty(
TimingInfo globalTiming,
StepTimingInfo[] stepTimings)
{
GlobalTiming = globalTiming;
StepTimings = stepTimings;
}
public TimingInfo GlobalTiming { get; }
public StepTimingInfo[] StepTimings { get; }
}
Die TimingProperty
wird verwendet, um Details zur zeitlichen Steuerung der TestNode
-Ausführung weiterzuleiten. Sie ermöglicht auch die zeitliche Steuerung einzelner Ausführungsschritte über StepTimingInfo
. Dies ist besonders nützlich, wenn Ihr Testkonzept in mehrere Phasen unterteilt ist (z. B. Initialisierung, Ausführung und Bereinigung).
Nur eine der folgenden Eigenschaften ist erforderlich pro TestNode
. Sie kommuniziert das Ergebnis des TestNode
an die Testplattform.
public sealed record PassedTestNodeStateProperty(
string? Explanation = null)
: TestNodeStateProperty(Explanation)
{
public static PassedTestNodeStateProperty CachedInstance
{ get; } = new PassedTestNodeStateProperty();
}
PassedTestNodeStateProperty
informiert die Testplattform, dass dieser TestNode
erfolgreich war.
Notieren Sie sich den praktischen zwischengespeicherten Wert, den die CachedInstance
-Eigenschaft bereitstellt.
public sealed record SkippedTestNodeStateProperty(
string? Explanation = null)
: TestNodeStateProperty(Explanation)
{
public static SkippedTestNodeStateProperty CachedInstance
{ get; } = new SkippedTestNodeStateProperty();
}
SkippedTestNodeStateProperty
informiert die Testplattform, dass dieser TestNode
übersprungen wurde.
Notieren Sie sich den praktischen zwischengespeicherten Wert, den die CachedInstance
-Eigenschaft bereitstellt.
public sealed record FailedTestNodeStateProperty : TestNodeStateProperty
{
public FailedTestNodeStateProperty()
: base(default(string))
{
}
public FailedTestNodeStateProperty(string explanation)
: base(explanation)
{
}
public FailedTestNodeStateProperty(
Exception exception,
string? explanation = null)
: base(explanation ?? exception.Message)
{
Exception = exception;
}
public Exception? Exception { get; }
}
FailedTestNodeStateProperty
informiert die Testplattform, dass dieser TestNode
nach einer Assertion fehlgeschlagen ist.
public sealed record ErrorTestNodeStateProperty : TestNodeStateProperty
{
public ErrorTestNodeStateProperty()
: base(default(string))
{
}
public ErrorTestNodeStateProperty(string explanation)
: base(explanation)
{
}
public ErrorTestNodeStateProperty(
Exception exception,
string? explanation = null)
: base(explanation ?? exception.Message)
{
Exception = exception;
}
public Exception? Exception { get; }
}
ErrorTestNodeStateProperty
informiert die Testplattform, dass bei diesem TestNode
ein Fehler aufgetreten ist. Dieser Fehler unterscheidet sich von der FailedTestNodeStateProperty
, die für Assertionsfehler verwendet wird. Beispielsweise können Sie Probleme wie Testinitialisierungsfehler mit ErrorTestNodeStateProperty
melden.
public sealed record TimeoutTestNodeStateProperty : TestNodeStateProperty
{
public TimeoutTestNodeStateProperty()
: base(default(string))
{
}
public TimeoutTestNodeStateProperty(string explanation)
: base(explanation)
{
}
public TimeoutTestNodeStateProperty(
Exception exception,
string? explanation = null)
: base(explanation ?? exception.Message)
{
Exception = exception;
}
public Exception? Exception { get; }
public TimeSpan? Timeout { get; init; }
}
TimeoutTestNodeStateProperty
informiert die Testplattform, dass dieser TestNode
aufgrund einer Zeitüberschreitung (Timeout) fehlgeschlagen ist. Sie können das Timeout mithilfe der Timeout
-Eigenschaft melden.
public sealed record CancelledTestNodeStateProperty : TestNodeStateProperty
{
public CancelledTestNodeStateProperty()
: base(default(string))
{
}
public CancelledTestNodeStateProperty(string explanation)
: base(explanation)
{
}
public CancelledTestNodeStateProperty(
Exception exception,
string? explanation = null)
: base(explanation ?? exception.Message)
{
Exception = exception;
}
public Exception? Exception { get; }
}
CancelledTestNodeStateProperty
informiert die Testplattform, dass dieser TestNode
aufgrund eines Abbruchs fehlgeschlagen ist.
Andere Erweiterungspunkte
Die Testplattform bietet zusätzliche Erweiterungspunkte, mit denen Sie das Verhalten der Plattform und des Testframeworks anpassen können. Diese Erweiterungspunkte sind optional und können verwendet werden, um die Testerfahrung zu verbessern.
ICommandLineOptionsProvider
-Erweiterungen
Hinweis
Wenn Sie diese API erweitern, ist die benutzerdefinierte Erweiterung sowohl innerhalb als auch außerhalb des Testhostprozesses vorhanden.
Wie im Abschnitt zur Architektur erläutert, erstellen Sie im ersten Schritt den ITestApplicationBuilder
, um das Testframework und die Erweiterungen zu registrieren.
var builder = await TestApplication.CreateBuilderAsync(args);
Die CreateBuilderAsync
-Methode akzeptiert ein Array von Zeichenfolgen (string[]
) namens args
. Diese Argumente können verwendet werden, um Befehlszeilenoptionen an alle Komponenten der Testplattform zu übergeben (einschließlich integrierter Komponenten, Testframeworks und Erweiterungen), sodass ihr Verhalten angepasst werden kann.
In der Regel werden die in der Main(string[] args)
-Standardmethode empfangenen Argumente übergeben. Wenn sich die Hostumgebung unterscheidet, kann jedoch eine beliebige Liste von Argumenten übergeben werden.
Argumente erfordern als Präfix einen doppelten Gedankenstrich --
. Beispiel: --filter
.
Wenn eine Komponente wie ein Testframework oder ein Erweiterungspunkt benutzerdefinierte Befehlszeilenoptionen bereitstellen soll, ist dies durch Implementieren der ICommandLineOptionsProvider
-Schnittstelle möglich. Diese Implementierung kann dann wie folgt mit der ITestApplicationBuilder
-Schnittstelle über die Registrierungsfactory der CommandLine
-Eigenschaft registriert werden:
builder.CommandLine.AddProvider(
static () => new CustomCommandLineOptions());
Im Beispiel ist CustomCommandLineOptions
eine Implementierung der ICommandLineOptionsProvider
-Schnittstelle. Diese Schnittstelle umfasst die folgenden Member und Datentypen:
public interface ICommandLineOptionsProvider : IExtension
{
IReadOnlyCollection<CommandLineOption> GetCommandLineOptions();
Task<ValidationResult> ValidateOptionArgumentsAsync(
CommandLineOption commandOption,
string[] arguments);
Task<ValidationResult> ValidateCommandLineOptionsAsync(
ICommandLineOptions commandLineOptions);
}
public sealed class CommandLineOption
{
public string Name { get; }
public string Description { get; }
public ArgumentArity Arity { get; }
public bool IsHidden { get; }
// ...
}
public interface ICommandLineOptions
{
bool IsOptionSet(string optionName);
bool TryGetOptionArgumentList(
string optionName,
out string[]? arguments);
}
Wie Sie sehen, erweitert die ICommandLineOptionsProvider
-Schnittstelle die IExtension
-Schnittstelle. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
Die Ausführungsreihenfolge von ICommandLineOptionsProvider
lautet wie folgt:
Sehen wir uns die APIs und ihre Bedeutung genauer an:
ICommandLineOptionsProvider.GetCommandLineOptions()
: Diese Methode wird verwendet, um alle Optionen abzurufen, die die Komponente bietet. Für jede CommandLineOption
(Befehlszeilenoption) müssen die folgenden Eigenschaften angegeben werden:
string name
: Dies ist der Name der Option (dargestellt ohne Gedankenstrich). Die Option filter wird von Benutzern beispielsweise als --filter
verwendet.
string description
: Dies ist eine Beschreibung der Option. Sie wird angezeigt, wenn Benutzer --help
als Argument an den Anwendungs-Generator übergeben.
ArgumentArity arity
: Die Stelligkeit oder Arität (Anzahl von Argumenten) einer Option ist die Anzahl von Werten, die übergeben werden können, wenn diese Option oder dieser Befehl angegeben ist. Folgende Stelligkeiten sind derzeit verfügbar:
-
Zero
: Stellt eine Stelligkeit der Argumente von Null dar. -
ZeroOrOne
: Stellt eine Stelligkeit der Argumente von Null oder 1 dar. -
ZeroOrMore
: Stellt eine Stelligkeit der Argumente von Null oder mehr dar. -
OneOrMore
: Stellt eine Stelligkeit der Argumente von 1 oder mehr dar. -
ExactlyOne
: Stellt eine Stelligkeit der Argumente von genau 1 dar.
Beispiele finden Sie in der Tabelle zur Argumentarität von System.CommandLine.
bool isHidden
: Diese Eigenschaft gibt an, dass die Option zur Verwendung verfügbar ist, aber nicht in der Beschreibung angezeigt wird, wenn --help
aufgerufen wird.
ICommandLineOptionsProvider.ValidateOptionArgumentsAsync
: Diese Methode wird verwendet, um das vom Benutzer bereitgestellte Argument zu überprüfen.
Wenn Sie beispielsweise einen Parameter namens --dop
haben, der den Grad der Parallelität für das benutzerdefinierte Testframework darstellt, kann ein Benutzer --dop 0
eingeben. In diesem Szenario wäre der Wert 0
ungültig, da ein Parallelitätsgrad von mindestens 1
erwartet wird. Mithilfe von ValidateOptionArgumentsAsync
können Sie eine Vorabüberprüfung durchführen und bei Bedarf eine Fehlermeldung zurückgeben.
Eine mögliche Implementierung für das obige Beispiel könnte wie folgt aussehen:
public Task<ValidationResult> ValidateOptionArgumentsAsync(
CommandLineOption commandOption,
string[] arguments)
{
if (commandOption.Name == "dop")
{
if (!int.TryParse(arguments[0], out int dopValue) || dopValue <= 0)
{
return ValidationResult.InvalidTask("--dop must be a positive integer");
}
}
return ValidationResult.ValidTask;
}
ICommandLineOptionsProvider.ValidateCommandLineOptionsAsync
: Diese Methode wird zuletzt aufgerufen und ermöglicht die Durchführung einer globalen Kohärenzprüfung.
Angenommen, unser Testframework bietet eine Funktion, mit der ein Testergebnisbericht generiert und in einer Datei gespeichert werden kann. Auf dieses Feature wird mithilfe der Option --generatereport
zugegriffen, und der Dateiname wird mit --reportfilename myfile.rep
angegeben. Wenn ein Benutzer in diesem Szenario die Option --generatereport
, aber keinen Dateinamen angibt, sollte die Überprüfung fehlschlagen, da der Bericht ohne einen Dateinamen nicht generiert werden kann.
Eine mögliche Implementierung für das obige Beispiel könnte wie folgt aussehen:
public Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions)
{
bool generateReportEnabled = commandLineOptions.IsOptionSet(GenerateReportOption);
bool reportFileName = commandLineOptions.TryGetOptionArgumentList(ReportFilenameOption, out string[]? _);
return (generateReportEnabled || reportFileName) && !(generateReportEnabled && reportFileName)
? ValidationResult.InvalidTask("Both `--generatereport` and `--reportfilename` need to be provided simultaneously.")
: ValidationResult.ValidTask;
}
Beachten Sie, dass die ValidateCommandLineOptionsAsync
-Methode den ICommandLineOptions
-Dienst bereitstellt, der zum Abrufen der Argumentinformationen verwendet wird, die von der Plattform selbst analysiert werden.
ITestSessionLifetimeHandler
-Erweiterungen
ITestSessionLifeTimeHandler
ist eine prozessinterne Erweiterung, die die Ausführung von Code vor und nach der Testsitzung ermöglicht.
Verwenden Sie die folgende API, um eine benutzerdefinierte ITestSessionLifeTimeHandler
-Schnittstelle zu registrieren:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
builder.TestHost.AddTestSessionLifetimeHandle(
static serviceProvider => new CustomTestSessionLifeTimeHandler());
Die Factory nutzt den IServiceProvider, um Zugriff auf die Sammlung von Diensten zu erhalten, die die Testplattform bietet.
Von Bedeutung
Die Registrierungsreihenfolge ist wichtig, da die APIs in der Reihenfolge aufgerufen werden, in der sie registriert wurden.
Die ITestSessionLifeTimeHandler
-Schnittstelle umfasst die folgenden Methoden:
public interface ITestSessionLifetimeHandler : ITestHostExtension
{
Task OnTestSessionStartingAsync(
SessionUid sessionUid,
CancellationToken cancellationToken);
Task OnTestSessionFinishingAsync(
SessionUid sessionUid,
CancellationToken cancellationToken);
}
public readonly struct SessionUid(string value)
{
public string Value { get; } = value;
}
public interface ITestHostExtension : IExtension
{
}
ITestSessionLifetimeHandler
ist ein Typ von ITestHostExtension
, der als Basis für alle Testhosterweiterungen dient. Wie alle anderen Erweiterungspunkte erbt er ebenfalls von IExtension. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
Beachten Sie für diese API die folgenden Details:
OnTestSessionStartingAsync
: Diese Methode wird vor Beginn der Testsitzung aufgerufen und empfängt das SessionUid
-Objekt, das einen nicht transparenten Bezeichner für die aktuelle Testsitzung bereitstellt.
OnTestSessionFinishingAsync
: Diese Methode wird nach Abschluss der Testsitzung aufgerufen, um sicherzustellen, dass das Testframework die Ausführung aller Tests abgeschlossen und alle relevanten Daten an die Plattform gemeldet hat. In dieser Methode verwendet die Erweiterung in der Regel den IMessageBus
, um benutzerdefinierte Ressourcen oder Daten an den freigegebenen Plattformbus zu übermitteln. Diese Methode kann auch beliebigen prozessexternen Erweiterungen den Abschluss der Testsitzung signalisieren.
Beide APIs akzeptieren zudem ein CancellationToken
, das von der Erweiterung berücksichtigt werden muss.
Wenn Ihre Erweiterung eine intensive Initialisierung erfordert und Sie das async/await-Muster verwenden müssen, finden Sie unter Async extension initialization and cleanup
weitere Informationen. Informationen zum Freigeben des Status zwischen Erweiterungspunkten finden Sie im Abschnitt CompositeExtensionFactory<T>
.
ITestApplicationLifecycleCallbacks
-Erweiterungen
ITestApplicationLifecycleCallbacks
ist eine prozessinterne Erweiterung, die die Ausführung von Code vor allen anderen Funktionen ermöglicht. Sie bietet sozusagen Zugriff auf die erste Zeile der hypothetischen main-Methode des Testhosts.
Verwenden Sie die folgende API, um eine benutzerdefinierte ITestApplicationLifecycleCallbacks
-Erweiterung zu registrieren:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
builder.TestHost.AddTestApplicationLifecycleCallbacks(
static serviceProvider
=> new CustomTestApplicationLifecycleCallbacks());
Die Factory nutzt den IServiceProvider, um Zugriff auf die Sammlung von Diensten zu erhalten, die die Testplattform bietet.
Von Bedeutung
Die Registrierungsreihenfolge ist wichtig, da die APIs in der Reihenfolge aufgerufen werden, in der sie registriert wurden.
Die ITestApplicationLifecycleCallbacks
-Schnittstelle umfasst die folgenden Methoden:
public interface ITestApplicationLifecycleCallbacks : ITestHostExtension
{
Task BeforeRunAsync(CancellationToken cancellationToken);
Task AfterRunAsync(
int exitCode,
CancellationToken cancellation);
}
public interface ITestHostExtension : IExtension
{
}
ITestApplicationLifecycleCallbacks
ist ein Typ von ITestHostExtension
, der als Basis für alle Testhosterweiterungen dient. Wie alle anderen Erweiterungspunkte erbt er ebenfalls von IExtension. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
BeforeRunAsync
: Diese Methode dient als anfänglicher Kontaktpunkt für den Testhost und ist für eine prozessinterne Erweiterung die erste Möglichkeit zum Ausführen eines Features. Sie wird in der Regel verwendet, um eine Verbindung mit entsprechenden prozessexternen Erweiterungen herzustellen, wenn ein Feature für die Ausführung in beiden Umgebungen konzipiert ist.
Das integrierte Feature zum Erstellen eines Speicherabbilds bei Nichtreagieren besteht beispielsweise aus prozessinternen und prozessexternen Erweiterungen, und diese Methode wird verwendet, um Informationen mit der prozessexternen Komponente der Erweiterung auszutauschen.
AfterRunAsync
: Diese Methode ist der letzte Aufruf vor dem Beenden der int ITestApplication.RunAsync()
-Methode und stellt den exit code
bereit. Sie sollte ausschließlich verwendet werden, um Bereinigungsaufgaben durchzuführen und alle entsprechenden prozessexternen Erweiterungen darüber zu benachrichtigen, dass der Testhost beendet wird.
Beide APIs akzeptieren zudem ein CancellationToken
, das von der Erweiterung berücksichtigt werden muss.
IDataConsumer
-Erweiterungen
IDataConsumer
ist eine prozessinterne Erweiterung, die IData
-Informationen abonnieren und empfangen kann, die vom Testframework und den Erweiterungen an den IMessageBus gepusht werden.
Dieser Erweiterungspunkt ist entscheidend, da er Entwicklern das Sammeln und Verarbeiten aller Informationen ermöglicht, die während einer Testsitzung generiert werden.
Verwenden Sie die folgende API, um eine benutzerdefinierte IDataConsumer
-Erweiterung zu registrieren:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
builder.TestHost.AddDataConsumer(
static serviceProvider => new CustomDataConsumer());
Die Factory nutzt den IServiceProvider, um Zugriff auf die Sammlung von Diensten zu erhalten, die die Testplattform bietet.
Von Bedeutung
Die Registrierungsreihenfolge ist wichtig, da die APIs in der Reihenfolge aufgerufen werden, in der sie registriert wurden.
Die IDataConsumer
-Schnittstelle umfasst die folgenden Methoden:
public interface IDataConsumer : ITestHostExtension
{
Type[] DataTypesConsumed { get; }
Task ConsumeAsync(
IDataProducer dataProducer,
IData value,
CancellationToken cancellationToken);
}
public interface IData
{
string DisplayName { get; }
string? Description { get; }
}
IDataConsumer
ist ein Typ von ITestHostExtension
, der als Basis für alle Testhosterweiterungen dient. Wie alle anderen Erweiterungspunkte erbt er ebenfalls von IExtension. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
DataTypesConsumed
: Diese Eigenschaft gibt eine Liste der Typen (Type
) zurück, die diese Erweiterung nutzen will. Sie entspricht IDataProducer.DataTypesProduced
. Insbesondere kann ein IDataConsumer
problemlos mehrere Typen aus verschiedenen IDataProducer
-Instanzen abonnieren.
ConsumeAsync
: Diese Methode wird ausgelöst, wenn Daten eines Typs, den der aktuelle Consumer abonniert hat, an den IMessageBus
gepusht werden. Sie empfängt den IDataProducer
, um Details zum Erzeuger der Datennutzlast sowie die IData
-Nutzdaten selbst bereitzustellen. Wie Sie sehen, ist IData
eine generische Platzhalterschnittstelle, die allgemeine informative Daten enthält. Die Möglichkeit, verschiedene IData
-Typen zu pushen, bedeutet, dass der Consumer den Typ selbst aktivieren muss, um ihn in den richtigen Typ umzuwandeln und auf die spezifischen Informationen zuzugreifen.
Eine Beispielimplementierung eines Consumers, der die von einem TestNodeUpdateMessage
erzeugte auswerten will, könnte wie folgt aussehen:
internal class CustomDataConsumer : IDataConsumer, IOutputDeviceDataProducer
{
public Type[] DataTypesConsumed => new[] { typeof(TestNodeUpdateMessage) };
...
public Task ConsumeAsync(
IDataProducer dataProducer,
IData value,
CancellationToken cancellationToken)
{
var testNodeUpdateMessage = (TestNodeUpdateMessage)value;
switch (testNodeUpdateMessage.TestNode.Properties.Single<TestNodeStateProperty>())
{
case InProgressTestNodeStateProperty _:
{
...
break;
}
case PassedTestNodeStateProperty _:
{
...
break;
}
case FailedTestNodeStateProperty failedTestNodeStateProperty:
{
...
break;
}
case SkippedTestNodeStateProperty _:
{
...
break;
}
...
}
return Task.CompletedTask;
}
...
}
Zu guter Letzt akzeptiert die API ein CancellationToken
, das von der Erweiterung berücksichtigt werden muss.
Von Bedeutung
Es ist entscheidend, die Nutzdaten direkt innerhalb der ConsumeAsync
-Methode zu verarbeiten. Der IMessageBus kann sowohl die synchrone als auch die asynchrone Verarbeitung verwalten und die Ausführung mit dem Testframework koordinieren. Zum Zeitpunkt der Erstellung dieses Dokuments ist der Nutzungs- bzw. Verbrauchsprozess vollständig asynchron und blockiert IMessageBus.Push nicht. Dies ist jedoch ein Implementierungsdetail, das sich aufgrund künftiger Anforderungen in Zukunft ändern kann. Die Plattform stellt jedoch sicher, dass diese Methode immer nur einmal aufgerufen wird, sodass eine komplexe Synchronisierung nicht erforderlich und die Skalierbarkeit der Consumer gewährleistet ist.
Warnung
Wenn Sie IDataConsumer
in Verbindung mit ITestHostProcessLifetimeHandler innerhalb eines zusammengesetzten Erweiterungspunkts verwenden, ist es wichtig, alle Daten zu ignorieren, die nach der Ausführung von ITestSessionLifetimeHandler.OnTestSessionFinishingAsync empfangen werden. Die OnTestSessionFinishingAsync
-Methode ist die letzte Möglichkeit, gesammelte Daten zu verarbeiten und neue Informationen an den IMessageBus zu übermitteln. Daher können alle Daten, die nach diesem Punkt genutzt werden, nicht von der Erweiterung verwendet werden.
Wenn Ihre Erweiterung eine intensive Initialisierung erfordert und Sie das async/await-Muster verwenden müssen, finden Sie unter Async extension initialization and cleanup
weitere Informationen. Informationen zum Freigeben des Status zwischen Erweiterungspunkten finden Sie im Abschnitt CompositeExtensionFactory<T>
.
ITestHostEnvironmentVariableProvider
-Erweiterungen
ITestHostEnvironmentVariableProvider
ist eine prozessexterne Erweiterung, mit der Sie benutzerdefinierte Umgebungsvariablen für den Testhost einrichten können. Die Verwendung dieses Erweiterungspunkts stellt sicher, dass die Testplattform einen neuen Host mit den entsprechenden Umgebungsvariablen initiiert (siehe hierzu den Abschnitt zur Architektur).
Verwenden Sie die folgende API, um eine benutzerdefinierte ITestHostEnvironmentVariableProvider
-Schnittstelle zu registrieren:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
builder.TestHostControllers.AddEnvironmentVariableProvider(
static serviceProvider => new CustomEnvironmentVariableForTestHost());
Die Factory nutzt den IServiceProvider, um Zugriff auf die Sammlung von Diensten zu erhalten, die die Testplattform bietet.
Von Bedeutung
Die Registrierungsreihenfolge ist wichtig, da die APIs in der Reihenfolge aufgerufen werden, in der sie registriert wurden.
Die ITestHostEnvironmentVariableProvider
-Schnittstelle umfasst die folgenden Methoden und Typen:
public interface ITestHostEnvironmentVariableProvider : ITestHostControllersExtension, IExtension
{
Task UpdateAsync(IEnvironmentVariables environmentVariables);
Task<ValidationResult> ValidateTestHostEnvironmentVariablesAsync(
IReadOnlyEnvironmentVariables environmentVariables);
}
public interface IEnvironmentVariables : IReadOnlyEnvironmentVariables
{
void SetVariable(EnvironmentVariable environmentVariable);
void RemoveVariable(string variable);
}
public interface IReadOnlyEnvironmentVariables
{
bool TryGetVariable(
string variable,
[NotNullWhen(true)] out OwnedEnvironmentVariable? environmentVariable);
}
public sealed class OwnedEnvironmentVariable : EnvironmentVariable
{
public IExtension Owner { get; }
public OwnedEnvironmentVariable(
IExtension owner,
string variable,
string? value,
bool isSecret,
bool isLocked);
}
public class EnvironmentVariable
{
public string Variable { get; }
public string? Value { get; }
public bool IsSecret { get; }
public bool IsLocked { get; }
}
ITestHostEnvironmentVariableProvider
ist ein Typ von ITestHostControllersExtension
, der als Basis für alle Testhostcontroller-Erweiterungen dient. Wie alle anderen Erweiterungspunkte erbt er ebenfalls von IExtension. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
Berücksichtigen Sie für diese API die folgenden Details:
UpdateAsync
: Diese Aktualisierungs-API stellt eine Instanz des IEnvironmentVariables
-Objekts bereit, über die Sie die Methoden SetVariable
oder RemoveVariable
aufrufen können. Wenn Sie SetVariable
verwenden, müssen Sie ein Objekt vom Typ EnvironmentVariable
übergeben, das die folgenden Angaben erfordert:
-
Variable
: Der Name der Umgebungsvariable. -
Value
: Der Wert der Umgebungsvariable. -
IsSecret
: Gibt an, ob die Umgebungsvariable vertrauliche Informationen enthält, die nicht protokolliert werden oder nicht überTryGetVariable
zugänglich sein sollen. -
IsLocked
: Legt fest, ob andereITestHostEnvironmentVariableProvider
-Erweiterungen diesen Wert ändern können.
ValidateTestHostEnvironmentVariablesAsync
: Diese Methode wird aufgerufen, nachdem alle UpdateAsync
-Methoden der registrierten ITestHostEnvironmentVariableProvider
-Instanzen aufgerufen wurden. Sie ermöglicht es Ihnen, die korrekte Einrichtung der Umgebungsvariablen zu überprüfen. Sie akzeptiert ein Objekt, das die IReadOnlyEnvironmentVariables
-Schnittstelle implementiert, die die TryGetVariable
-Methode zum Abrufen bestimmter Umgebungsvariableninformationen mit dem OwnedEnvironmentVariable
-Objekttyp bereitstellt. Nach der Überprüfung geben Sie ein ValidationResult
zurück, das alle Fehlerursachen enthält.
Hinweis
Die Testplattform implementiert und registriert standardmäßig den SystemEnvironmentVariableProvider
. Dieser Anbieter lädt alle aktuellen Umgebungsvariablen. Als erster registrierter Anbieter wird er zuerst ausgeführt und gewährt allen anderen ITestHostEnvironmentVariableProvider
-Benutzererweiterungen den Zugriff auf die Standardumgebungsvariablen.
Wenn Ihre Erweiterung eine intensive Initialisierung erfordert und Sie das async/await-Muster verwenden müssen, finden Sie unter Async extension initialization and cleanup
weitere Informationen. Informationen zum Freigeben des Status zwischen Erweiterungspunkten finden Sie im Abschnitt CompositeExtensionFactory<T>
.
ITestHostProcessLifetimeHandler
-Erweiterungen
ITestHostProcessLifetimeHandler
ist eine prozessexterne Erweiterung, mit der Sie den Testhostprozess aus externer Sicht beobachten können. Dadurch wird sichergestellt, dass sich durch den getesteten Code verursachte mögliche Abstürze oder ein Hängenbleiben nicht auf Ihre Erweiterung auswirken. Wenn Sie diesen Erweiterungspunkt verwenden, wird die Testplattform aufgefordert, einen neuen Host zu initiieren (siehe hierzu den Abschnitt zur Architektur).
Verwenden Sie die folgende API, um eine benutzerdefinierte ITestHostProcessLifetimeHandler
-Schnittstelle zu registrieren:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
builder.TestHostControllers.AddProcessLifetimeHandler(
static serviceProvider => new CustomMonitorTestHost());
Die Factory nutzt den IServiceProvider, um Zugriff auf die Sammlung von Diensten zu erhalten, die die Testplattform bietet.
Von Bedeutung
Die Registrierungsreihenfolge ist wichtig, da die APIs in der Reihenfolge aufgerufen werden, in der sie registriert wurden.
Die ITestHostProcessLifetimeHandler
-Schnittstelle umfasst die folgenden Methoden:
public interface ITestHostProcessLifetimeHandler : ITestHostControllersExtension
{
Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken);
Task OnTestHostProcessStartedAsync(
ITestHostProcessInformation testHostProcessInformation,
CancellationToken cancellation);
Task OnTestHostProcessExitedAsync(
ITestHostProcessInformation testHostProcessInformation,
CancellationToken cancellation);
}
public interface ITestHostProcessInformation
{
int PID { get; }
int ExitCode { get; }
bool HasExitedGracefully { get; }
}
ITestHostProcessLifetimeHandler
ist ein Typ von ITestHostControllersExtension
, der als Basis für alle Testhostcontroller-Erweiterungen dient. Wie alle anderen Erweiterungspunkte erbt er ebenfalls von IExtension. Daher können Sie diese Erweiterung wie jede andere mithilfe der IExtension.IsEnabledAsync
-API aktivieren oder deaktivieren.
Beachten Sie für diese API die folgenden Details:
BeforeTestHostProcessStartAsync
: Diese Methode wird aufgerufen, bevor die Testplattform die Testhosts initiiert.
OnTestHostProcessStartedAsync
: Diese Methode wird unmittelbar nach dem Start des Testhosts aufgerufen. Diese Methode bietet ein Objekt, das die ITestHostProcessInformation
-Schnittstelle implementiert, die wichtige Details zum Testhostprozessergebnis bereitstellt.
Von Bedeutung
Durch den Aufruf dieser Methode wird die Ausführung des Testhosts nicht angehalten. Wenn Sie die Ausführung anhalten müssen, sollten Sie eine prozessinterne Erweiterung wie ITestApplicationLifecycleCallbacks
registrieren und sie mit der prozessexternen Erweiterung synchronisieren.
OnTestHostProcessExitedAsync
: Diese Methode wird aufgerufen, wenn die Ausführung der Testsammlung abgeschlossen ist. Sie stellt ein Objekt bereit, das der ITestHostProcessInformation
-Schnittstelle entspricht, die wichtige Details zum Ergebnis des Testhostprozesses übermittelt.
Die ITestHostProcessInformation
-Schnittstelle stellt die folgenden Details bereit:
-
PID
: Die Prozess-ID des Testhosts. -
ExitCode
: Der Exitcode des Prozesses. Dieser Wert ist nur in derOnTestHostProcessExitedAsync
-Methode verfügbar. Der Versuch, innerhalb derOnTestHostProcessStartedAsync
-Methode darauf zuzugreifen, führt zu einer Ausnahme. -
HasExitedGracefully
: Ein boolescher Wert, der angibt, ob der Testhost abgestürzt ist. Wenn der Wert „true” ist, wurde der Testhost nicht ordnungsgemäß beendet.
Ausführungsreihenfolge der Erweiterungen
Die Testplattform besteht aus einem Testframework und einer beliebigen Anzahl von Erweiterungen, die prozessintern oder prozessextern ausgeführt werden können. In diesem Dokument wird die Reihenfolge der Aufrufe aller möglichen Erweiterungspunkte beschrieben, um zu verdeutlichen, wann der Aufruf eines Features erwartet wird:
- ITestHostEnvironmentVariableProvider.UpdateAsync: Prozessextern
- ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync: Prozessextern
- ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync: Prozessextern
- Start des Testhostprozesses
- ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync: Prozessextern; dieses Ereignis kann die Aktionen von prozessinternen Erweiterungen abhängig von Racebedingungen miteinander verknüpfen.
- ITestApplicationLifecycleCallbacks.BeforeRunAsync: Prozessintern
- ITestSessionLifetimeHandler.OnTestSessionStartingAsync: Prozessintern
- ITestFramework.CreateTestSessionAsync: Prozessintern
- ITestFramework.ExecuteRequestAsync: Prozessintern; diese Methode kann einmal oder mehrmals aufgerufen werden. An diesem Punkt übermittelt das Testframework Informationen an den IMessageBus, die vom IDataConsumer genutzt werden können.
- ITestFramework.CloseTestSessionAsync: Prozessintern
- ITestSessionLifetimeHandler.OnTestSessionFinishingAsync: Prozessintern
- ITestApplicationLifecycleCallbacks.AfterRunAsync: Prozessintern
- Die prozessinterne Bereinigung umfasst das Aufrufen von „dispose” und IAsyncCleanableExtension für alle Erweiterungspunkte.
- ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync: Prozessextern
- Die prozessexterne Bereinigung umfasst das Aufrufen von „dispose” und IAsyncCleanableExtension für alle Erweiterungspunkte.
Hilfsprogramme für Erweiterungen
Die Testplattform bietet eine Reihe von Hilfsklassen und -schnittstellen, um die Implementierung von Erweiterungen zu vereinfachen. Diese Hilfsprogramme beschleunigen den Entwicklungsprozess und stellen sicher, dass die Erweiterung den Standards der Plattform entspricht.
Asynchrone Initialisierung und Bereinigung von Erweiterungen
Die Erstellung des Testframeworks und der Erweiterungen über Factorys entspricht dem standardmäßigen .NET-Objekterstellungsmechanismus, bei dem synchrone Konstruktoren verwendet werden. Wenn eine Erweiterung eine intensive Initialisierung erfordert (z. B. mit Zugriff auf das Dateisystem oder Netzwerk), kann sie das async/await-Muster nicht im Konstruktor verwenden, da Konstruktoren „void” zurückgeben, nicht Task
.
Daher stellt die Testplattform eine Methode zum Initialisieren einer Erweiterung mit dem async/await-Muster über eine einfache Schnittstelle bereit. Aus Gründen der Symmetrie bietet sie auch eine asynchrone Schnittstelle für die Bereinigung, die Erweiterungen nahtlos implementieren können.
public interface IAsyncInitializableExtension
{
Task InitializeAsync();
}
public interface IAsyncCleanableExtension
{
Task CleanupAsync();
}
IAsyncInitializableExtension.InitializeAsync
: Diese Methode wird immer nach der Erstellungsfactory aufgerufen.
IAsyncCleanableExtension.CleanupAsync
: Diese Methode wird immer mindestens einmal während der Beendigung der Testsitzung vor dem standardmäßigen DisposeAsync
- oder Dispose
-Aufruf aufgerufen.
Von Bedeutung
Wie die Dispose
-Standardmethode kann CleanupAsync
mehrmals aufgerufen werden. Wenn die CleanupAsync
-Methode eines Objekts mehrmals aufgerufen wird, muss das Objekt alle Aufrufe nach dem ersten Aufruf ignorieren. Das Objekt darf keine Ausnahme auslösen, wenn seine CleanupAsync
-Methode mehrmals aufgerufen wird.
Hinweis
Standardmäßig ruft die Testplattform die DisposeAsync
-Methode auf, sofern diese verfügbar ist, oder die Dispose
-Methode, wenn diese implementiert ist. Beachten Sie, dass die Testplattform nicht beide dispose-Methoden aufruft, sondern die asynchrone Methode priorisiert, sofern diese implementiert ist.
CompositeExtensionFactory<T>
Wie im Abschnitt zu den Erweiterungen beschrieben, ermöglicht Ihnen die Testplattform das Implementieren von Schnittstellen, um sowohl prozessinterne als auch prozessexterne benutzerdefinierte Erweiterungen zu integrieren.
Jede Schnittstelle stellt ein bestimmtes Feature bereit, und gemäß dem .NET-Entwurf implementieren Sie diese Schnittstelle in einem spezifischen Objekt. Wie in den entsprechenden Abschnitten beschrieben, können Sie die Erweiterung selbst mithilfe der spezifischen Registrierungs-API AddXXX
über das TestHost
- oder TestHostController
-Objekt von ITestApplicationBuilder
registrieren.
Wenn Sie zwischen zwei Erweiterungen den Status freigeben müssen, wird die Freigabe jedoch durch die Tatsache erschwert, dass Sie unterschiedliche Objekte implementieren und registrieren können, die verschiedene Schnittstellen implementieren. Ohne jegliche Unterstützung würden Sie eine Möglichkeit benötigen, eine Erweiterung an die andere weiterzugeben, um Informationen auszutauschen, was den Entwurf verkompliziert.
Die Testplattform bietet daher eine hochentwickelte Methode zum Implementieren mehrerer Erweiterungspunkte mit demselben Typ, die die Datenfreigabe vereinfacht. Dazu müssen Sie lediglich die CompositeExtensionFactory<T>
verwenden, die dann mit der gleichen API wie eine einzelne Schnittstellenimplementierung registriert werden kann.
Angenommen, Sie haben einen Typ, der sowohl ITestSessionLifetimeHandler
als auch IDataConsumer
implementiert. Dies ist ein gängiges Szenario, da Sie häufig Informationen vom Testframework sammeln und Ihr Artefakt dann beim Abschluss der Testsitzung mithilfe des IMessageBus
innerhalb von ITestSessionLifetimeHandler.OnTestSessionFinishingAsync
senden möchten.
Dazu implementieren Sie normalerweise die Schnittstellen:
internal class CustomExtension : ITestSessionLifetimeHandler, IDataConsumer, ...
{
...
}
Nachdem Sie die CompositeExtensionFactory<CustomExtension>
für Ihren Typ erstellt haben, können Sie sie mit den APIs IDataConsumer
und ITestSessionLifetimeHandler
registrieren, die eine Überladung für die CompositeExtensionFactory<T>
bieten:
var builder = await TestApplication.CreateBuilderAsync(args);
// ...
var factory = new CompositeExtensionFactory<CustomExtension>(serviceProvider => new CustomExtension());
builder.TestHost.AddTestSessionLifetimeHandle(factory);
builder.TestHost.AddDataConsumer(factory);
Der Factorykonstruktor verwendet den IServiceProvider, um auf die von der Testplattform bereitgestellten Dienste zuzugreifen.
Die Testplattform ist für die Verwaltung des Lebenszyklus der zusammengesetzten Erweiterung verantwortlich.
Da die Testplattform sowohl prozessinterne als auch prozessexterne Erweiterungen unterstützt, ist es nicht möglich, Erweiterungspunkte beliebig zu kombinieren. Die Erstellung und Verwendung von Erweiterungen ist vom Hosttyp abhängig, d. h. Sie können nur prozessinterne (TestHost) und prozessexterne (TestHostController) Erweiterungen gruppieren.
Folgende Kombinationen sind möglich:
- Für
ITestApplicationBuilder.TestHost
können SieIDataConsumer
undITestSessionLifetimeHandler
kombinieren. - Für
ITestApplicationBuilder.TestHostControllers
können SieITestHostEnvironmentVariableProvider
undITestHostProcessLifetimeHandler
kombinieren.