Compartilhar via


Criar uma estrutura de teste

Este artigo explica como criar uma estrutura de teste personalizada para Microsoft.Testing.Platform. A estrutura de teste é a única extensão obrigatória. Ele descobre e executa os testes, e reporta os resultados de volta para a plataforma.

Para obter um resumo completo dos pontos de extensão e dos conceitos de processo interno/externo, consulte Criar extensões personalizadas.

Se você estiver migrando uma estrutura de teste baseada em VSTest existente, implementar a ITestFramework interface nativamente é a abordagem recomendada. A extensão VSTest Bridge está disponível como uma etapa transitória, mas uma implementação nativa fornece a melhor experiência.

Extensão da estrutura de teste

A estrutura de teste é a extensão primária que fornece à plataforma de teste a capacidade de descobrir e executar testes. A estrutura de teste é responsável por comunicar os resultados dos testes de volta à plataforma de teste. A estrutura de teste é a única extensão obrigatória necessária para executar uma sessão de teste

Registrar uma estrutura de teste

Esta seção explica como registrar a estrutura de teste com a plataforma de teste. Você registra apenas uma estrutura de teste por construtor de aplicativos de teste usando a TestApplication.RegisterTestFramework API, conforme mostrado na documentação da arquitetura Microsoft.Testing.Platform .

A API de registro é definida da seguinte forma:

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

A RegisterTestFramework API espera duas fábricas:

  1. Func<IServiceProvider, ITestFrameworkCapabilities>: Este é um delegado que aceita um objeto que implementa a interface IServiceProvider e retorna um objeto que implementa a interface ITestFrameworkCapabilities. O IServiceProvider fornece acesso a serviços de plataforma, como configurações, registradores e argumentos de linha de comando.

    A interface ITestFrameworkCapabilities é usada para anunciar os recursos suportados pela estrutura de teste para a plataforma e extensões. Ela permite que a plataforma e as extensões interajam corretamente, implementando e dando suporte a comportamentos específicos. Para uma melhor compreensão do conceito de recursos, consulte a respectiva seção.

  2. Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework>: Este é um delegado que usa um objeto ITestFrameworkCapabilities, que é a instância retornada pelo Func<IServiceProvider, ITestFrameworkCapabilities>, e um IServiceProvider para fornecer novamente acesso aos serviços de plataforma. O objeto de retorno esperado é aquele que implementa a interface ITestFramework. O ITestFramework serve como o mecanismo de execução que descobre e executa testes e, em seguida, comunica os resultados de volta à plataforma de teste.

A necessidade de a plataforma separar a criação do ITestFrameworkCapabilities e a criação do ITestFramework é uma otimização para evitar a criação da estrutura de teste se os recursos suportados não forem suficientes para executar a sessão de teste atual.

Considere o seguinte exemplo de código de usuário, que demonstra um registro de estrutura de teste que retorna um conjunto de recursos vazio:

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

// ...

Agora, considere o ponto de entrada correspondente deste exemplo com o código de registro:

var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args);
// Register the testing framework
testApplicationBuilder.AddTestingFramework();
using var testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();

Observação

Retornar ITestFrameworkCapabilities vazio não deve impedir a execução da sessão de teste. Todas as estruturas de teste devem ser capazes de descobrir e executar testes. O impacto deve ser limitado às extensões que podem optar por não participar se a estrutura de teste não tiver um determinado recurso.

Criar uma estrutura de teste

O Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework é implementado por extensões que fornecem uma estrutura de teste:

public interface ITestFramework : IExtension
{
    Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context);
    Task ExecuteRequestAsync(ExecuteRequestContext context);
    Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context);
}

A interface IExtension.

A interface ITestFramework herda da interface IExtension, que é uma interface da qual todas as extensões herdam. IExtension é usado para recuperar o nome e a descrição da extensão. O IExtension também fornece uma maneira de ativar ou desativar dinamicamente a extensão na instalação, por meio do Task<bool> IsEnabledAsync(). Certifique-se de retornar true deste método se não tiver requisitos específicos para desabilitá-lo.

O método CreateTestSessionAsync

O método CreateTestSessionAsync é chamado no início da sessão de teste e é usado para inicializar a estrutura de teste. A API aceita um objeto CreateTestSessionContext e retorna um arquivo CreateTestSessionResult.

public sealed class CreateTestSessionContext : TestSessionContext
{
    public CancellationToken CancellationToken { get; }
}

A propriedade SessionUid é herdada de TestSessionContext (consulte a seção TestSessionContext). O CancellationToken é usado para interromper a execução de CreateTestSessionAsync.

O objeto de retorno é um CreateTestSessionResult:

public sealed class CreateTestSessionResult
{
    public string? WarningMessage { get; set; }
    public string? ErrorMessage { get; set; }
    public bool IsSuccess { get; set; }
}

A propriedade IsSuccess é usada para indicar se a criação da sessão foi bem-sucedida. Quando retorna false, a execução do teste é interrompida.

O método CloseTestSessionAsync

O método CloseTestSessionAsync é justaposto à CreateTestSessionAsync em funcionalidade, com a única diferença sendo os nomes dos objetos. Para obter mais informações, consulte a seção CreateTestSessionAsync.

O método ExecuteRequestAsync

O método ExecuteRequestAsync aceita um objeto do tipo ExecuteRequestContext. Esse objeto, conforme sugerido por seu nome, contém as especificações sobre a ação que a estrutura de teste deve executar. A definição ExecuteRequestContext é:

public sealed class ExecuteRequestContext
{
    public IRequest Request { get; }
    public IMessageBus MessageBus { get; }
    public CancellationToken CancellationToken { get; }
    public void Complete();
}

IRequest: Essa é a interface básica para qualquer tipo de solicitação. Você deve pensar na estrutura de teste como um servidor com estado em processo em que o ciclo de vida é:

Um diagrama de sequência que representa o ciclo de vida da estrutura de teste.

O diagrama anterior ilustra que a plataforma de teste emite três solicitações depois de criar a instância da estrutura de teste. A estrutura de teste processa essas solicitações e usa o serviço IMessageBus, que está incluído na própria solicitação, para entregar o resultado de cada solicitação específica. Depois que uma solicitação específica é tratada, a estrutura de teste invoca o método Complete() nela, indicando à plataforma de teste que a solicitação foi atendida. A plataforma de teste monitora todas as solicitações enviadas. Depois que todas as solicitações forem atendidas, ele invocará CloseTestSessionAsync e descartará a instância (se IDisposable/IAsyncDisposable for implementada). É evidente que as solicitações e suas conclusões podem se sobrepor, permitindo a execução simultânea e assíncrona de solicitações.

Observação

Atualmente, a plataforma de testes não envia solicitações sobrepostas e aguarda a conclusão de uma solicitação >> antes de enviar a próxima. No entanto, esse comportamento pode mudar no futuro. O suporte para solicitações simultâneas será determinado por meio do sistema de recursos.

A implementação IRequest especifica a solicitação exata que precisa ser atendida. A estrutura de teste identifica o tipo de solicitação e a trata de acordo. Se o tipo de solicitação não for reconhecido, deverá ser gerada uma exceção.

Você pode encontrar detalhes sobre as solicitações disponíveis na seção IRequest.

IMessageBus: esse serviço, vinculado à solicitação, permite que a estrutura de teste publique de forma assíncrona informações sobre a solicitação em andamento na plataforma de teste. O barramento de mensagens funciona como o hub central da plataforma, facilitando a comunicação assíncrona entre todos os componentes e extensões da plataforma. Para obter uma lista abrangente de informações que podem ser publicadas na plataforma de teste, consulte a seção IMessageBus.

CancellationToken: Esse token é usado para interromper o processamento de uma solicitação específica.

Complete(): conforme descrito na sequência anterior, o método Complete notifica a plataforma de que a solicitação foi processada com êxito e todas as informações relevantes foram transmitidas para o IMessageBus.

Aviso

Negligenciar a invocação Complete() da solicitação fará com que o aplicativo de teste pare de responder.

Para personalizar sua estrutura de teste de acordo com seus requisitos ou os de seus usuários, você pode usar uma seção personalizada dentro do arquivo de configuração ou com opções de de linha de comando personalizadas.

Tratamento de solicitações

A seção seguinte fornece uma descrição detalhada das várias solicitações que uma estrutura de teste pode receber e processar.

Antes de prosseguir para a próxima seção, é fundamental compreender completamente o conceito de IMessageBus, que é o serviço essencial para transmitir informações sobre a execução de testes para a plataforma de testes.

TestSessionContext

O TestSessionContext é uma propriedade compartilhada em todas as solicitações, fornecendo informações sobre a sessão de teste em andamento:

public class TestSessionContext
{
    public SessionUid SessionUid { get; }
}

public readonly struct SessionUid(string value)
{
    public string Value { get; }
}

O TestSessionContext consiste no SessionUid, um identificador exclusivo para a sessão de teste em andamento que ajuda a registrar e correlacionar os dados da sessão de teste.

DiscoverTestExecutionRequest

public class DiscoverTestExecutionRequest
{
    public TestSessionContext Session { get; }
    public ITestExecutionFilter Filter { get; }
}

O DiscoverTestExecutionRequest instrui a estrutura de teste para descobrir os testes e comunicar essas informações pensadas ao IMessageBus.

Conforme descrito na seção anterior, a propriedade de um teste descoberto é DiscoveredTestNodeStateProperty. Aqui está um trecho de código genérico para referência:

var testNode = new TestNode
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        DiscoveredTestNodeStateProperty.CachedInstance),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        discoverTestExecutionRequest.Session.SessionUid,
        testNode));

// ...

ExecutarSolicitaçãoDeExecuçãoDeTeste

public class RunTestExecutionRequest
{
    public TestSessionContext Session { get; }
    public ITestExecutionFilter Filter { get; }
}

O RunTestExecutionRequest instrui a estrutura de teste para executar os testes e comunicar essas informações pensadas ao IMessageBus.

Aqui está um trecho de código genérico para referência:

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

Os dados TestNodeUpdateMessage.

Conforme mencionado na seção IMessageBus, antes de usar o barramento de mensagens, você deve especificar o tipo de dados que pretende fornecer. A plataforma de teste definiu um tipo bem conhecido, TestNodeUpdateMessage, para representar o conceito de informações de atualização de teste.

Esta parte do documento explicará como usar esses dados de payload. Vamos examinar a superfície:

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: o TestNodeUpdateMessage consiste em duas propriedades: a TestNode e a ParentTestNodeUid. O ParentTestNodeUid indica que um teste pode ter um teste pai, introduzindo o conceito de uma árvore de teste em que TestNodes podem ser relacionados uns aos outros. Essa estrutura possibilita aprimoramentos futuros e funcionalidades com base na relação hierárquica de árvore entre os nós. Se sua estrutura de teste não exigir uma estrutura de árvore de teste, você poderá optar por não usá-la e simplesmente defini-la como nula, resultando em uma lista simples de TestNodes.

  • TestNode: o TestNode é composto por três propriedades, uma das quais é o Uid do tipo TestNodeUid. Esse Uid serve como a ID ÚNICO ESTÁVEL para o nó. O termo UNIQUE STABLE ID implica que ele deve permanecer TestNode em diferentes execuções e sistemas operacionais. A TestNodeUid é uma cadeia de caracteres opaca arbitrária que a plataforma de teste aceita como está.

Importante

A estabilidade e a exclusividade da ID são cruciais no domínio de testes. Elas permitem o direcionamento preciso de um único teste para execução e permitem que a ID sirva como um identificador persistente para um teste, facilitando extensões e recursos avançados.

A segunda propriedade é DisplayName, que é um nome fácil de entender para o teste. Por exemplo, esse nome é exibido quando você executa a linha de comando --list-tests.

O terceiro atributo é Properties, que é um tipo PropertyBag. Como demonstrado no código, este é um conjunto de propriedades especializado que contém propriedades genéricas sobre o TestNodeUpdateMessage. Isso implica que você pode adicionar qualquer propriedade ao nó que implementa a interface de espaço reservado IProperty.

A plataforma de teste identifica propriedades específicas adicionadas a um TestNode.Properties para determinar se um teste foi aprovado, reprovado ou ignorado.

Você pode encontrar a lista atual de propriedades disponíveis com a descrição relativa na seção TestNodeUpdateMessage.TestNode.

O tipo PropertyBag normalmente é acessível em todos os IData e utiliza-se para armazenar propriedades diversas que podem ser consultadas pela plataforma e pelas extensões. Esse mecanismo nos permite aprimorar a plataforma com novas informações sem introduzir alterações significativas. Se um componente reconhecer a propriedade, ele poderá consultá-la; caso contrário, ele a desconsiderará.

Por fim, esta seção deixa claro que a implementação da estrutura de teste precisa implementar o IDataProducer que produz TestNodeUpdateMessages como no exemplo abaixo:

internal sealed class TestingFramework
    : ITestFramework, IDataProducer
{
   // ...

   public Type[] DataTypesProduced =>
   [
       typeof(TestNodeUpdateMessage)
   ];

   // ...
}

Se o adaptador de teste exigir a publicação de arquivos durante a execução, você poderá encontrar as propriedades reconhecidas neste arquivo de origem: https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs. Como você pode ver, é possível fornecer ativos de arquivo de maneira geral ou associá-los a um TestNode específico. Lembre-se, se pretende enviar um SessionFileArtifact, deverá declará-lo para a plataforma com antecedência, conforme mostrado abaixo:

internal sealed class TestingFramework
    : ITestFramework, IDataProducer
{
   // ...

   public Type[] DataTypesProduced =>
   [
       typeof(TestNodeUpdateMessage),
       typeof(SessionFileArtifact)
   ];

   // ...
}

Propriedades conhecidas

Conforme detalhado na seção solicitações, a plataforma de teste identifica propriedades específicas adicionadas ao TestNodeUpdateMessage para determinar o status de um TestNode (por exemplo, bem-sucedido, com falha, ignorado etc.). Isso permite que o tempo de execução exiba com precisão uma lista de testes com falha com as informações correspondentes no console e defina o código de saída apropriado para o processo de teste.

Neste segmento, vamos elucidar as várias opções conhecidas IProperty e suas respectivas implicações.

Para obter uma lista abrangente de propriedades conhecidas, consulte TestNodeProperties.cs. Se você observar que uma descrição de propriedade está ausente, registre um problema.

Essas propriedades podem ser divididas nas seguintes categorias:

  1. Informações genéricas: propriedades que podem ser incluídas em qualquer tipo de solicitação.
  2. Informações de descoberta: propriedades fornecidas durante uma solicitação de descoberta DiscoverTestExecutionRequest.
  3. Informações de execução: propriedades fornecidas durante uma solicitação de execução de teste RunTestExecutionRequest.

Certas propriedades são necessárias, enquanto outras são opcionais. As propriedades obrigatórias são necessárias para fornecer a funcionalidade básica de teste, como relatar testes com falha e indicar se toda a sessão de teste foi bem-sucedida ou não.

As propriedades opcionais, por outro lado, aprimoram a experiência de teste fornecendo informações adicionais. Elas são especialmente úteis em cenários de IDE (como VS, VSCode, etc.), execuções de console ou ao oferecer suporte a extensões específicas que exigem informações mais detalhadas para funcionar corretamente. Entretanto, essas propriedades opcionais não afetam a execução dos testes.

Observação

As extensões têm a tarefa de alertar e gerenciar exceções quando precisam de informações específicas para funcionar corretamente. Se uma extensão não tiver as informações necessárias, ela não deverá causar falha na execução do teste, mas deverá simplesmente optar por não participar.

Informações gerais
public record KeyValuePairStringProperty(
    string Key,
    string Value)
        : IProperty;

O KeyValuePairStringProperty significa um par de dados de chave/valor geral.

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 é usado para identificar o local do teste no arquivo de origem. Isso é particularmente útil quando o iniciador é um IDE como Visual Studio ou Visual Studio Code.

public sealed record TestMethodIdentifierProperty(
    string AssemblyFullName,
    string Namespace,
    string TypeName,
    string MethodName,
    string[] ParameterTypeFullNames,
    string ReturnTypeFullName)

TestMethodIdentifierProperty é um identificador exclusivo para um método de teste.

public sealed record TestMetadataProperty(
    string Key,
    string Value)

TestMetadataProperty é utilizado para transmitir as características ou traços de um TestNode.

Informações de descoberta
public sealed record DiscoveredTestNodeStateProperty(
    string? Explanation = null)
{
    public static DiscoveredTestNodeStateProperty CachedInstance { get; }
}

O DiscoveredTestNodeStateProperty indica que este TestNode foi descoberto. Ele é usado quando DiscoverTestExecutionRequest é enviado para a estrutura de teste. Anote o valor em cache útil oferecido pela propriedade CachedInstance. Esta propriedade é obrigatória.

Informações de execução
public sealed record InProgressTestNodeStateProperty(
    string? Explanation = null)
{
    public static InProgressTestNodeStateProperty CachedInstance { get; }
}

O InProgressTestNodeStateProperty informa à plataforma de teste que o TestNode foi agendado para execução e está em andamento no momento. Anote o valor em cache útil oferecido pela propriedade CachedInstance.

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

O TimingProperty é utilizado para transmitir informações de cronometragem da execução do TestNode. Ele também permite cronometrar etapas de execução individuais por meio do StepTimingInfo. Isso é especialmente útil quando o conceito de teste é dividido em várias fases, como inicialização, execução e limpeza.

Uma e apenas uma das propriedades a seguir é necessária por TestNode e comunica o resultado do TestNode para a plataforma de teste.

public sealed record PassedTestNodeStateProperty(
    string? Explanation = null)
        : TestNodeStateProperty(Explanation)
{
    public static PassedTestNodeStateProperty CachedInstance
        { get; } = new PassedTestNodeStateProperty();
}

PassedTestNodeStateProperty informa à plataforma de teste que esse TestNode foi aprovado. Anote o valor em cache útil oferecido pela propriedade CachedInstance.

public sealed record SkippedTestNodeStateProperty(
    string? Explanation = null)
        : TestNodeStateProperty(Explanation)
{
    public static SkippedTestNodeStateProperty CachedInstance
        { get; } =  new SkippedTestNodeStateProperty();
}

SkippedTestNodeStateProperty informa à plataforma de testes que este TestNode foi ignorado. Anote o valor em cache útil oferecido pela propriedade CachedInstance.

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 informa à plataforma de teste que TestNode falhou após a verificação de uma condição.

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 informa à plataforma de teste que esta TestNode falhou. Esse tipo de falha é diferente do FailedTestNodeStateProperty, que é usado para falhas de afirmação. Por exemplo, você pode relatar problemas como erros de inicialização de teste com ErrorTestNodeStateProperty.

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 informa à plataforma de teste que esse TestNode falhou por um motivo de tempo limite. Você pode informar o tempo limite usando a propriedade Timeout.

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 informa à plataforma de teste que esse TestNode falhou devido ao cancelamento.