Condividi tramite


Sviluppare estensioni per Microsoft.Testing.Platform

Questo articolo illustra i punti di estendibilità per Microsoft.Testing.Platform oltre al framework di test stesso. Per la creazione del framework di test, vedere Creare un framework di test.

Per il riepilogo completo del punto di estensione e i concetti in-process/out-of-process, vedere Creare estensioni personalizzate.

Punti di estendibilità

La piattaforma di test fornisce ulteriori punti di estendibilità che consentono di personalizzare il comportamento della piattaforma e del framework di test. Questi punti di estendibilità sono facoltativi e possono essere usati per migliorare l'esperienza di test.

Le estensioni ICommandLineOptionsProvider

Annotazioni

Quando si estende questa API, l'estensione personalizzata esiste sia all'interno che all'esterno del processo host di test.

Come illustrato nella sezione architettura , il passaggio iniziale prevede la creazione ITestApplicationBuilder di per registrare il framework di test e le estensioni con esso.

var builder = await TestApplication.CreateBuilderAsync(args);

Il CreateBuilderAsync metodo accetta una matrice di stringhe (string[]) denominata args. Questi argomenti possono essere usati per passare le opzioni della riga di comando a tutti i componenti della piattaforma di test (inclusi i componenti predefiniti, i framework di test e le estensioni), consentendo la personalizzazione del comportamento.

In genere, gli argomenti passati sono quelli ricevuti nel metodo standard Main(string[] args). Tuttavia, se l'ambiente di hosting è diverso, è possibile specificare qualsiasi elenco di argomenti.

Gli argomenti devono essere preceduti da un trattino doppio --. Ad esempio: --filter.

Se un componente, ad esempio un framework di test o un punto di estensione, desidera offrire opzioni della riga di comando personalizzate, può farlo implementando l'interfaccia ICommandLineOptionsProvider. Questa implementazione può quindi essere registrata con ITestApplicationBuilder tramite la factory di registrazione della proprietà CommandLine, come illustrato di seguito:

builder.CommandLine.AddProvider(
    static () => new CustomCommandLineOptions());

Nell'esempio fornito, CustomCommandLineOptions è un'implementazione dell'interfaccia ICommandLineOptionsProvider. Questa interfaccia comprende i membri e i tipi di dati seguenti:

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

Come osservato, ICommandLineOptionsProvider estende l'interfaccia IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

L'ordine di esecuzione di ICommandLineOptionsProvider è:

Diagramma che rappresenta l'ordine di esecuzione dell'interfaccia 'ICommandLineOptionsProvider'.

Analizziamo le API e la loro media:

ICommandLineOptionsProvider.GetCommandLineOptions(): questo metodo viene utilizzato per recuperare tutte le opzioni offerte dal componente. Per ogni CommandLineOption, è necessario specificare le proprietà seguenti:

string name: nome dell'opzione, presentato senza trattino. Ad esempio, il filtro viene usato come --filter dagli utenti.

string description: questa è una descrizione dell'opzione. Verrà visualizzato quando gli utenti passano --help come argomento al generatore di applicazioni.

ArgumentArity arity: l'arietà di un'opzione è il numero di valori che possono essere passati se viene specificata tale opzione o comando. Le attuali arità disponibili sono:

  • Zero: rappresenta l'arietà di un argomento pari a zero.
  • ZeroOrOne: rappresenta l'arietà di un argomento pari a zero o uno.
  • ZeroOrMore: rappresenta l'arietà di un argomento pari a zero o più.
  • OneOrMore: rappresenta l'arietà di uno o più argomenti.
  • ExactlyOne: rappresenta l'arietà di un argomento.

Per gli esempi, vedere la tabella arity di System.CommandLine.

bool isHidden: questa proprietà indica che l'opzione è disponibile per l'uso, ma non verrà visualizzata nella descrizione quando --help viene richiamata.

ICommandLineOptionsProvider.ValidateOptionArgumentsAsync: questo metodo viene usato per convalidare l'argomento fornito dall'utente.

Ad esempio, se si dispone di un parametro denominato --dop che rappresenta il grado di parallelismo per il framework di test personalizzato, un utente potrebbe immettere --dop 0. In questo scenario, il valore 0 non è valido perché dovrebbe avere un grado di parallelismo di 1 o più. Usando ValidateOptionArgumentsAsync, è possibile eseguire la convalida iniziale e restituire un messaggio di errore, se necessario.

Un'implementazione possibile per l'esempio precedente potrebbe essere:

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: questo metodo viene chiamato come ultimo e consente di eseguire un controllo di coerenza globale.

Si supponga, ad esempio, che il framework di test abbia la possibilità di generare un report dei risultati del test e salvarlo in un file. Questa funzionalità è accessibile usando l'opzione --generatereport e il nome file viene specificato con --reportfilename myfile.rep. In questo scenario, se un utente fornisce solo l'opzione --generatereport senza specificare un nome file, la convalida deve avere esito negativo perché il report non può essere generato senza un nome file. Un'implementazione possibile per l'esempio precedente potrebbe essere:

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

Si noti che il ValidateCommandLineOptionsAsync metodo fornisce il servizio ICommandLineOptions, che viene usato per recuperare le informazioni sull'argomento analizzate dalla piattaforma stessa.

Le estensioni ITestSessionLifetimeHandler

ITestSessionLifeTimeHandler è un'estensione in-process che consente l'esecuzione del codice prima e dopo la sessione di test.

Per registrare un oggetto personalizzato ITestSessionLifeTimeHandler, usare l'API seguente:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestSessionLifetimeHandle(
    static serviceProvider => new CustomTestSessionLifeTimeHandler());

La factory usa IServiceProvider per ottenere l'accesso alla suite di servizi offerti dalla piattaforma di test.

Importante

La sequenza di registrazione è significativa, poiché le API vengono chiamate nell'ordine in cui sono state registrate.

L'interfaccia ITestSessionLifeTimeHandler include i metodi riportati di seguito:

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 è un tipo di ITestHostExtension, che funge da base per tutte le estensioni host di test. Come tutti gli altri punti di estensione, eredita anche da IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

Considerare i dettagli seguenti per questa API:

OnTestSessionStartingAsync: questo metodo viene richiamato prima dell'inizio della sessione di test e riceve l'oggetto SessionUid , che fornisce un identificatore opaco per la sessione di test corrente.

OnTestSessionFinishingAsync: questo metodo viene richiamato dopo il completamento della sessione di test, assicurandosi che il framework di test abbia completato l'esecuzione di tutti i test e abbia segnalato tutti i dati pertinenti alla piattaforma. In genere, in questo metodo, l'estensione usa IMessageBus per trasmettere asset o dati personalizzati al bus di piattaforma condiviso. Questo metodo può anche segnalare a qualsiasi estensione out-of-process personalizzata che la sessione di test ha concluso.

Infine, entrambe le API accettano un parametro CancellationToken che deve essere rispettato.

Se l'estensione richiede un'inizializzazione intensiva ed è necessario usare il modello async/await, è possibile fare riferimento a Async extension initialization and cleanup. Se è necessario condividere lo stato tra i punti di estensione, è possibile fare riferimento alla CompositeExtensionFactory<T> sezione .

Le estensioni ITestApplicationLifecycleCallbacks

ITestApplicationLifecycleCallbacks è un'estensione in-process che consente l'esecuzione del codice prima di tutto, è come avere accesso alla prima riga dell'ipotetico main dell'host di test.

Per registrare un oggetto personalizzato ITestApplicationLifecycleCallbacks, usare l'API seguente:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestApplicationLifecycleCallbacks(
    static serviceProvider
    => new CustomTestApplicationLifecycleCallbacks());

La factory usa IServiceProvider per ottenere l'accesso alla suite di servizi offerti dalla piattaforma di test.

Importante

La sequenza di registrazione è significativa, poiché le API vengono chiamate nell'ordine in cui sono state registrate.

L'interfaccia ITestApplicationLifecycleCallbacks include i metodi riportati di seguito:

public interface ITestApplicationLifecycleCallbacks : ITestHostExtension
{
    Task BeforeRunAsync(CancellationToken cancellationToken);

    Task AfterRunAsync(
        int exitCode,
        CancellationToken cancellation);
}

public interface ITestHostExtension : IExtension
{
}

ITestApplicationLifecycleCallbacks è un tipo di ITestHostExtension, che funge da base per tutte le estensioni host di test. Come tutti gli altri punti di estensione, eredita anche da IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

BeforeRunAsync: questo metodo funge da punto di contatto iniziale per l'host di test ed è la prima opportunità per un'estensione in-process per eseguire una funzionalità. Viene in genere usato per stabilire una connessione con le estensioni out-of-process corrispondenti se una funzionalità è progettata per operare in entrambi gli ambienti.

Ad esempio, la funzionalità di dump di blocco predefinita è costituita da estensioni in-process e out-of-process e questo metodo viene usato per scambiare informazioni con il componente out-of-process dell'estensione.

AfterRunAsync: questo metodo è l'ultima chiamata prima di uscire da int ITestApplication.RunAsync() e fornisce il exit code. Deve essere usato esclusivamente per le attività di pulizia e per notificare qualsiasi estensione out-of-process corrispondente che il test host sta per terminare.

Infine, entrambe le API accettano un parametro CancellationToken che deve essere rispettato.

Le estensioni IDataConsumer

L'estensione IDataConsumer è un'estensione in-process in grado di sottoscrivere e ricevere IData informazioni inviate al IMessageBus dal framework di test e dalle relative estensioni.

Questo punto di estensione è fondamentale perché consente agli sviluppatori di raccogliere ed elaborare tutte le informazioni generate durante una sessione di test.

Per registrare un oggetto personalizzato IDataConsumer, usare l'API seguente:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddDataConsumer(
    static serviceProvider => new CustomDataConsumer());

La factory usa IServiceProvider per ottenere l'accesso alla suite di servizi offerti dalla piattaforma di test.

Importante

La sequenza di registrazione è significativa, poiché le API vengono chiamate nell'ordine in cui sono state registrate.

L'interfaccia IDataConsumer include i metodi riportati di seguito:

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 è un tipo di ITestHostExtension, che funge da base per tutte le estensioni host di test. Come tutti gli altri punti di estensione, eredita anche da IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

DataTypesConsumed: questa proprietà restituisce un elenco di Type che questa estensione prevede di consumare. Corrisponde a IDataProducer.DataTypesProduced. In particolare, un IDataConsumer può abbonarsi a più tipi provenienti da IDataProducer istanze diverse, senza problemi.

ConsumeAsync: questo metodo viene attivato ogni volta che dei dati di un tipo a cui il consumatore corrente è iscritto vengono inseriti nell'oggetto IMessageBus. Riceve il IDataProducer per fornire informazioni dettagliate sul produttore del payload dei dati, nonché sul payload IData stesso. Come si può notare, IData è un'interfaccia segnaposto generica che contiene dati informativi generali. La possibilità di eseguire il push di diversi tipi di IData implica che il consumer deve attivare il tipo stesso per eseguirne il cast al tipo corretto e accedere alle informazioni specifiche.

Un'implementazione di esempio di un consumer che vuole elaborare l'oggetto TestNodeUpdateMessage prodotto da un framework di test può essere:

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

Infine, l'API accetta un valore CancellationToken che l'estensione deve rispettare.

Importante

È fondamentale elaborare il payload direttamente all'interno del metodo ConsumeAsync. IMessageBus può gestire sia l'elaborazione sincrona che l'elaborazione asincrona, coordinando l'esecuzione con il framework di test. Anche se il processo di utilizzo è completamente asincrono e non blocca IMessageBus.Push al momento della scrittura, si tratta di un dettaglio di implementazione che potrebbe cambiare in futuro a causa di requisiti futuri. Tuttavia, la piattaforma garantisce che questo metodo venga sempre chiamato una sola volta, eliminando la necessità di una sincronizzazione complessa e gestendo anche la scalabilità dei consumatori.

Avvertimento

Quando si usa IDataConsumer in combinazione con ITestHostProcessLifetimeHandler all'interno di un punto di estensione composito, è fondamentale ignorare tutti i dati ricevuti dopo l'esecuzione di ITestSessionLifetimeHandler.OnTestSessionFinishingAsync. È OnTestSessionFinishingAsync l'ultima opportunità per elaborare i dati accumulati e trasmettere nuove informazioni a IMessageBus, di conseguenza, tutti i dati utilizzati oltre questo punto non saranno utilizzabili dall'estensione.

Se l'estensione richiede un'inizializzazione intensiva ed è necessario usare il modello async/await, è possibile fare riferimento a Async extension initialization and cleanup. Se è necessario condividere lo stato tra i punti di estensione, è possibile fare riferimento alla CompositeExtensionFactory<T> sezione .

Le estensioni ITestHostEnvironmentVariableProvider

ITestHostEnvironmentVariableProvider è un'estensione out-of-process che consente di stabilire variabili di ambiente personalizzate per l'host di test. L'uso di questo punto di estensione garantisce che la piattaforma di test avvii un nuovo host con le variabili di ambiente appropriate, come descritto nella sezione relativa all'architettura.

Per registrare un oggetto personalizzato ITestHostEnvironmentVariableProvider, usare l'API seguente:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHostControllers.AddEnvironmentVariableProvider(
    static serviceProvider => new CustomEnvironmentVariableForTestHost());

La factory usa IServiceProvider per ottenere l'accesso alla suite di servizi offerti dalla piattaforma di test.

Importante

La sequenza di registrazione è significativa, poiché le API vengono chiamate nell'ordine in cui sono state registrate.

L'interfaccia ITestHostEnvironmentVariableProvider include i metodi e i tipi riportati di seguito:

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 è un tipo di ITestHostControllersExtension, che funge da base per tutte le estensioni del host controller di test. Come tutti gli altri punti di estensione, eredita anche da IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

Prendere in considerazione i dettagli per questa API:

UpdateAsync: questa API di aggiornamento fornisce un'istanza dell'oggetto IEnvironmentVariables , da cui è possibile chiamare i SetVariable metodi o RemoveVariable . Quando si usa SetVariable, è necessario passare un oggetto di tipo EnvironmentVariable, che richiede le specifiche seguenti:

  • Variable: nome della variabile di ambiente.
  • Value: valore della variabile di ambiente.
  • IsSecret: indica se la variabile di ambiente contiene informazioni riservate che non devono essere registrate o accessibili tramite TryGetVariable.
  • IsLocked: determina se altre estensioni ITestHostEnvironmentVariableProvider possono modificare questo valore.

ValidateTestHostEnvironmentVariablesAsync: questo metodo viene richiamato dopo che sono stati chiamati tutti i UpdateAsync metodi delle istanze registrate ITestHostEnvironmentVariableProvider . Consente di verificare la configurazione corretta delle variabili di ambiente. Accetta un oggetto che implementa IReadOnlyEnvironmentVariables, che fornisce il metodo TryGetVariable per recuperare informazioni specifiche sulle variabili di ambiente del tipo di oggetto OwnedEnvironmentVariable. Dopo la convalida, viene restituito un ValidationResult contenente eventuali motivi di errore.

Annotazioni

La piattaforma di test, per impostazione predefinita, implementa e registra SystemEnvironmentVariableProvider. Questo provider carica tutte le variabili di ambienti correnti. Come primo provider registrato, viene eseguito per primo, concedendo l'accesso alle variabili di ambiente predefinite per tutte le altre estensioni utente ITestHostEnvironmentVariableProvider.

Se l'estensione richiede un'inizializzazione intensiva ed è necessario usare il modello async/await, è possibile fare riferimento a Async extension initialization and cleanup. Se è necessario condividere lo stato tra i punti di estensione, è possibile fare riferimento alla CompositeExtensionFactory<T> sezione .

Le estensioni ITestHostProcessLifetimeHandler

ITestHostProcessLifetimeHandler è un'estensione out-of-process che consente di osservare il processo host di test da un punto di vista esterno. In questo modo, l'estensione rimane non influenzata da potenziali arresti anomali o blocchi indotti dal codice in fase di test. L'uso di questo punto di estensione richiederà alla piattaforma di test di avviare un nuovo host, come descritto nella sezione relativa all'architettura.

Per registrare un oggetto personalizzato ITestHostProcessLifetimeHandler, usare l'API seguente:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHostControllers.AddProcessLifetimeHandler(
    static serviceProvider => new CustomMonitorTestHost());

La factory usa IServiceProvider per ottenere l'accesso alla suite di servizi offerti dalla piattaforma di test.

Importante

La sequenza di registrazione è significativa, poiché le API vengono chiamate nell'ordine in cui sono state registrate.

L'interfaccia ITestHostProcessLifetimeHandler include i metodi riportati di seguito:

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 è un tipo di ITestHostControllersExtension, che funge da base per tutte le estensioni del host controller di test. Come tutti gli altri punti di estensione, eredita anche da IExtension. Di conseguenza, come qualsiasi altra estensione, è possibile scegliere di abilitarla o disabilitarla usando l'API IExtension.IsEnabledAsync.

Considerare i dettagli seguenti per questa API:

BeforeTestHostProcessStartAsync: Questo metodo viene richiamato prima che la piattaforma di test avvii gli host di test.

OnTestHostProcessStartedAsync: questo metodo viene richiamato immediatamente dopo l'avvio dell'host di test. Questo metodo offre un oggetto che implementa l'interfaccia ITestHostProcessInformation, che fornisce informazioni chiave sul risultato del processo host di test.

Importante

La chiamata di questo metodo non interrompe l'esecuzione dell'host di test. Se è necessario sospenderla, è necessario registrare un'estensione in-process , ITestApplicationLifecycleCallbacks ad esempio e sincronizzarla con l'estensione out-of-process .

OnTestHostProcessExitedAsync: questo metodo viene richiamato al termine dell'esecuzione del gruppo di test. Questo metodo fornisce un oggetto che rispetta l'interfaccia ITestHostProcessInformation, che fornisce dettagli cruciali sul risultato del processo host di test.

L'interfaccia ITestHostProcessInformation fornisce i dettagli seguenti:

  • PID: ID del processo dell'host di test.
  • ExitCode: codice di uscita del processo. Questo valore è disponibile solo all'interno del metodo OnTestHostProcessExitedAsync. Il tentativo di accedervi all'interno del metodo OnTestHostProcessStartedAsync genererà un'eccezione.
  • HasExitedGracefully: un valore booleano che indica se l'host di test si è arrestato in modo imprevisto. Se true, significa che l'host di test non è uscito normalmente.

Ordine di esecuzione delle estensioni

La piattaforma di test è costituita da un framework di test e da un numero qualsiasi di estensioni che possono funzionare in-process o out-of-process. Questo documento descrive la sequenza di chiamate a tutti i potenziali punti di estendibilità per fornire maggiore chiarezza quando si prevede che venga richiamata una funzionalità:

  1. ITestHostEnvironmentVariableProvider.UpdateAsync: fuori processo
  2. ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync: out-of-process
  3. ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync: out-of-process
  4. Avvio del processo host di test
  5. ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync: fuori processo, questo evento può intrecciare le azioni delle estensioni in-process a seconda della condizione di competizione.
  6. ITestApplicationLifecycleCallbacks.BeforeRunAsync: in corso
  7. ITestSessionLifetimeHandler.OnTestSessionStartingAsync: in corso
  8. ITestFramework.CreateTestSessionAsync: in-process
  9. ITestFramework.ExecuteRequestAsync: in-process; questo metodo può essere chiamato una o più volte. A questo punto, il framework di test trasmetterà informazioni a IMessageBus che possono essere usate da IDataConsumer.
  10. ITestFramework.CloseTestSessionAsync: in processo
  11. ITestSessionLifetimeHandler.OnTestSessionFinishingAsync: in-process
  12. ITestApplicationLifecycleCallbacks.AfterRunAsync: in-process
  13. La pulizia in-process prevede la chiamata di dispose e IAsyncCleanableExtension in tutti i punti di estensione.
  14. ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync: out-of-process
  15. La pulizia fuori dal processo prevede la chiamata di dispose e di IAsyncCleanableExtension su tutti i punti di estensione.

Assistente delle estensioni

La piattaforma di test fornisce un set di classi e interfacce helper per semplificare l'implementazione delle estensioni. Questi helper sono progettati per semplificare il processo di sviluppo e garantire che l'estensione rispetti gli standard della piattaforma.

Inizializzazione asincrona e pulizia delle estensioni

La creazione del framework di test e delle estensioni tramite factory è conforme al meccanismo standard di creazione di oggetti .NET, che usa costruttori sincroni. Se un'estensione richiede un'inizializzazione intensiva ,ad esempio l'accesso al file system o alla rete, non può usare il modello asincrono/await nel costruttore perché i costruttori restituiscono void, non Task.

Pertanto, la piattaforma di test fornisce un metodo per inizializzare un'estensione usando il modello asincrono/await tramite una semplice interfaccia. Per la simmetria, offre anche un'interfaccia asincrona per la pulizia che le estensioni possono implementare senza problemi.

public interface IAsyncInitializableExtension
{
    Task InitializeAsync();
}

public interface IAsyncCleanableExtension
{
    Task CleanupAsync();
}

IAsyncInitializableExtension.InitializeAsync: questo metodo verrà sicuramente richiamato dopo la creazione della factory.

IAsyncCleanableExtension.CleanupAsync: questo metodo deve essere richiamato almeno una volta durante la chiusura della sessione di test, prima dell'impostazione predefinita DisposeAsync o Dispose.

Importante

Analogamente al metodo standard Dispose , CleanupAsync può essere richiamato più volte. Se il metodo CleanupAsync di un oggetto viene chiamato più volte, l'oggetto deve ignorare tutte le chiamate dopo la prima. L'oggetto non deve generare un'eccezione se il metodo CleanupAsync viene chiamato più volte.

Annotazioni

Per impostazione predefinita, la piattaforma di test chiamerà DisposeAsync se è disponibile o Dispose se è implementata. È importante notare che la piattaforma di test non chiamerà entrambi i metodi dispose, ma darà priorità al metodo asincrono se implementato.

CompositeExtensionFactory<T>

Come descritto nella sezione estensioni, la piattaforma di test consente di implementare interfacce per incorporare estensioni personalizzate sia in che fuori processo.

Ogni interfaccia punta a una particolare funzionalità e, in base alla progettazione di .NET, si implementa questa interfaccia in un oggetto specifico. È possibile registrare l'estensione stessa usando l'API AddXXX di registrazione specifica dall'oggetto TestHost o TestHostController da ITestApplicationBuilder come descritto nelle sezioni corrispondenti.

Tuttavia, se è necessario condividere lo stato tra due estensioni, il fatto che sia possibile implementare e registrare oggetti diversi che implementano interfacce diverse rende la condivisione di un'attività complessa. Senza alcuna assistenza, è necessario un modo per passare un'estensione all'altra per condividere informazioni, che complica la progettazione.

Di conseguenza, la piattaforma di test fornisce un metodo sofisticato per implementare più punti di estensione usando lo stesso tipo, rendendo la condivisione dei dati un'attività semplice. È sufficiente usare CompositeExtensionFactory<T>, che può quindi essere registrato usando la stessa API che si farebbe per una singola implementazione dell'interfaccia.

Si consideri, ad esempio, un tipo che implementa sia ITestSessionLifetimeHandler che IDataConsumer. Si tratta di uno scenario comune perché spesso si vogliono raccogliere informazioni dal framework di test e quindi, al termine della sessione di test, si invierà l'artefatto usando all'interno IMessageBus di ITestSessionLifetimeHandler.OnTestSessionFinishingAsync.

Ciò che è necessario fare è implementare normalmente le interfacce:

internal class CustomExtension : ITestSessionLifetimeHandler, IDataConsumer, ...
{
   ...
}

Dopo aver creato CompositeExtensionFactory<CustomExtension> per il tipo, è possibile registrarlo con le API IDataConsumer e ITestSessionLifetimeHandler, che offrono un overload per CompositeExtensionFactory<T>:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

var factory = new CompositeExtensionFactory<CustomExtension>(serviceProvider => new CustomExtension());

builder.TestHost.AddTestSessionLifetimeHandle(factory);
builder.TestHost.AddDataConsumer(factory);

Il costruttore di fabbrica usa IServiceProvider per accedere ai servizi forniti dalla piattaforma di test.

La piattaforma di test sarà responsabile della gestione del ciclo di vita dell'estensione composita.

È importante notare che a causa del supporto della piattaforma di test per le estensioni in-process e out-of-process, non è possibile combinare arbitrariamente alcun punto di estensione. La creazione e l'utilizzo delle estensioni dipendono dal tipo host, ovvero è possibile raggruppare solo le estensioni in-process (TestHost) e out-of-process (TestHostController).

Sono possibili le seguenti combinazioni:

  • Per ITestApplicationBuilder.TestHost, è possibile combinare IDataConsumer e ITestSessionLifetimeHandler.
  • Per ITestApplicationBuilder.TestHostControllers, è possibile combinare ITestHostEnvironmentVariableProvider e ITestHostProcessLifetimeHandler.