Funcionalidades do Microsoft.Testing.Platform (MTP)

No contexto do MTP, uma funcionalidade refere-se ao potencial de executar uma ação específica ou fornecer informações específicas. É um meio para a estrutura de teste e extensões declararem sua capacidade de operar de determinada maneira ou fornecer informações específicas aos solicitantes.

Os solicitantes podem ser qualquer componente envolvido em uma sessão de teste, como a plataforma, uma extensão ou a própria estrutura de teste.

O objetivo principal do sistema de funcionalidade é facilitar a comunicação efetiva entre os componentes envolvidos em uma sessão de teste, permitindo que eles exchange informações e atendam às respectivas necessidades com precisão.

Exemplo guiado

Vamos considerar um exemplo hipotético para demonstrar a necessidade de um sistema de funcionalidades.

Observação

Este exemplo é puramente para fins ilustrativos e não é implementado atualmente no MTP ou em qualquer estrutura de teste.

Imagine uma situação em que você tenha uma extensão que exija que a estrutura de teste execute não mais do que um teste por vez. Além disso, após cada teste, a extensão precisa saber o uso da CPU para esse teste específico.

Para acomodar o cenário anterior, você precisará consultar a estrutura de teste se:

  1. Ele tem a capacidade de executar apenas um teste por vez.
  2. Ele pode fornecer informações sobre a quantidade de CPU consumida por cada teste.

Como a extensão pode determinar se a estrutura de teste tem a capacidade de operar nesse modo e fornecer informações de uso da CPU para uma sessão de teste? No MTP, essa funcionalidade é representada por uma implementação da interface Microsoft.Testing.Platform.Capabilities.ICapability:

// Base capabilities contracts

public interface ICapability
{
}

public interface ICapabilities<TCapability>
    where TCapability : ICapability
{
    IReadOnlyCollection<TCapability> Capabilities { get; }
}

// Specific testing framework capabilities

public interface ITestFrameworkCapabilities : ICapabilities<ITestFrameworkCapability>
{
}

public interface ITestFrameworkCapability : ICapability
{
}

Como você pode ver, a ICapability interface é uma interface de marcador porque pode representar qualquer funcionalidade e a implementação real dependerá do contexto. Você também observará o ITestFrameworkCapability, que herda de ICapability para classificar a capacidade. A natureza genérica do sistema de funcionalidades permite o agrupamento conveniente por contexto. O ITestFrameworkCapability agrupa todos os recursos implementados pelo framework de teste. A ICapabilities<TCapability> interface revela o conjunto de todos os recursos implementados por uma extensão. Da mesma forma, para a base um, há uma estrutura de teste específica para o contexto chamada ITestFrameworkCapabilities. O ITestFrameworkCapabilities é fornecido à plataforma durante o processo de registro do framework de teste.

Para criar um recurso que resolva o cenário mencionado acima, defina-o da seguinte maneira:

public interface IDisableParallelismCapability : ITestFrameworkCapability
{
    bool CanDisableParallelism { get; }
    bool CanProvidePerTestCpuConsumption { get; }
    void Enable();
}

Se a estrutura de teste implementar essa interface, em runtime, o seguinte poderá ser consultado:

  • Verifique se a estrutura de teste tem a capacidade de desativar o paralelismo CanDisableParallelism = true.
  • Determine se a estrutura de teste pode fornecer dados CanProvidePerTestCPUConsumption = truede uso da CPU.
  • Solicite que o adaptador de teste ative esse modo invocando o Enable() método antes do início da sessão de teste.

O fragmento de código hipotético dentro da extensão pode ser algo como:

IServiceProvider serviceProvider = null; // TODO: Get IServiceProvider.
var capabilities = serviceProvider.GetRequiredService<ITestFrameworkCapabilities>();

// Utilize the `GetCapability` API to search for the specific capability to query.
var capability = capabilities.GetCapability<IDisableParallelismCapability>();
if (capability is null)
{
    // Capability not supported...
}
else
{
    capability.Enable();
    if (capability.CanDisableParallelism)
    {
        // Do something...
    }

    if (capability.CanProvidePerTestCpuConsumption)
    {
        // Do something...
    }
}

O exemplo anterior ilustra como a infraestrutura de funcionalidade permite um mecanismo poderoso para comunicar habilidades entre os componentes envolvidos em uma sessão de teste. Embora o exemplo demonstre uma funcionalidade especificamente projetada para a estrutura de teste, qualquer componente pode expor e implementar extensões que herdam de ICapability.

É evidente que nem todos os detalhes podem ser comunicados por meio de uma interface. Considerando o exemplo anterior, o que a extensão deve esperar se houver suporte para CanProvidePerTestCpuConsumption? Que tipo de informações personalizadas devem ser transmitidas por meio do IMessageBus pela estrutura de teste? A solução é a documentação da funcionalidade. É responsabilidade do proprietário da funcionalidade projetar, enviar e documentá-lo o mais claramente possível para ajudar os implementadores que desejam colaborar efetivamente com a extensão que requer a funcionalidade específica.

Por exemplo, a extensão de relatório TRX permite que a estrutura de teste implemente a capacidade necessária para gerar com precisão um relatório TRX. A extensão para registro está incluída no pacote NuGet https://www.nuget.org/packages/Microsoft.Testing.Extensions.TrxReport, mas a capacidade de implementação é encontrada no pacote apenas no contrato.

Em conclusão, vamos resumir os principais aspectos do sistema de funcionalidade:

  • É essencial para facilitar a comunicação clara e estável entre componentes.
  • Todos os recursos devem herdar de ICapability ou de uma interface que herda dele e são expostos por meio de uma coleção com a interface ICapabilities.
  • Ajuda na evolução dos recursos sem causar alterações significativas. Se uma determinada funcionalidade não tiver suporte, medidas apropriadas poderão ser tomadas.
  • A responsabilidade de projetar, enviar e documentar o uso de uma funcionalidade é do proprietário da funcionalidade. O MTP também pode ter uma funcionalidade da mesma forma que qualquer outra extensão.

Funcionalidades da estrutura

A plataforma expõe uma interface especializada chamada ITestFrameworkCapability que é a base de todos os recursos expostos para estruturas de teste. Esses recursos são fornecidos ao registrar a estrutura de teste na plataforma.

IBannerMessageOwnerCapability

Uma capacidade opcional do framework de teste permite que o framework de teste forneça a mensagem de banner à plataforma. Se a mensagem for null ou o recurso não estiver presente, a plataforma usará sua mensagem padrão do banner.

Essa implementação de funcionalidade permite que você abstraia as várias condições que a estrutura de teste pode precisar considerar ao decidir se a mensagem de faixa deve ou não ser exibida.

A plataforma expõe o IPlatformInformation serviço para fornecer algumas informações sobre a plataforma que podem ser úteis ao criar sua mensagem de banner personalizada.

ITrxReportCapability

Para fornecer metadados específicos do framework para a extensão de relatório TRX, implemente Microsoft.Testing.Extensions.TrxReport.Abstractions.ITrxReportCapability. Referencie o pacote Microsoft.Testing.Extensions.TrxReport.Abstractions do seu framework. O aplicativo de teste também precisa do pacote Microsoft.Testing.Extensions.TrxReport para criar relatórios.

O recurso separa o suporte da ativação:

  • Retorne true de IsSupported quando seu framework puder fornecer metadados TRX.
  • Em Enable(), registre que o repórter TRX solicitou os metadados. O reporter chama esse método no início de uma sessão de teste quando os relatórios TRX estão habilitados.
  • Após a ativação, adicione as propriedades TRX aos nós de teste que seu framework publica.

A funcionalidade a seguir armazena o estado de ativação. Os projetos de exemplo têm como destino .NET 10 e fazem referência ao MTP 2.5.1:

internal sealed class TrxCapability : ITrxReportCapability
{
    public bool IsSupported => true;
    public bool IsEnabled { get; private set; }

    public void Enable() => IsEnabled = true;
}
Friend NotInheritable Class TrxCapability
    Implements ITrxReportCapability

    Public ReadOnly Property IsSupported As Boolean Implements ITrxReportCapability.IsSupported
        Get
            Return True
        End Get
    End Property

    Public Property IsEnabled As Boolean
        Get
            Return _isEnabled
        End Get
        Private Set(value As Boolean)
            _isEnabled = value
        End Set
    End Property

    Private _isEnabled As Boolean

    Public Sub Enable() Implements ITrxReportCapability.Enable
        IsEnabled = True
    End Sub
End Class

Retorne a capacidade do capabilitiesFactory que você passa para RegisterTestFramework. Dê ao framework a mesma instância de capacidade para que ele possa observar Enable(). Para um aplicativo de teste composto manualmente, registre o provedor TRX após sua estrutura e outras extensões:

var trxCapability = new TrxCapability();
var builder = await TestApplication.CreateBuilderAsync(args);
builder.RegisterTestFramework(
    _ => new TestFrameworkCapabilities(trxCapability),
    (_, _) => new ReportFramework(trxCapability));
builder.AddTrxReportProvider();
Dim trxCapability As New TrxCapability()
Dim builder = Await TestApplication.CreateBuilderAsync(args)
builder.RegisterTestFramework(
    Function(services) New TestFrameworkCapabilities(trxCapability),
    Function(capabilities, services) New ReportFramework(trxCapability))
builder.AddTrxReportProvider()

O pacote de abstração fornece propriedades tipadas para TestNode.Properties:

Property Purpose
TrxFullyQualifiedTypeNameProperty Fornece o nome da classe de teste totalmente qualificado.
TrxCategoriesProperty Fornece as categorias do teste.
TrxExceptionProperty Fornece a mensagem de falha e o rastreamento de pilha.
TrxMessagesProperty Contém as entradas StandardOutputTrxMessage, StandardErrorTrxMessage e DebugOrTraceTrxMessage.
TrxTestDefinitionName Substitui o nome da definição de teste no relatório.
TrxWorkItemsProperty Fornece identificadores de item de trabalho.

Por exemplo, enriqueça um resultado depois que seu framework determinar o resultado do teste:

if (trxCapability.IsEnabled)
{
    testNode.Properties.Add(
        new TrxFullyQualifiedTypeNameProperty("Contoso.CalculatorTests"));
    testNode.Properties.Add(new TrxCategoriesProperty(["Unit"]));
    testNode.Properties.Add(new TrxMessagesProperty(
        [new StandardOutputTrxMessage($"Captured output for {test.Name}.")]));
    if (failure is not null)
    {
        testNode.Properties.Add(
            new TrxExceptionProperty(failure.Message, failure.StackTrace));
    }
}
If _trxCapability.IsEnabled Then
    testNode.Properties.Add(
        New TrxFullyQualifiedTypeNameProperty("Contoso.CalculatorTests"))
    testNode.Properties.Add(New TrxCategoriesProperty({"Unit"}))
    testNode.Properties.Add(New TrxMessagesProperty(
        {New StandardOutputTrxMessage($"Captured output for {test.Name}.")}))
    If failure IsNot Nothing Then
        testNode.Properties.Add(
            New TrxExceptionProperty(failure.Message, failure.StackTrace))
    End If
End If

Essas propriedades complementam o estado de nó de teste necessário; elas não o substituem. As mensagens específicas do TRX também não substituem StandardOutputProperty e StandardErrorProperty da plataforma.

O exemplo completo contém um teste bem-sucedido e um teste com falha intencional. Com --report-trxele grava os resultados e os metadados enriquecidos em um arquivo TRX e retorna o código 2de saída. Para executar apenas o teste aprovado, adicione --filter-uid trx.pass.

Se a funcionalidade estiver ausente ou IsSupported estiver false, o repórter TRX avisará que o framework não dá suporte ao enriquecimento TRX. O repórter ainda pode criar um relatório com base nos dados disponíveis, mas os detalhes específicos do framework podem estar ausentes. Não anuncie suporte a menos que sua estrutura forneça os metadados correspondentes.

IGracefulStopTestExecutionCapability

Para dar suporte a --maximum-failed-tests em uma estrutura de teste personalizada, implemente Microsoft.Testing.Platform.Capabilities.TestFramework.IGracefulStopTestExecutionCapability. A funcionalidade faz parte do pacote Microsoft.Testing.Platform e está disponível a partir do MTP 1.5. Os autores de teste que usam uma estrutura que já dá suporte à opção não precisam implementar a funcionalidade.

A interface define Task StopTestExecutionAsync(CancellationToken cancellationToken). Quando a contagem de falhas atinge o máximo solicitado, o MTP chama esse método. Sua implementação deve informar a estrutura para interromper o agendamento de novos testes, enquanto permite que os testes que já foram iniciados sejam concluídos, executem a limpeza e publiquem seus resultados.

Importante

A funcionalidade e o auxiliar AddMaximumFailedTestsService são APIs experimentais que produzem diagnósticos do compilador TPEXP. Seus contratos podem ser alterados ou removidos. Os projetos de exemplo suprimem TPEXP explicitamente.

Siga estes requisitos ao implementar a funcionalidade:

  • Compartilhe o estado de parada entre a funcionalidade e o agendador de teste do framework. Apenas retornar uma tarefa concluída não interrompe os testes.
  • Torne as solicitações de parada thread-safe e idempotentes. Mais de um componente de plataforma pode solicitar a interrupção.
  • Retorne após sinalizar a parada. Não aguarde até que toda a execução ou o barramento de mensagens seja concluído: o MTP pode chamar o método enquanto processa um resultado de teste, pois essa espera pode causar um deadlock.
  • Mantenha o estado de parada controlada separado do cancelamento da solicitação. Não encerre o processo nem cancele a publicação de resultados para implementar uma interrupção controlada. Honre o token de cancelamento do método se a operação de parada exigir trabalho assíncrono.
  • Após a conclusão dos testes ativos, conclua o ciclo de vida normal da solicitação, incluindo a publicação de resultados, a limpeza e a chamada para ExecuteRequestContext.Complete().

A implementação a seguir usa um sinalizador thread-safe. Em seu framework, verifique IsStopRequested antes de agendar cada teste e pare de aceitar novo trabalho quando o sinalizador for true.

public sealed class GracefulStopCapability : IGracefulStopTestExecutionCapability
{
    private int _stopRequested;

    public bool IsStopRequested => Volatile.Read(ref _stopRequested) != 0;

    public Task StopTestExecutionAsync(CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        Interlocked.Exchange(ref _stopRequested, 1);
        return Task.CompletedTask;
    }
}
Public NotInheritable Class GracefulStopCapability
    Implements IGracefulStopTestExecutionCapability

    Private _stopRequested As Integer

    Public ReadOnly Property IsStopRequested As Boolean
        Get
            Return Volatile.Read(_stopRequested) <> 0
        End Get
    End Property

    Public Function StopTestExecutionAsync(cancellationToken As CancellationToken) As Task _
        Implements IGracefulStopTestExecutionCapability.StopTestExecutionAsync

        cancellationToken.ThrowIfCancellationRequested()
        Interlocked.Exchange(_stopRequested, 1)
        Return Task.CompletedTask
    End Function
End Class

Expor a capacidade por meio de ITestFrameworkCapabilities.Capabilities. O exemplo de registro usa a coleção do TestFrameworkCapabilities MTP.

Durante o registro do framework de teste, passe a instância de capability registrada para seu framework. O auxiliar a seguir aceita sua fábrica de framework e um IExtension que possui a opção de linha de comando. Ele também chama AddMaximumFailedTestsService do Microsoft.Testing.Platform.Helpers namespace para registrar o provedor de opção --maximum-failed-tests . Sem esse provedor, o MTP não reconhece a opção.

public static void Register(
    ITestApplicationBuilder builder,
    IExtension owner,
    Func<GracefulStopCapability, ITestFramework> createFramework)
{
    builder.RegisterTestFramework(
        _ => new TestFrameworkCapabilities(new GracefulStopCapability()),
        (capabilities, _) => createFramework(
            capabilities.Capabilities.OfType<GracefulStopCapability>().Single()));
    builder.AddMaximumFailedTestsService(owner);
}
Public Sub Register(
    builder As ITestApplicationBuilder,
    owner As IExtension,
    createFramework As Func(Of GracefulStopCapability, ITestFramework))

    builder.RegisterTestFramework(
        Function(services) New TestFrameworkCapabilities(New GracefulStopCapability()),
        Function(capabilities, services) createFramework(
            capabilities.Capabilities.OfType(Of GracefulStopCapability)().Single()))
    builder.AddMaximumFailedTestsService(owner)
End Sub

A fábrica de recursos cria um novo estado de parada e a fábrica de framework recupera essa mesma instância. Não crie uma segunda capacidade desconectada em seu framework. Para frameworks que dão suporte a várias solicitações de execução, mantenha o estado de parada no escopo da execução correspondente. Não remova uma solicitação de parada aceita quando a execução for iniciada.

O MTP rejeita --maximum-failed-tests durante a validação da linha de comando se o provedor de opções estiver registrado, mas o framework não expuser essa capacidade. Quando o limite de falha dispara uma parada, o MTP retorna o código 13de saída. A contagem final de falhas pode exceder o limite porque o processamento de resultados é assíncrono e os testes podem já estar em andamento.

Para saber o contrato da interface, consulte o código-fonte IGracefulStopTestExecutionCapability.