Microsoft.Testing.Platform (MTP) 機能

MTP のコンテキストでは、 機能 とは、 特定のアクションを実行したり、特定の情報を提供したりする可能性を指します。 これは、テスト フレームワークと拡張機能が特定の方法で動作する能力を宣言したり、要求者に特定の情報を提供したりする手段です。

要求者には、プラットフォーム、拡張機能、テスト フレームワーク自体など、テスト セッションに関係する任意のコンポーネントを指定できます。

機能システムの主な目的は、テスト セッションに関係するコンポーネント間の効果的な通信を容易にし、情報をexchangeし、それぞれのニーズを正確に満たすことです。

ガイド付き例

機能システムの必要性を示す架空の例を考えてみましょう。

注

この例は説明のみを目的としており、現在、MTP またはテスト フレームワーク内には実装されていません。

テストフレームワークが一度に1つのテストのみを実行する必要がある拡張機能の状況を想像してください。 さらに、各テストの後、拡張機能はその特定のテストの CPU 使用率を認識する必要があります。

上記のシナリオに対応するには、次の場合にテスト フレームワークから問い合わせる必要があります。

  1. 一度に 1 つのテストのみを実行する機能があります。
  2. 各テストで使用される CPU の量に関する情報を提供できます。

拡張機能は、テスト フレームワークがこのモードで動作し、テスト セッションの CPU 使用率情報を提供する機能を持っているかどうかを判断するにはどうすればよいですか? MTP では、この機能は 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
{
}

ご覧のように、ICapability インターフェイスは任意の機能を表すことができるため、マーカー インターフェイスであり、実際の実装はコンテキストに依存します。 また、ITestFrameworkCapabilityから継承して機能を分類するICapabilityも確認します。 機能システムの一般的な性質により、コンテキストによる便利なグループ化が可能になります。 ITestFrameworkCapabilityは、テスト フレームワークによって実装されるすべての機能をグループ化します。 ICapabilities<TCapability> インターフェイスは、拡張機能によって実装されるすべての機能のセットを明らかにします。 同様に、基本フレームワークには、 ITestFrameworkCapabilitiesと呼ばれるコンテキスト固有のテスト フレームワークがあります。 ITestFrameworkCapabilitiesは、テスト フレームワークの登録プロセス中にプラットフォームに提供されます。

前述のシナリオに対応する機能を作成するには、次のように定義します。

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

テスト フレームワークがこのインターフェイスを実装している場合は、実行時に次のクエリを実行できます。

  • テスト フレームワークに並列処理 CanDisableParallelism = trueを無効にする機能があるかどうかを確認します。
  • テスト フレームワークが CPU 使用率データを CanProvidePerTestCPUConsumption = true提供できるかどうかを判断します。
  • テスト セッションが開始される前に、 Enable() メソッドを呼び出して、テスト アダプターにこのモードのアクティブ化を要求します。

拡張機能内の架空のコード フラグメントは、次のようになります。

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

前の例では、機能インフラストラクチャを使用して、テスト セッションに関係するコンポーネント間で機能を通信するための強力なメカニズムを実現する方法を示します。 このサンプルでは、テスト フレームワーク用に特別に設計された機能を示していますが、どのコンポーネントでも、 ICapabilityから継承する拡張機能を公開して実装できます。

インターフェイスを介してすべての詳細を伝達できるわけではないのは明らかです。 前の例を考えると、 CanProvidePerTestCpuConsumption がサポートされている場合、拡張機能は何を期待する必要がありますか? テスト フレームワークによって IMessageBus を介して送信されるカスタム情報の種類は何ですか? このソリューションは、 この機能のドキュメントです。 特定の機能を必要とする拡張機能と効果的に共同作業を行う実装者を支援するために、可能な限り明確に設計、出荷、文書化するのは、機能所有者の責任です。

たとえば、TRX レポート拡張機能を使用すると、TRX レポートを正確に生成するために必要な機能をテスト フレームワークで実装できます。 登録する拡張機能は https://www.nuget.org/packages/Microsoft.Testing.Extensions.TrxReport NuGet パッケージに含まれていますが、実装する機能は コントラクト専用パッケージにあります。

結論として、機能システムの主な側面をまとめてみましょう。

  • コンポーネント間の明確で安定した通信を促進するために不可欠です。
  • すべての機能は、 ICapability またはそれを継承するインターフェイスから継承する必要があり、 ICapabilities インターフェイスを持つコレクションを介して公開されます。
  • これは、破壊的変更を引き起こすことなく、特徴の進化に役立ちます。 特定の機能がサポートされていない場合は、適切なアクションを実行できます。
  • 機能の使用方法を設計、出荷、文書化する責任は、機能 所有者にあります。 MTP は、他の拡張機能と同じ方法で機能を 所有 することもできます。

フレームワークの機能

プラットフォームは、テスト フレームワーク用に公開されるすべての機能のベースである ITestFrameworkCapability という名前の特殊なインターフェイスを公開します。 これらの機能は、 テスト フレームワークをプラットフォームに登録するときに提供されます。

IBannerMessageOwnerCapability

オプションの テスト フレームワーク機能により、 テスト フレームワークはバナー メッセージをプラットフォームに提供できます。 メッセージが null されている場合、または機能が存在しない場合、プラットフォームは既定のバナー メッセージを使用します。

この機能の実装により、バナー メッセージを表示するかどうかを決定するときにテスト フレームワークが考慮する必要があるさまざまな条件を抽象化できます。

プラットフォームは、カスタム バナー メッセージを作成するときに役立つ可能性のあるプラットフォームに関する情報を提供するために、 IPlatformInformation サービス を公開します。

ITrxReportCapability

フレームワーク固有のメタデータをTRX レポート拡張機能に提供するには、Microsoft.Testing.Extensions.TrxReport.Abstractions.ITrxReportCapability を実装してください。 フレームワークから Microsoft.Testing.Extensions.TrxReport.Abstractions パッケージを参照してください。 テスト アプリケーションには、レポートを作成するために Microsoft.Testing.Extensions.TrxReport パッケージも必要です。

この機能により、サポートとアクティブ化が区別されます:

  • フレームワークが TRX メタデータを提供できる場合は、true から IsSupported を返します。
  • Enable() に、TRX レポーターがメタデータを要求したことを記録します。 レポーターは、TRX レポートが有効になっているときに、テスト セッションの開始時にこのメソッドを呼び出します。
  • アクティブ化後、フレームワークが発行するテスト ノードに TRX プロパティを追加してください。

以下の機能は、アクティブ化の状態を保存します。 サンプル プロジェクトは .NET 10 をターゲットとし、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

RegisterTestFramework に渡した capabilitiesFactory からの機能を返します。 Enable()を観察できるように、フレームワークに同じ機能インスタンスを与えます。 手動で構成されたテスト アプリケーションの場合は、フレームワークやその他の拡張機能の後に TRX プロバイダーを登録します。

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

この抽象化パッケージは、TestNode.Properties に対して型付きのプロパティを提供します:

プロパティ Purpose
TrxFullyQualifiedTypeNameProperty 完全修飾されたテスト クラス名を指定します。
TrxCategoriesProperty テストのカテゴリを指定します。
TrxExceptionProperty 失敗メッセージとスタック トレースを提供します。
TrxMessagesProperty StandardOutputTrxMessage、StandardErrorTrxMessage、およびDebugOrTraceTrxMessageエントリを格納します。
TrxTestDefinitionName レポート内のテスト定義名をオーバーライドします。
TrxWorkItemsProperty 作業項目識別子を提供します。

たとえば、フレームワークがテストの結果を判定した後、その結果に情報を追加する場合などです:

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

これらのプロパティは必要なテスト ノードの状態を補完します。これらはそれに代わるものではありません。 TRX 固有のメッセージも、プラットフォームの StandardOutputProperty と StandardErrorProperty を置き換えることはありません。

完全なサンプルには、1 つの合格テストと 1 つの意図的に失敗したテストが含まれています。 --report-trxでは、結果とエンリッチされたメタデータの両方を TRX ファイルに書き込み、終了コード2返します。 合格テストのみを実行するには、--filter-uid trx.passを追加してください。

機能がない場合、または IsSupported が false の場合、TRX レポーターは、フレームワークが TRX エンリッチメントをサポートしていないことを警告します。 レポーターは使用可能なデータからレポートを作成できますが、フレームワーク固有の詳細が見つからない可能性があります。 フレームワークが対応するメタデータを提供しない限り、サポートをアドバタイズしないでください。

IGracefulStopTestExecutionCapability

カスタム テスト フレームワークで --maximum-failed-tests をサポートするには、 Microsoft.Testing.Platform.Capabilities.TestFramework.IGracefulStopTestExecutionCapabilityを実装します。 この機能は、Microsoft.Testing.Platform パッケージの一部であり、MTP 1.5 以降で利用できます。 オプションを既にサポートしているフレームワークを使用するテスト作成者は、その機能性を実装する必要はありません。

このインターフェースは Task StopTestExecutionAsync(CancellationToken cancellationToken) を定義します。 失敗回数が要求された最大値に達すると、MTP はこのメソッドを呼び出します。 実装では、既に開始されているテストの完了、クリーンアップの実行、結果の公開を許可しながら、新しいテストのスケジュール設定を停止するようにフレームワークに指示する必要があります。

Important

この機能および AddMaximumFailedTestsService ヘルパーは、コンパイラ診断メッセージ TPEXP を生成する実験的な API です。 契約内容は変更されたり、解除されたりすることがあります。 サンプル プロジェクトでは、TPEXP を明示的に抑制しています。

機能を実装するときは、次の要件に従います。

  • 機能とフレームワークのテスト スケジューラの間で停止状態を共有します。 完了したタスクを返すだけでは、テストは停止しません。
  • 停止要求をスレッド セーフに保ち、べき等性を持たせる。 複数のプラットフォーム コンポーネントが停止を要求できます。
  • 停止を指示した後に戻ります。 実行全体またはメッセージ バス全体が終了するのを待つ必要はありません。MTP はテスト結果を処理している間にメソッドを呼び出すことができるため、待機でデッドロックが発生する可能性があります。
  • 正常終了の状態と要求のキャンセルは区別して認識してください。 グレースフル ストップを実装するために、プロセスを終了したり、結果のパブリケーションを取り消したりしないでください。 ストップ操作で非同期処理が必要な場合は、メソッドのキャンセル トークンを尊重してください。
  • アクティブなテストが完了したら、結果の公開、クリーンアップ、 ExecuteRequestContext.Complete()の呼び出しなど、通常の要求ライフサイクルを完了します。

次の実装では、スレッド セーフ フラグを使用します。 フレームワークで、各テストをスケジュールする前に IsStopRequested を確認し、フラグが 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

ITestFrameworkCapabilities.Capabilities を通じてその機能を公開します。 登録例では、MTP の TestFrameworkCapabilities コレクションを使用します。

テスト フレームワークの登録中に、登録済みの機能インスタンスをフレームワークに渡してください。 以下のヘルパーは、フレームワーク ファクトリと、コマンドライン オプションを保持する IExtension を受け取ります。 また、AddMaximumFailedTestsService 名前空間から Microsoft.Testing.Platform.Helpers を呼び出して、--maximum-failed-tests オプション プロバイダーを登録します。 そのプロバイダーがないと、MTP はオプションを認識しません。

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

capability ファクトリは新しい停止状態を作成し、フレームワーク ファクトリはその同じインスタンスを取得します。 フレームワーク内に、連携していない別の機能を作成しないでください。 複数の実行要求をサポートするフレームワークの場合は、停止状態は対応する実行のスコープ内に保持します。 実行の開始時に、受け入れられた停止要求をクリアしないでください。

オプション プロバイダーが登録されているが、フレームワークがこの機能を公開していない場合、MTP はコマンド ライン検証中に --maximum-failed-tests を拒否します。 エラーしきい値によって停止がトリガーされると、MTP は 終了コード 13を返します。 結果処理は非同期であり、テストが既に進行中である可能性があるため、最終的なエラー数がしきい値を超える可能性があります。

インターフェイス コントラクトについては、 IGracefulStopTestExecutionCapability ソースを参照してください。