Freigeben über


Microsoft.Testing.Platform Services

Die Testplattform bietet wertvolle Dienste sowohl für das Testframework als auch für Erweiterungspunkte. Diese Dienste erfüllen allgemeine Anforderungen wie den Zugriff auf die Konfiguration, das Analysieren und Abrufen von Befehlszeilenargumenten, das Abrufen der Protokollierungsinstanz und den Zugriff auf das Protokollierungssystem sowie weitere Aufgaben. IServiceProvider verwendet das Service-Locator-Muster für die Testplattform.

Dies IServiceProvider wird direkt von der Basisklassenbibliothek abgeleitet.

namespace System
{
    public interface IServiceProvider
    {
        object? GetService(Type serviceType);
    }
}

Die Testplattform bietet praktische Erweiterungsmethoden für den Zugriff auf bekannte Dienstobjekte. Alle diese Methoden werden in einer statischen Klasse innerhalb des Microsoft.Testing.Platform.Services Namespaces untergebracht.

public static class ServiceProviderExtensions
{
    public static TService GetRequiredService<TService>(
        this IServiceProvider provider)

    public static TService? GetService<TService>(
        this IServiceProvider provider)

    public static IMessageBus GetMessageBus(
        this IServiceProvider serviceProvider)

    public static IConfiguration GetConfiguration(
        this IServiceProvider serviceProvider)

    public static ICommandLineOptions GetCommandLineOptions(
        this IServiceProvider serviceProvider)

    public static ILoggerFactory GetLoggerFactory(
        this IServiceProvider serviceProvider)

    public static IOutputDevice GetOutputDevice(
        this IServiceProvider serviceProvider)

    // ... and more
}

Die meisten Registrierungsfactorys, die von Erweiterungspunkten angeboten werden, bieten Zugriff auf die IServiceProvider: Wenn Sie beispielsweise das Testing Framework registrieren, wird IServiceProvider als Parameter an die Factory-Methode übergeben.

ITestApplicationBuilder RegisterTestFramework(
    Func<IServiceProvider, ITestFrameworkCapabilities> capabilitiesFactory,
    Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework> adapterFactory);

Im vorherigen Code geben sowohl das capabilitiesFactory als auch das adapterFactory das IServiceProvider als Parameter an.

Der IConfiguration Dienst

Die IConfiguration-Schnittstelle kann mithilfe von IServiceProvider abgerufen werden und bietet Zugriff auf die Konfigurationseinstellungen des Testframeworks sowie aller Erweiterungspunkte. Standardmäßig werden diese Konfigurationen von den folgenden Quellen geladen:

  • Umgebungsvariablen
  • Eine JSON-Datei mit dem Namen [assemblyName].testingplatformconfig.json, die sich in der Nähe der Einstiegspunkt-Assembly befindet.

Die Reihenfolge der Rangfolge wird beibehalten, was bedeutet, dass die JSON-Datei nicht verarbeitet wird, wenn eine Konfiguration in den Umgebungsvariablen gefunden wird.

Die Schnittstelle ist ein simples Schlüssel-Wert-Paar von Zeichenfolgen.

public interface IConfiguration
{
    string? this[string key] { get; }
}

JSON-Konfigurationsdatei

Die JSON-Datei folgt einer hierarchischen Struktur. Um auf untergeordnete Eigenschaften zuzugreifen, müssen Sie das Trennzeichen : verwenden. Betrachten Sie z. B. eine Konfiguration für ein potenzielles Testframework wie:

{
  "CustomTestingFramework": {
    "DisableParallelism": true
  }
}

Der Codeausschnitt sieht ungefähr wie folgt aus:

IServiceProvider serviceProvider = null; // Get the service provider...

var configuration = serviceProvider.GetConfiguration();

if (bool.TryParse(configuration["CustomTestingFramework:DisableParallelism"], out var value) && value is true)
{
    // ...
}

Im Falle eines Arrays, wie zum Beispiel:

{
  "CustomTestingFramework": {
    "Engine": [
      "ThreadPool",
      "CustomThread"
    ]
  }
}

Die Syntax für den Zugriff auf das erste Element ("ThreadPool") lautet:

IServiceProvider serviceProvider = null; // Get the service provider...

var configuration = serviceProvider.GetConfiguration();

var fistElement = configuration["CustomTestingFramework:Engine:0"];

Umgebungsvariablen

Das Trennzeichen : funktioniert nicht mit hierarchischen Schlüsseln der Umgebungsvariablen auf allen Plattformen. __, der doppelte Unterstrich, ist:

  • Unterstützt von allen Plattformen. Das Trennzeichen : wird beispielsweise nicht von Bash unterstützt, __ hingegen schon.
  • automatisch durch : ersetzt.

Die Umgebungsvariable kann z. B. wie folgt festgelegt werden (dieses Beispiel gilt für Windows):

setx CustomTestingFramework__DisableParallelism=True

Sie können sich entscheiden, die Umgebungsvariablen-Konfigurationsquelle nicht zu verwenden, wenn Sie das ITestApplicationBuilder erstellen:

var options = new TestApplicationOptions();

options.Configuration.ConfigurationSources.RegisterEnvironmentVariablesConfigurationSource = false;

var builder = await TestApplication.CreateBuilderAsync(args, options);

Der ICommandLineOptions Dienst

Der ICommandLineOptions Dienst wird verwendet, um Details zu den Befehlszeilenoptionen abzurufen, die die Plattform analysiert hat. Die verfügbaren APIs umfassen:

public interface ICommandLineOptions
{
    bool IsOptionSet(string optionName);

    bool TryGetOptionArgumentList(
        string optionName, 
        out string[]? arguments);
}

Dies ICommandLineOptions kann über bestimmte APIs abgerufen werden, z. B. den ICommandLineOptionsProvider, oder Sie können eine Instanz davon über die Erweiterungsmethode aus dem serviceProvider.GetCommandLineOptions() abrufen.

ICommandLineOptions.IsOptionSet(string optionName): Mit dieser Methode können Sie überprüfen, ob eine bestimmte Option angegeben wurde. Wenn Sie das optionNamePräfix angeben, wird das -- Präfix weggelassen. Wenn der Benutzer zum Beispiel --myOption eingibt, sollten Sie einfach myOption übergeben.

ICommandLineOptions.TryGetOptionArgumentList(string optionName, out string[]? arguments): Mit dieser Methode können Sie überprüfen, ob eine bestimmte Option festgelegt wurde, und wenn ja, den entsprechenden Wert oder die entsprechenden Werte abrufen (wenn die Arität mehr als eine ist). Ähnlich wie im vorherigen Fall sollte die optionName Angabe ohne Präfix -- erfolgen.

Der ILoggerFactory Dienst

Die Testplattform verfügt über ein integriertes Protokollierungssystem, das eine Protokolldatei generiert. Sie können die Protokollierungsoptionen anzeigen, indem Sie den --help Befehl ausführen. Zu den Optionen, die Sie auswählen können, gehören:

--diagnostic                             Enable the diagnostic logging. The default log level is 'Trace'. The file will be written in the output directory with the name log_[MMddHHssfff].diag
--diagnostic-filelogger-synchronouswrite Force the built-in file logger to write the log synchronously. Useful for scenario where you don't want to lose any log (i.e. in case of crash). Note that this is slowing down the test execution.
--diagnostic-output-directory            Output directory of the diagnostic logging, if not specified the file will be generated inside the default 'TestResults' directory.
--diagnostic-output-fileprefix           Prefix for the log file name that will replace '[log]_.'
--diagnostic-verbosity                   Define the level of the verbosity for the --diagnostic. The available values are 'Trace', 'Debug', 'Information', 'Warning', 'Error', and 'Critical'

Vom Standpunkt des Codes aus betrachtet, müssen Sie, um Informationen zu protokollieren, die ILoggerFactory von der IServiceProvider abrufen. Die ILoggerFactory API lautet wie folgt:

public interface ILoggerFactory
{
    ILogger CreateLogger(string categoryName);
}

public static class LoggerFactoryExtensions
{
    public static ILogger<TCategoryName> CreateLogger<TCategoryName>(this ILoggerFactory factory);
}

Mit der Loggerfactory können Sie ein ILogger Objekt mithilfe der CreateLogger API erstellen. Es gibt auch eine praktische API, die ein generisches Argument akzeptiert, das als Kategoriename verwendet wird.

public interface ILogger
{
    Task LogAsync<TState>(
        LogLevel logLevel, 
        TState state, 
        Exception? exception, 
        Func<TState, Exception?, string> formatter);

    void Log<TState>(
        LogLevel logLevel,
        TState state, 
        Exception? exception, 
        Func<TState, Exception?, string> formatter);

    bool IsEnabled(LogLevel logLevel);
}

public interface ILogger<out TCategoryName> : ILogger
{
}

public static class LoggingExtensions
{
    public static Task LogCriticalAsync(this ILogger logger, string message);
    public static Task LogDebugAsync(this ILogger logger, string message);
    public static Task LogErrorAsync(this ILogger logger, Exception ex);
    public static Task LogErrorAsync(this ILogger logger, string message, Exception ex);
    public static Task LogErrorAsync(this ILogger logger, string message);
    public static Task LogInformationAsync(this ILogger logger, string message);
    public static Task LogTraceAsync(this ILogger logger, string message);
    public static Task LogWarningAsync(this ILogger logger, string message);
    public static void LogCritical(this ILogger logger, string message);
    public static void LogDebug(this ILogger logger, string message);
    public static void LogError(this ILogger logger, Exception ex);
    public static void LogError(this ILogger logger, string message, Exception ex);
    public static void LogError(this ILogger logger, string message);
    public static void LogInformation(this ILogger logger, string message);
    public static void LogTrace(this ILogger logger, string message);
    public static void LogWarning(this ILogger logger, string message);
}

Das ILogger Objekt, das durch das ILoggerFactoryObjekt erstellt wird, bietet APIs zum Protokollieren von Informationen auf verschiedenen Ebenen. Diese Protokollierungsebenen umfassen:

public enum LogLevel
{
    Trace,
    Debug,
    Information,
    Warning,
    Error,
    Critical,
    None,
}

Hier ist ein Beispiel für die Verwendung der Protokollierungs-API:

...
IServiceProvider provider = null; // Get the service provider...

var factory = provider.GetLoggerFactory();

var logger = factory.CreateLogger<TestingFramework>();

// ...

if (logger.IsEnabled(LogLevel.Information))
{
    await logger.LogInformationAsync(
        $"Executing request of type '{context.Request}'");
}

// ...

Denken Sie daran, dass Sie, um unnötige Zuordnungen zu verhindern, mithilfe der API überprüfen sollten, ob das Level ILogger.IsEnabled(LogLevel) ist.

Der IMessageBus Dienst

Der Nachrichtenbusdienst ist der zentrale Mechanismus, der den Informationsaustausch zwischen dem Testframework und seinen Erweiterungen erleichtert.

Der Nachrichtenbus der Testplattform verwendet das Publish-Subscribe-Muster.

Die übergeordnete Struktur des gemeinsam genutzten Busses lautet wie folgt:

Ein Bild, das die Interaktionen der verschiedenen Erweiterungen mit dem Nachrichtenbus darstellt.

Wie im Diagramm dargestellt, das erweiterungen und ein Testframework enthält, gibt es zwei mögliche Aktionen: Das Pushen von Informationen an den Bus oder die Nutzung von Informationen vom Bus.

Das IMessageBus erfüllt die Pushes-Aktion an den Bus und die API ist:

public interface IMessageBus
{
    Task PublishAsync(
        IDataProducer dataProducer, 
        IData data);
}

public interface IDataProducer : IExtension
{
    Type[] DataTypesProduced { get; }
}

public interface IData
{
    string DisplayName { get; }
    string? Description { get; }
}

Berücksichtigen Sie die folgenden Details zu den Parametern:

  • IDataProducer: Der IDataProducer teilt dem Nachrichtenbus die Type Informationen mit, die er liefern kann, und legt die Eigentümerschaft durch Vererbung von der Basisschnittstelle IExtension fest. Dies bedeutet, dass Sie daten nicht diskriminierend an den Nachrichtenbus übertragen können. Sie müssen den im Voraus erstellten Datentyp deklarieren. Wenn Sie unerwartete Daten übertragen, wird eine Ausnahme ausgelöst.

  • IData: Diese Schnittstelle dient als Platzhalter, in dem Sie nur beschreibende Details wie den Namen und eine Beschreibung angeben müssen. Die Schnittstelle offenbart absichtlich nicht viel über die Natur der Daten. Es bedeutet, dass das Testframework und Erweiterungen jede Art von Daten an den Bus übertragen können, und diese Daten können von jeder registrierten Erweiterung oder dem Testframework selbst genutzt werden.

Dieser Ansatz erleichtert die Weiterentwicklung des Informationsaustauschprozesses und verhindert unterbrechungslose Änderungen, wenn eine Erweiterung mit neuen Daten nicht vertraut ist. Es ermöglicht verschiedene Versionen von Erweiterungen und das Testframework, auf der Grundlage ihres gegenseitigen Verständnisses in Harmonie zu arbeiten.

Das gegenteilige Ende des Busses wird als Verbraucher bezeichnet, der einen bestimmten Datentyp abonniert und somit nutzen kann.

Von Bedeutung

Verwenden Sie immer await für den Aufruf von PublishAsync. Wenn Sie dies nicht tun, könnte IData möglicherweise nicht korrekt von der Testplattform und den Erweiterungen verarbeitet werden, was zu subtilen Fehlern führen könnte. Erst nachdem Sie von await zurückgekehrt sind, können Sie sicher sein, dass die IData zur Verarbeitung auf dem Nachrichtenbus in die Warteschlange gestellt wurde. Unabhängig von dem Erweiterungspunkt, an dem Sie arbeiten, stellen Sie sicher, dass Sie alle PublishAsync Aufrufe abgewartet haben, bevor Sie die Erweiterung beenden. Wenn Sie zum Beispiel testing framework implementieren, sollten Sie Complete für Anfragen erst dann aufrufen, wenn Sie alle PublishAsyncAufrufe für diese spezielle Anfrage awaitiert haben.

Der IOutputDevice Dienst

Die Testplattform kapselt die Idee eines Ausgabegeräts, sodass das Testframework und Erweiterungen Informationen darstellen können, indem jede Art von Daten an das aktuell genutzte Anzeigesystem übertragen wird.

Das traditionellste Beispiel für ein Ausgabegerät ist die Konsolenausgabe.

Hinweis

Während die Testplattform entwickelt wird, um benutzerdefinierte Ausgabegeräte zu unterstützen, ist dieser Erweiterungspunkt derzeit nicht verfügbar.

Um Daten an das Ausgabegerät zu übertragen, müssen Sie die IOutputDevice von der IServiceProvider abrufen.

Die API besteht aus:

public interface IOutputDevice
{
    Task DisplayAsync(
        IOutputDeviceDataProducer producer, 
        IOutputDeviceData data);
}

public interface IOutputDeviceDataProducer : IExtension
{
}

public interface IOutputDeviceData
{
}

IOutputDeviceDataProducer erweitert IExtension und stellt dem Ausgabegerät Informationen über den Absender bereit.

Das IOutputDeviceData dient als Platzhalterschnittstelle. Das Dahinter IOutputDevice liegende Konzept besteht darin, komplexere Informationen als nur farbigen Text aufzunehmen. Beispielsweise könnte es sich um ein komplexes Objekt handeln, das grafisch dargestellt werden kann.

Die Testplattform bietet standardmäßig ein herkömmliches farbiges Textmodell für das IOutputDeviceData Objekt:

public class TextOutputDeviceData : IOutputDeviceData
{
    public TextOutputDeviceData(string text)
    public string Text { get; }
}

public sealed class FormattedTextOutputDeviceData : TextOutputDeviceData
{
    public FormattedTextOutputDeviceData(string text)
    public IColor? ForegroundColor { get; init; }
    public IColor? BackgroundColor { get; init; }
}

public sealed class SystemConsoleColor : IColor
{
    public ConsoleColor ConsoleColor { get; init; }
}

Hier ist ein Beispiel dafür, wie Sie den farbigen Text mit dem aktiven Ausgabegerät verwenden können:

IServiceProvider provider = null; // Get the service provider...

var outputDevice = provider.GetOutputDevice();

await outputDevice.DisplayAsync(
    this, 
    new FormattedTextOutputDeviceData($"TestingFramework version '{Version}' running tests with parallelism of {_dopValue}")
    {
        ForegroundColor = new SystemConsoleColor
        {
            ConsoleColor = ConsoleColor.Green
        }
    });

Über die standardmäßige Verwendung von farbigem Text hinaus ist der Hauptvorteil von IOutputDevice und IOutputDeviceData, dass das Ausgabegerät völlig unabhängig ist und dem Benutzer unbekannt bleibt. Dies ermöglicht die Entwicklung komplexer Benutzeroberflächen. Beispielsweise ist es völlig machbar, eine Webanwendung in Echtzeit zu implementieren, die den Fortschritt von Tests anzeigt.

Der IPlatformInformation Dienst

Stellt Informationen zur Plattform bereit, z. B. Name, Version, Commit-Hash und Builddatum.