Condividi tramite


Servizi Microsoft.Testing.Platform

La piattaforma di test offre servizi preziosi sia al framework di test che ai punti di estensione. Questi servizi soddisfano esigenze comuni, come l'accesso alla configurazione, l'analisi e il recupero degli argomenti della riga di comando, l'acquisizione della fabbrica di log e l'accesso al sistema di registrazione, tra gli altri. IServiceProvider implementa il modello di localizzatore di servizi per la piattaforma di test.

Il IServiceProvider è derivato direttamente dalla libreria di classi di base.

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

La piattaforma di test offre metodi di estensione utili per accedere a oggetti di servizio noti. Tutti questi metodi sono ospitati in una classe statica all'interno dello spazio dei nomi Microsoft.Testing.Platform.Services.

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
}

La maggior parte delle fabbriche di registrazione esposte dai punti di estensione fornisce accesso al IServiceProvider: ad esempio, quando si registra il framework di test, il IServiceProvider viene passato come parametro al metodo di fabbrica.

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

Nel codice precedente, sia il capabilitiesFactory che il adapterFactory forniscono il IServiceProvider come parametro.

Servizio IConfiguration

L'interfaccia IConfiguration può essere recuperata usando il IServiceProvider e fornisce access alle impostazioni di configurazione per il framework di test ed eventuali punti di estensione. Per impostazione predefinita, queste configurazioni vengono caricate da:

  • Variabili di ambiente
  • File JSON denominato [assemblyName].testingplatformconfig.json situato vicino all'assembly del punto di ingresso.

L'ordine di precedenza viene mantenuto, il che significa che se viene trovata una configurazione nelle variabili di ambiente, il file JSON non verrà elaborato.

L'interfaccia è una semplice coppia chiave-valore di stringhe:

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

File di configurazione JSON

Il file JSON segue una struttura gerarchica. Per accedere alle proprietà figlio, è necessario usare il separatore :. Si consideri ad esempio una configurazione per un potenziale framework di test, ad esempio:

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

Il frammento di codice sarà simile al seguente:

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

var configuration = serviceProvider.GetConfiguration();

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

Nel caso di una matrice, ad esempio:

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

La sintassi per accedere al primo elemento ("ThreadPool") è:

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

var configuration = serviceProvider.GetConfiguration();

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

Variabili di ambiente

Il separatore : non funziona con le chiavi gerarchica delle variabili di ambiente in tutte le piattaforme. __, il doppio carattere di sottolineatura è:

  • Supportato da tutte le piattaforme. Ad esempio, il separatore di : non è supportato da Bash, ma __ è .
  • Sostituito automaticamente da un :

Ad esempio, la variabile di ambiente può essere impostata come segue (questo esempio è applicabile per Windows):

setx CustomTestingFramework__DisableParallelism=True

È possibile scegliere di non usare l'origine di configurazione delle variabili di ambiente durante la creazione del ITestApplicationBuilder:

var options = new TestApplicationOptions();

options.Configuration.ConfigurationSources.RegisterEnvironmentVariablesConfigurationSource = false;

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

Servizio ICommandLineOptions

Il servizio ICommandLineOptions viene utilizzato per recuperare i dettagli relativi alle opzioni della riga di comando analizzate dalla piattaforma. Le API disponibili includono:

public interface ICommandLineOptions
{
    bool IsOptionSet(string optionName);

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

Il ICommandLineOptions può essere ottenuto tramite determinate API, ad esempio ICommandLineOptionsProvideroppure è possibile recuperarne un'istanza dal IServiceProvider tramite il metodo di estensione serviceProvider.GetCommandLineOptions().

ICommandLineOptions.IsOptionSet(string optionName): questo metodo consente di verificare se è stata specificata un'opzione specifica. Quando si specifica il optionName, omettere il prefisso --. Ad esempio, se l'utente inserisce --myOption, è sufficiente passare myOption.

ICommandLineOptions.TryGetOptionArgumentList(string optionName, out string[]? arguments): questo metodo consente di verificare se è stata impostata un'opzione specifica e, in tal caso, recuperare il valore o i valori corrispondenti (se l'arity è più di una). Analogamente al caso precedente, il optionName deve essere fornito senza il prefisso --.

Servizio ILoggerFactory

La piattaforma di test include un sistema di registrazione integrato che genera un file di log. È possibile visualizzare le opzioni di registrazione eseguendo il comando --help. Le opzioni tra cui scegliere includono:

--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-synchronous-write 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-file-prefix           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'

Dal punto di vista del codice, per registrare le informazioni, è necessario ottenere il ILoggerFactory dal IServiceProvider. L'API ILoggerFactory è la seguente:

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

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

La fabbrica del logger consente di creare un oggetto ILogger usando l'API CreateLogger. È disponibile anche un'API utile che accetta un argomento generico, che verrà usato come nome di categoria.

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);
}

L'oggetto ILogger, creato dal ILoggerFactory, offre API per la registrazione delle informazioni a vari livelli. Questi livelli di registrazione includono:

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

Ecco un esempio di come usare l'API di registrazione:

...
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}'");
}

// ...

Tenere presente che per evitare l'allocazione non necessaria, è necessario verificare se il livello è abilitato usando l'API ILogger.IsEnabled(LogLevel).

Servizio IMessageBus

Il servizio di bus dei messaggi è il meccanismo centrale che facilita lo scambio di informazioni tra il framework di test e le relative estensioni.

Il bus di messaggio della piattaforma di test usa il modello di publish-subscribe.

La struttura generale del bus condiviso è la seguente:

Immagine che rappresenta le interazioni delle varie estensioni con il bus di messaggi.

Come illustrato nel diagramma, che include estensioni e un framework di test, esistono due azioni potenziali: push di informazioni sul bus o utilizzo di informazioni dal bus.

Il IMessageBus ha soddisfatto l'azione di spinta verso il bus e l'API è:

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; }
}

Considerare i dettagli seguenti sui parametri:

  • IDataProducer: il IDataProducer comunica al bus dei messaggi il Type di informazioni che può fornire e stabilisce la titolarità tramite ereditarietà dall'interfaccia di base IExtension. Ciò implica che non è possibile eseguire il push indiscriminato dei dati nel bus di messaggi; è necessario dichiarare il tipo di dati prodotto in anticipo. Se si esegue il push di dati imprevisti, verrà attivata un'eccezione.

  • IData: questa interfaccia funge da segnaposto in cui è sufficiente fornire dettagli descrittivi, ad esempio il nome e una descrizione. L'interfaccia non rivela molto sulla natura dei dati, che è intenzionale. Implica che il framework di test e le estensioni possono eseguire il push di qualsiasi tipo di dati nel bus e questi dati possono essere utilizzati da qualsiasi estensione registrata o dal framework di test stesso.

Questo approccio favorisce l'evoluzione del processo di scambio di informazioni, impedendo cambiamenti critici quando un'estensione non è familiare con i nuovi dati. Consente a versioni diverse di estensioni e framework di test di operare in armonia, in base alla loro comprensione reciproca.

L'estremità opposta del bus viene definita consumer, che è iscritto a un tipo specifico di dati e può quindi consumarli.

Importante

Si consiglia sempre di usare await per la chiamata a PublishAsync. Se non lo fai, il IData potrebbe non essere elaborato correttamente dalla piattaforma di test e dalle estensioni, portando a bug difficili da individuare. È solo dopo essere tornati dall'attesa che si può essere certi che il IData sia stato accodato per l'elaborazione sul bus di messaggi. Indipendentemente dal punto di estensione su cui si sta lavorando, assicurarsi di aver atteso tutte le chiamate PublishAsync prima di uscire dall'estensione. Ad esempio, se si implementa il testing framework, non è consigliabile chiamare Complete nelle richieste di finché non sono state attese tutte le chiamate PublishAsync per tale richiesta specifica.

Servizio IOutputDevice

La piattaforma di test incapsula l'idea di un dispositivo di output , consentendo al framework di test e alle estensioni di presenti informazioni trasmettendo qualsiasi tipo di dati al sistema di visualizzazione attualmente utilizzato.

L'esempio più tradizionale di un dispositivo di output è l'output della console.

Nota

Sebbene la piattaforma di test sia progettata per supportare dispositivi di output personalizzati , attualmente questo punto di estensione non è disponibile.

Per trasmettere i dati al dispositivo di output , è necessario ottenere il IOutputDevice dal IServiceProvider.

L'API è costituita da:

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

public interface IOutputDeviceDataProducer : IExtension
{
}

public interface IOutputDeviceData
{
}

Il IOutputDeviceDataProducer estende il IExtension e fornisce informazioni sul mittente al dispositivo di output .

Il IOutputDeviceData funge da interfaccia segnaposto. Il concetto alla base di IOutputDevice è quello di contenere informazioni più complesse rispetto al semplice testo colorato. Ad esempio, potrebbe essere un oggetto complesso che può essere rappresentato graficamente.

Per impostazione predefinita, la piattaforma di test offre un modello di testo colorato tradizionale per l'oggetto IOutputDeviceData:

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; }
}

Ecco un esempio di come potresti usare il testo colorato con il dispositivo di output attivo:

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
        }
    });

Oltre all'uso standard del testo colorato, il vantaggio principale di IOutputDevice e IOutputDeviceData è che il dispositivo di output è completamente indipendente e sconosciuto all'utente. Ciò consente lo sviluppo di interfacce utente complesse. Ad esempio, è completamente fattibile implementare un'applicazione Web in tempo reale che visualizza lo stato di avanzamento dei test.

Servizio IPlatformInformation

Fornisce informazioni sulla piattaforma, ad esempio: nome, versione, hash di commit e data di compilazione.