Microsoft.Testing.Platform (MTP) capacidades

En el contexto de MTP, una funcionalidad hace referencia a la posibilidad de realizar una acción específica o proporcionar información específica. Es un medio para que el marco de pruebas y las extensiones declaren su capacidad de operar de una manera determinada o proporcionar información específica a los solicitantes.

Los solicitantes pueden ser cualquier componente implicado en una sesión de prueba, como la plataforma, una extensión o el propio marco de pruebas.

El objetivo principal del sistema de funcionalidad es facilitar la comunicación eficaz entre los componentes implicados en una sesión de prueba, lo que les permite exchange información y satisfacer sus necesidades respectivas con precisión.

Ejemplo guiado

Consideremos un ejemplo hipotético para demostrar la necesidad de un sistema de funcionalidad.

Nota:

Este ejemplo es exclusivamente para fines ilustrativos y no se implementa actualmente en MTP ni en ningún marco de pruebas.

Imagine una situación en la que tiene una extensión que requiere que el marco de pruebas no ejecute más de una prueba a la vez. Además, después de cada prueba, la extensión debe conocer el uso de CPU para esa prueba específica.

Para dar cabida al escenario anterior, debe consultar el marco de pruebas si:

  1. Tiene la capacidad de ejecutar solo una prueba a la vez.
  2. Puede proporcionar información sobre la cantidad de CPU consumida por cada prueba.

¿Cómo puede determinar la extensión si el marco de pruebas tiene la capacidad de funcionar en este modo y proporcionar información de uso de CPU para una sesión de prueba? En MTP, esta funcionalidad se representa mediante una implementación de la interfaz 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 puede ver, la ICapability interfaz es una interfaz de marcador porque puede representar cualquier funcionalidad y la implementación real dependerá del contexto. También observará ITestFrameworkCapability, que hereda de ICapability para clasificar la funcionalidad. La naturaleza genérica del sistema de funcionalidad permite agrupar convenientemente por contexto. El ITestFrameworkCapability agrupa todas las capacidades implementadas por el marco de pruebas. La ICapabilities<TCapability> interfaz revela el conjunto de todas las funcionalidades implementadas por una extensión. Del mismo modo, para el caso base, hay un marco de pruebas específico del contexto denominado ITestFrameworkCapabilities. El ITestFrameworkCapabilities se proporciona a la plataforma durante el proceso de registro del marco de pruebas.

Para crear una funcionalidad que aborde el escenario mencionado anteriormente, defina como se indica a continuación:

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

Si el marco de pruebas implementa esta interfaz, en tiempo de ejecución, se puede consultar lo siguiente:

  • Compruebe si el marco de pruebas tiene la capacidad de desactivar el paralelismo CanDisableParallelism = true.
  • Determine si el marco de pruebas puede proporcionar datos CanProvidePerTestCPUConsumption = truede uso de CPU.
  • Solicite al adaptador de prueba que active este modo invocando el Enable() método antes de que comience la sesión de prueba.

El fragmento de código hipotético dentro de la extensión podría ser algo parecido a:

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

En el ejemplo anterior se muestra cómo la infraestructura de funcionalidad permite un mecanismo eficaz para comunicar capacidades entre los componentes implicados en una sesión de prueba. Aunque el ejemplo muestra una funcionalidad diseñada específicamente para el marco de pruebas, cualquier componente puede exponer e implementar extensiones que heredan de ICapability.

Es evidente que no todos los detalles se pueden comunicar a través de una interfaz. Teniendo en cuenta el ejemplo anterior, ¿qué debe esperar la extensión si se admite CanProvidePerTestCpuConsumption? ¿Qué tipo de información personalizada se espera que se transmita a través de IMessageBus mediante el marco de pruebas? La solución es documentación de la funcionalidad. Es responsabilidad del propietario de la capacidad diseñar, enviar y documentarlo lo más claramente posible para ayudar a los implementadores que desean colaborar eficazmente con la extensión que requiere la funcionalidad específica.

Por ejemplo, la extensión de informe TRX permite al marco de pruebas implementar la funcionalidad necesaria para generar con precisión un informe TRX. La extensión que hay que registrar está incluida en el paquete NuGet https://www.nuget.org/packages/Microsoft.Testing.Extensions.TrxReport, pero la capacidad de implementación se encuentra en el paquete que solo contiene contratos.

En conclusión, resumiremos los aspectos principales del sistema de funcionalidad:

  • Es esencial para facilitar la comunicación clara y estable entre los componentes.
  • Todas las funcionalidades deben heredar de ICapability o de una interfaz que herede de esta, y se deben exponer a través de una colección con la interfaz ICapabilities.
  • Ayuda a la evolución de las características sin causar cambios importantes. Si no se admite una determinada funcionalidad, se pueden realizar acciones adecuadas.
  • La responsabilidad de diseñar, enviar y documentar el uso de una funcionalidad reside en el propietario de la funcionalidad. MTP también puede poseer una funcionalidad de la misma manera que cualquier otra extensión.

Funcionalidades del marco

La plataforma expone una interfaz especializada denominada ITestFrameworkCapability que es la base de todas las funcionalidades expuestas para marcos de pruebas. Estas funcionalidades se proporcionan al registrar el marco de pruebas en la plataforma.

IBannerMessageOwnerCapability

Una funcionalidad opcional del marco de pruebas permite que el marco de pruebas proporcione el mensaje de banner a la plataforma. Si el mensaje es null o la funcionalidad no está presente, la plataforma usa su mensaje de banner predeterminado.

Esta implementación de funcionalidad le permite abstraer las diversas condiciones que puede que el marco de pruebas tenga que tener en cuenta al decidir si se debe mostrar o no el mensaje de banner.

La plataforma expone el IPlatformInformation servicio para proporcionar información sobre la plataforma que podría ser útil al compilar el mensaje de banner personalizado.

ITrxReportCapability

Para proporcionar metadatos específicos del marco de trabajo a la extensión de informe TRX, implemente Microsoft.Testing.Extensions.TrxReport.Abstractions.ITrxReportCapability. Haga referencia al paquete Microsoft.Testing.Extensions.TrxReport.Abstractions desde su framework. La aplicación de prueba también necesita el paquete Microsoft.Testing.Extensions.TrxReport para crear informes.

La funcionalidad separa el soporte de la activación:

  • Devuelva true desde IsSupported cuando el marco pueda proporcionar metadatos TRX.
  • En Enable(), registre que el informador TRX solicitó los metadatos. El generador de informes llama a este método al principio de una sesión de prueba cuando se habilitan los informes TRX.
  • Después de la activación, agregue las propiedades TRX a los nodos de prueba que publica su marco.

La siguiente funcionalidad almacena el estado de activación. Los proyectos de ejemplo tienen como destino .NET 10 y hacen referencia a 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

Devuelve la funcionalidad de capabilitiesFactory que se pasa a RegisterTestFramework. Asigne al marco la misma instancia de capacidad para que pueda observar Enable(). Para una aplicación de prueba compuesta manualmente, registre el proveedor TRX después del framework y otras extensiones:

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

El paquete de abstracción proporciona propiedades con tipo para TestNode.Properties:

Property Purpose
TrxFullyQualifiedTypeNameProperty Proporciona el nombre de clase de prueba completo.
TrxCategoriesProperty Proporciona las categorías de la prueba.
TrxExceptionProperty Proporciona el mensaje de error y la traza de pila.
TrxMessagesProperty Contiene entradas StandardOutputTrxMessage, StandardErrorTrxMessage y DebugOrTraceTrxMessage.
TrxTestDefinitionName Sobrescribe el nombre de la definición de prueba en el informe.
TrxWorkItemsProperty Proporciona identificadores de elemento de trabajo.

Por ejemplo, enriquezca un resultado después de que el marco haya determinado el resultado de la prueba:

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

Estas propiedades complementan el estado necesario del nodo de prueba; no lo reemplazan. Los mensajes específicos de TRX tampoco reemplazan los StandardOutputProperty y StandardErrorProperty de la plataforma.

El ejemplo completo contiene una prueba satisfactoria y una prueba que falla intencionadamente. Con --report-trx, escribe los resultados y los metadatos enriquecidos en un archivo TRX y, a continuación, devuelve el código 2de salida . Para ejecutar solo la prueba superda, agregue --filter-uid trx.pass.

Si la funcionalidad está ausente o IsSupported es false, el informador TRX advierte de que el framework no admite el enriquecimiento con TRX. El generador de informes todavía puede crear un informe a partir de los datos disponibles, pero es posible que falten detalles específicos del marco. No anuncie compatibilidad a menos que el marco proporcione los metadatos correspondientes.

IGracefulStopTestExecutionCapability

Para admitir --maximum-failed-tests en un marco de pruebas personalizado, implemente Microsoft.Testing.Platform.Capabilities.TestFramework.IGracefulStopTestExecutionCapability. La funcionalidad forma parte del paquete Microsoft.Testing.Platform y está disponible a partir de MTP 1.5. Los autores de pruebas que usan un marco que ya admite la opción no necesitan implementar la funcionalidad.

La interfaz define Task StopTestExecutionAsync(CancellationToken cancellationToken). Cuando el recuento de errores alcanza el máximo solicitado, MTP llama a este método. La implementación debe indicar al marco que deje de programar nuevas pruebas mientras permite que las pruebas que ya hayan empezado terminen, ejecuten la limpieza y publiquen sus resultados.

Importante

La capacidad y el AddMaximumFailedTestsService asistente son API experimentales que generan diagnóstico TPEXPdel compilador. Los contratos pueden cambiar o quitarse. Los proyectos de ejemplo suprimen TPEXP explícitamente.

Siga estos requisitos al implementar la funcionalidad:

  • Comparta el estado de detención entre la funcionalidad y el planificador de pruebas del marco. Simplemente devolver una tarea completada no detiene las pruebas.
  • Haga que las solicitudes de parada sean seguras para ejecución multihilo e idempotentes. Más de un componente de plataforma puede solicitar una detención.
  • Vuelva después de señalar la parada. No espere a que finalice toda la ejecución o el bus de mensajes: MTP puede llamar al método mientras procesa un resultado de prueba, por lo que esa espera puede provocar un interbloqueo.
  • Mantenga el estado de detención ordenada independiente de la cancelación de la solicitud. No finalice el proceso ni cancele la publicación de resultados para implementar una detención ordenada. Respeta el token de cancelación del método si la operación de detención requiere trabajo asincrónico.
  • Una vez finalizadas las pruebas activas, complete el ciclo de vida de la solicitud normal, incluida la publicación de resultados, la limpieza y la llamada a ExecuteRequestContext.Complete().

La siguiente implementación usa un indicador seguro para hilos. En su framework, compruebe IsStopRequested antes de programar cada prueba y deje de aceptar trabajo nuevo cuando el indicador sea 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

Exponga la funcionalidad a través de ITestFrameworkCapabilities.Capabilities. En el ejemplo de registro se usa la colección TestFrameworkCapabilities de MTP.

Durante el registro del framework de pruebas, pase la instancia de capacidad registrada al framework. La siguiente función auxiliar acepta la factoría del framework y un IExtension que tiene asociada la opción de línea de comandos. También llama AddMaximumFailedTestsService del espacio de nombres Microsoft.Testing.Platform.Helpers para registrar el proveedor de opciones --maximum-failed-tests. Sin ese proveedor, MTP no reconoce la opción.

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

La factoría de capacidades crea un nuevo estado de parada, y la factoría de framework recupera esa misma instancia. No cree una segunda funcionalidad desconectada en el framework. En el caso de los marcos que admiten varias solicitudes de ejecución, mantenga el estado de detención en el ámbito de la ejecución correspondiente. No borre una solicitud de parada aceptada cuando se inicie la ejecución.

MTP rechaza --maximum-failed-tests durante la validación de la línea de comandos si el proveedor de opciones está registrado, pero el marco no expone esta funcionalidad. Cuando el umbral de error desencadena una detención, MTP devuelve el código 13de salida . El recuento final de errores puede superar el umbral porque el procesamiento de resultados es asincrónico y es posible que las pruebas ya estén en curso.

Para obtener el contrato de interfaz, consulte elIGracefulStopTestExecutionCapability fuente.