Microsoft.Testing.Platform (MTP) 用の拡張機能を構築する

この記事では、テスト フレームワーク自体以外の MTP の機能拡張ポイントについて説明します。 テスト フレームワークの作成については、「 テスト フレームワークの構築」を参照してください。

完全な拡張ポイントの概要とプロセス内/プロセス外の概念については、「 カスタム拡張機能の作成」を参照してください。

機能拡張ポイント

テスト プラットフォームには、プラットフォームとテスト フレームワークの動作をカスタマイズできる追加の拡張ポイントが用意されています。 これらの拡張ポイントは省略可能であり、テスト エクスペリエンスを強化するために使用できます。

Tip

この記事に示す各拡張機能には、手動登録スニペット ( builder.TestHost.AddDataConsumer(...) など) が含まれています。 拡張機能を NuGet パッケージとして出荷する場合は、 TestingPlatformBuilderHook と小さな MSBuild props ファイルを公開することで、コンシューマーが手動呼び出しをスキップできます。 自動生成されたエントリ ポイントによって、フックが自動的に呼び出されます。 詳細については、「拡張機能をTestingPlatformBuilderHookに自動登録する」を参照してください

ICommandLineOptionsProvider 拡張

この API を拡張すると、カスタム拡張機能はテスト ホスト プロセスの内外の両方に存在することになります。

アーキテクチャ セクションで説明したように、最初の手順では、ITestApplicationBuilder を作成し、それを使用してテスト フレームワークと拡張機能を登録する必要があります。

var builder = await TestApplication.CreateBuilderAsync(args);

CreateBuilderAsync メソッドは、string[] という名前の文字列 (args) の配列を受け入れます。 これらの引数は、テスト プラットフォームのすべてのコンポーネント (組み込みコンポーネント、テスト フレームワーク、および拡張機能を含む) にコマンド ライン オプションを渡すために使用でき、それらの動作をカスタマイズすることが可能です。

通常、渡される引数は標準 Main(string[] args) メソッドで受け取った引数です。 ただし、ホスティング環境が異なる場合は、引数の一覧を指定できます。

引数には、必ずダブルハイフン----を付けなければなりません。 たとえば、「 --filter 」のように入力します。

テスト フレームワークや拡張点などのコンポーネントでカスタム コマンド ライン オプションを提供する必要がある場合は、ICommandLineOptionsProvider インターフェイスを実装することでこれを行うことができます。 その後、この実装は、次に示すように ITestApplicationBuilder プロパティの登録ファクトリを介して CommandLine に登録できます。

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

提供されている例では、CustomCommandLineOptionsICommandLineOptionsProvider インターフェイスの実装です。このインターフェイスは、次のメンバーとデータ型で構成されます。

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

以上のように、ICommandLineOptionsProviderIExtension インターフェイスを拡張します。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

ICommandLineOptionsProvider の実行順序は次のとおりです。

API とその平均値を調べてみましょう。

ICommandLineOptionsProvider.GetCommandLineOptions(): このメソッドは、コンポーネントで提供されるすべてのオプションを取得するために使用されます。 各 CommandLineOption は、次のプロパティを指定する必要があります。

string name: これはオプションの名前で、ダッシュなしで表示されます。 たとえば、filter はユーザーにより --filter として使用されます。

string description: これはオプションの説明です。 ユーザーがアプリケーション ビルダーに --help を引数として渡すと表示されます。

ArgumentArity arity: オプションのアリティとは、そのオプションまたはコマンドが指定された場合に渡すことができる値の数を指します。 現在使用可能な機能は次のとおりです。

  • Zero: 引数アリティが 0 であることを表します。
  • ZeroOrOne: 引数アリティが 0 または 1 であることを表します。
  • ZeroOrMore: 引数アリティが 0 以上であることを表します。
  • OneOrMore: 引数アリティが 1 つ以上であることを表します。
  • ExactlyOne: 引数アリティが 1 つのみであることを表します。

例については、System.CommandLine アリティ テーブルを参照してください。

bool isHidden: このプロパティは、オプションは使用できるものの、--help が呼び出されると説明には表示されないことを示します。

ICommandLineOptionsProvider.ValidateOptionArgumentsAsync: このメソッドは、ユーザーによって提供される引数を検証するために使用されます。

たとえば、カスタム テスト フレームワークの並列処理の程度を表す、--dop という名前のパラメーターがある場合、ユーザーは --dop 0 を入力する可能性があります。 このシナリオでは、並列処理の程度は 0 以上が想定されるため、値 1 は無効になります。 ValidateOptionArgumentsAsync を使用すると、事前検証を実行し、必要に応じてエラー メッセージを返すことができます。

上記のサンプルに対して可能な実装は、次のとおりです。

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: このメソッドは最後のメソッドとして呼び出され、グローバルな一貫性チェックを実行できます。

たとえば、テスト フレームワークに、テスト結果レポートを生成してファイルに保存する機能があるとします。 この機能には --generatereport オプションを使用してアクセスでき、ファイル名は --reportfilename myfile.rep で指定されます。 このシナリオでは、ユーザーがファイル名を指定せずに --generatereport オプションのみを指定した場合、ファイル名なしでレポートを生成できないため、検証は失敗します。 上記のサンプルに対して可能な実装は、次のとおりです。

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

ValidateCommandLineOptionsAsync メソッドは、プラットフォーム自体によって解析された引数情報をフェッチするために使用される ICommandLineOptions サービスを提供することに注意してください。

ITestSessionLifetimeHandler 拡張

ITestSessionLifetimeHandler は、テスト セッションのにコードを実行できるインプロセス拡張機能です。

カスタム ITestSessionLifetimeHandlerを登録するには、次の API を使用します。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

ファクトリは IServiceProvider を利用して、テスト プラットフォームで提供されるサービス スイートへのアクセスを取得します。

Von Bedeutung

API は登録された順序で呼び出されるため、登録のシーケンスは重要です。

ITestSessionLifetimeHandler インターフェイスには、以下のメソッドが含まれます。

public interface ITestSessionLifetimeHandler : ITestHostExtension
{
    Task OnTestSessionStartingAsync(ITestSessionContext testSessionContext);

    Task OnTestSessionFinishingAsync(ITestSessionContext testSessionContext);
}

public interface ITestSessionContext
{
    SessionUid SessionUid { get; }

    CancellationToken CancellationToken { get; }
}

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

public interface ITestHostExtension : IExtension
{
}

Von Bedeutung

MTP 2.0.0 では、両方のメソッドが、ITestSessionContextSessionUidを公開する単一のCancellationToken パラメーターを受け取るように変更されました。 MTP 1.x では、各メソッドは個別の SessionUidCancellationToken 引数を受け取りました。 詳細については、Microsoft.Testing.Platform (MTP) v1 から v2 への移行を参照してください。

ITestSessionLifetimeHandler は、すべてのITestHostExtension拡張機能のベースとして機能する の型です。 他のすべての拡張点と同様に、IExtension からも継承されます。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

この API の詳細に注意を払いましょう。

OnTestSessionStartingAsync: このメソッドは、テスト セッションの開始前に呼び出され、 ITestSessionContextを受け取ります。 SessionUid は現在のテスト セッションの不透明な識別子を提供します。

OnTestSessionFinishingAsync: このメソッドは、テスト セッションの完了後に呼び出され、テスト フレームワークがすべてのテストの実行を完了しており、関連するすべてのデータをプラットフォームに報告していることを確認します。 通常、このメソッドでは、拡張機能はカスタム資産またはデータを共有プラットフォーム バスに送信するために IMessageBus を使用します。 このメソッドは、テスト セッションが終了したことをカスタムのアウトプロセス拡張機能に通知することもできます。

最後に、ITestSessionContext は、拡張機能が従うことを想定した CancellationToken を公開します。

拡張機能が集中的な初期化を必要としており、ユーザーが async/await パターンを使用する必要がある場合には、Async extension initialization and cleanup を参照できます。 拡張点の間で状態を共有する必要がある場合は、CompositeExtensionFactory<T> セクションを参照できます。

ITestApplicationLifecycleCallbacks 拡張

Von Bedeutung

ITestApplicationLifecycleCallbacks は MTP 2.0.0 で削除されました。 ITestHostApplicationLifetime を代わりに使用します。 詳細については、Microsoft.Testing.Platform (MTP) v1 から v2 への移行を参照してください。

ITestHostApplicationLifetime インターフェイスを使用すると、インプロセス拡張機能でテスト ホストの開始時と終了時にコードを実行できます。

カスタム ITestHostApplicationLifetimeを登録するには、次の API を使用します。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestHostApplicationLifetime(
    static serviceProvider
    => new CustomTestHostApplicationLifetime());

ファクトリは IServiceProvider を 使用して、テスト プラットフォームによって提供されるサービスにアクセスします。

Von Bedeutung

API は登録された順序で呼び出されるため、登録のシーケンスは重要です。

ITestHostApplicationLifetime インターフェイスには、以下のメソッドが含まれます。

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

    Task AfterRunAsync(
        int exitCode,
        CancellationToken cancellationToken);
}

public interface ITestHostExtension : IExtension
{
}

ITestHostApplicationLifetime インターフェイスは、すべてのITestHostExtension拡張機能のベースとして機能するを拡張します。 他のすべての拡張点と同様に、IExtension からも継承されます。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

BeforeRunAsync: このメソッドは、テスト ホストの最初の接触点として機能し、インプロセス拡張機能が機能を実行する最初の機会です。 通常、機能が両方の環境で動作するように設計されている場合、対応するアウトプロセス拡張機能との接続を確立するために使用されます。

例えば、組み込みのハング ダンプ機能は、インプロセスアウトプロセスの両方の拡張機能で構成されており、このメソッドは拡張機能のアウトプロセス コンポーネントと情報を交換するために使用されます。

AfterRunAsync: このメソッドは int ITestApplication.RunAsync() を終了する前の最後の呼び出しであり、exit code を提供します。 これは、もっぱらクリーンアップ タスクのためと、対応するアウトプロセス拡張機能にテスト ホストが終了しようとしていることを通知するために使用する必要があります。

最後に、どちらの API も、拡張機能が受け入れる必要がある CancellationToken を取得します。

IDataConsumer 拡張

IDataConsumerは、テスト フレームワークとその拡張機能によって IData に発行される情報をサブスクライブおよび受信できるインプロセス拡張機能です。

この拡張点は、それによって開発者がテスト セッション中に生成されたすべての情報を収集して処理できるため、非常に重要です。

カスタム IDataConsumer を登録するには、次の API を利用します。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

ファクトリは IServiceProvider を利用して、テスト プラットフォームで提供されるサービス スイートへのアクセスを取得します。

Von Bedeutung

API は登録された順序で呼び出されるため、登録のシーケンスは重要です。

IDataConsumer インターフェイスには、以下のメソッドが含まれます。

public interface IDataConsumer : IExtension
{
    Type[] DataTypesConsumed { get; }

    Task ConsumeAsync(
        IDataProducer dataProducer,
        IData value,
        CancellationToken cancellationToken);
}

public interface IData
{
    string DisplayName { get; }
    string? Description { get; }
}

Von Bedeutung

MTP 2.0.0 では、IDataConsumerMicrosoft.Testing.Platform.Extensions名前空間に移動され、IExtension直接拡張されるようになりました。 MTP 1.x では、 ITestHostExtension拡張されました。 引き続きbuilder.TestHost.AddDataConsumer(...)で登録します。 詳細については、Microsoft.Testing.Platform (MTP) v1 から v2 への移行を参照してください。

IDataConsumerIExtension から継承されます。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

DataTypesConsumed: このプロパティは、この拡張機能が使用する予定の Type の一覧を返します。 これは IDataProducer.DataTypesProduced に対応します。 特に、IDataConsumer は異なる IDataProducer インスタンスから発生した複数の型を問題なくサブスクライブできます。

ConsumeAsync: このメソッドは、現在のコンシューマーがサブスクライブされている型のデータが IMessageBusに発行されるたびにトリガーされます。 IDataProducer を受け取って、データ ペイロードのプロデューサーと IData ペイロード自体に関する詳細を提供します。 ご覧のように、IData は、一般的な情報データを含む汎用プレースホルダー インターフェイスです。 さまざまな種類の IData を発行する機能は、コンシューマーが型自体を 切り替えて 正しい型にキャストし、特定の情報にアクセスする必要があることを意味します。

テストフレームワークによって生成されたを詳細に扱うコンシューマの実装例としては、次のようなものがあります。

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

最後に、API は拡張機能が受け入れると想定されている CancellationToken を取得します。

Von Bedeutung

ConsumeAsync メソッド内でペイロードを直接処理します。 通常の IDataConsumer では、データが非同期的に使用されます。 IMessageBus は、発行された各ペイロードをキューに入れ、バックグラウンド ループで処理するため、 IMessageBus.PublishAsync はプロデューサーをブロックせず、プロデューサーが作業を継続する場合に ConsumeAsync がいつ実行されるかについて保証されません。 プラットフォームは配信をシリアル化するため、コンシューマーごとに一度に 1 つのペイロードのみが処理されるため、1 つのコンシューマー内で複雑な同期を行う必要がなくなります。

プロデューサーが進行する前 (たとえば、テストの実行が開始される前) に消費が発生することを保証する必要があるシナリオの場合、MTP 2.3.0 では試験的な IBlockingDataConsumer マーカー インターフェイスが導入されました ( TPEXP 診断を抑制する必要があります)。 IBlockingDataConsumerも実装するコンシューマーは、メッセージ バスによってインラインで呼び出されます。呼び出しはシリアル化され、PublishAsyncが完了するまでConsumeAsyncブロックされ、ConsumeAsyncによってスローされた例外は、データをパブリッシュしたプロデューサーに反映されます。 メッセージ バスはプロデューサーのデータを同じプロデューサー (同じ UID) に戻すのをスキップするため、独自の UID での公開は安全です。 ただし、ブロッキングコンシューマーは、ConsumeAsync内から、のプロデューサー UID で自身にルーティングされるデータを公開してはなりません。そうした再入はデッドロックを引き起こすためです。

Warnung

IDataConsumer内で ITestSessionLifetimeHandler と組み合わせてを使用する場合は、ITestSessionLifetimeHandler.OnTestSessionFinishingAsync の実行後に受信したデータを無視することが重要です。 OnTestSessionFinishingAsync は、蓄積されたデータを処理し、新しい情報を IMessageBus に送信する最後の機会であるため、この時点以降に使用されるデータは拡張機能では利用できません

拡張機能が集中的な初期化を必要としており、ユーザーが async/await パターンを使用する必要がある場合には、Async extension initialization and cleanup を参照できます。 拡張点の間で状態を共有する必要がある場合は、CompositeExtensionFactory<T> セクションを参照できます。

メッセージ バス ファイル アーティファクト

カスタム IData ペイロードには、UI またはコマンド ラインの自動出力はありません。 このプラットフォームでは、一致するコンシューマーが登録されているデータのみが表示されるため、独自の IData 型を公開しても何も使用しない場合、何も印刷または転送されません。 拡張機能によって生成されるファイルをユーザーとツールに表示できるようにするには、組み込みの ターミナルdotnet テスト コンシューマーが既に認識している組み込みのファイル成果物メッセージのいずれかを発行します。

実行レベルまたはセッション レベルのファイルの場合は、 FileArtifact または SessionFileArtifactを発行します。 どちらも MTP 1.0.0 で導入され、 Microsoft.Testing.Platform.Extensions.Messages 名前空間に含まれています。

  • FileArtifact はスコープ外です。 特定のテスト セッションに関連付けられていないファイルに使用します。
  • SessionFileArtifact は、その SessionUid によって実行またはセッションに限定されます。 カバレッジ結果、レポート、ダンプ、録画した動画など、実行全体に対して生成される成果物に使用してください。

組み込みのターミナルと dotnet test コンシューマーは、両方の種類を選択し、ファイル パスを印刷または転送するため、ファイルはコンソール出力および dotnet test パイプ全体で検出可能になります。 コンシューマーは最終的なプレゼンテーションを制御するため、各ファイルをディスク上に保持し、使用または転送される可能性がある限り使用できるようにします。同じ PublishAsync 呼び出し内で削除しないでください。

成果物オブジェクト自体はプロデューサー ID を持っていません。 メッセージ バスは、IDataProducerdataProducer引数を通じて各コンシューマーに元のIDataConsumer.ConsumeAsyncを提供するため、コンシューマーは成果物からではなく、その引数からファイルを生成したユーザーを学習します。 メッセージ バスは、参照先ファイルの所有権、移動、または削除も行いません。プロデューサーはファイルの有効期間を所有し、コンシューマーはパスの受信、読み取り、または転送のみを行います。

サードパーティの IDataConsumer 登録は、 builder.TestHost ( インプロセス テスト ホスト) でのみパブリックです。 builder.TestHostControllers.AddDataConsumer プロセス外 コンシューマー向けの公開 API はありません。 成果物を表示するファースト パーティのレポート拡張機能では、カスタム拡張機能に依存できない内部プラットフォーム統合が使用されます。 独自の拡張機能からファイルを表示するには、ここで説明する組み込みの成果物メッセージを発行し、組み込みのコンシューマーがファイルを表示できるようにします。

セッション成果物を発行するプロデューサーは、 IDataProducer を実装し、 DataTypesProducedで公開する正確なランタイム メッセージの種類を一覧表示する必要があります。 次の例では、セッションの終了時にカバレッジ レポートを発行します。

internal sealed class CoverageReportProducer(IMessageBus messageBus)
    : IDataProducer, ITestSessionLifetimeHandler
{
    public string Uid => nameof(CoverageReportProducer);
    public string Version => "1.0.0";
    public string DisplayName => "Coverage report producer";
    public string Description => "Publishes the coverage report as a session artifact.";

    // List the exact runtime message types this producer publishes.
    public Type[] DataTypesProduced => new[] { typeof(SessionFileArtifact) };

    public Task<bool> IsEnabledAsync() => Task.FromResult(true);

    public Task OnTestSessionStartingAsync(ITestSessionContext context)
        => Task.CompletedTask;

    public Task OnTestSessionFinishingAsync(ITestSessionContext context)
    {
        var report = new FileInfo("coverage.cobertura.xml");
        return messageBus.PublishAsync(
            this,
            new SessionFileArtifact(
                context.SessionUid,
                report,
                "Code coverage",
                "Cobertura coverage report for the run."));
    }
}

ターミナル、、IDE がそのdotnet testに関連付けて表示されるように、特定のテストにファイルをアタッチするには、スタンドアロンのファイル成果物を発行しないでください。 代わりに、FileArtifactPropertyTestNodeを通じて報告するに、1 つ以上のTestNodeUpdateMessage エントリを追加します。 FileArtifactProperty は MTP 1.7.0 で導入されました。

var testNode = new TestNode
{
    Uid = testUid,
    DisplayName = testDisplayName,
    Properties = new PropertyBag(
        PassedTestNodeStateProperty.CachedInstance,
        new FileArtifactProperty(
            new FileInfo("screenshot.png"),
            "Failure screenshot",
            "Screenshot captured while the test ran.")),
};

await messageBus.PublishAsync(
    dataProducer,
    new TestNodeUpdateMessage(sessionUid, testNode));

Von Bedeutung

TestNodeFileArtifact は古く、MTP 2.0.0 で削除されました。 テスト レベルのファイルを添付するには、FileArtifactPropertyTestNodeを使用します。 詳細については、Microsoft.Testing.Platform (MTP) v1 から v2 への移行を参照してください。

MTP 2.4.0 (2026 年 7 月現在未リリース) では、試験的な kind コンストラクターのオーバーロードと Kind プロパティが FileArtifact および SessionFileArtifact に追加されます ( TPEXP 診断を抑制する必要があります)。 Kind は、成果物 形式 ( microsoft.testing.trxmicrosoft.testing.junitmicrosoft.testing.ctrfmicrosoft.testing.htmlなど) のプロデューサー アサートの逆引き DNS 識別子です。この識別子は、後処理で統合のために同じ形式の成果物をグループ化するために使用できます。 プロデューサーが既知の種類を宣言しない場合は、 null のままにするか、省略します。 現時点では、 Kind はメタデータ コントラクトにすぎません。それ以上の広範なマージ オーケストレーションは想定しないでください。

#pragma warning disable TPEXP // Experimental API.
new SessionFileArtifact(
    context.SessionUid,
    trxFile,
    "TRX report",
    "Test results in TRX format.",
    kind: "microsoft.testing.trx");
#pragma warning restore TPEXP

ITestHostEnvironmentVariableProvider 拡張

ITestHostEnvironmentVariableProvider は、テスト ホストのカスタム環境変数を確立できるアウトプロセス拡張機能です。 この拡張点を使用すると、アーキテクチャ セクションで詳しく説明したように、テスト プラットフォームが適切な環境変数を使用して新しいホストを開始できるようになります。

カスタム ITestHostEnvironmentVariableProvider を登録するには、次の API を利用します。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

ファクトリは IServiceProvider を利用して、テスト プラットフォームで提供されるサービス スイートへのアクセスを取得します。

Von Bedeutung

API は登録された順序で呼び出されるため、登録のシーケンスは重要です。

ITestHostEnvironmentVariableProvider インターフェイスには、以下のメソッドと型が含まれます。

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 は、あらゆるITestHostControllersExtension拡張の基盤となるの一種です。 他のすべての拡張点と同様に、IExtension からも継承されます。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

この API の詳細に注意を払いましょう。

UpdateAsync: この更新 API は IEnvironmentVariables オブジェクトのインスタンスを提供し、そこから SetVariable または RemoveVariable のメソッドを呼び出すことができます。 SetVariable を使用する場合は、次の仕様を必要とする型 EnvironmentVariableのオブジェクトを渡す必要があります。

  • Variable: 環境変数の名前。
  • Value: 環境変数の値。
  • IsSecret: これは、TryGetVariable を使用してログを作成したりアクセスしたりしてはならない機密情報が環境変数に含まれているかどうかを示します。
  • IsLocked: これにより、他の ITestHostEnvironmentVariableProvider 拡張機能がこの値を変更できるかどうかが決まります。

ValidateTestHostEnvironmentVariablesAsync: このメソッドは、登録済みの UpdateAsync インスタンスのすべての ITestHostEnvironmentVariableProvider メソッドが呼び出された後に呼び出されます。 環境変数の正しいセットアップを確認できます。 IReadOnlyEnvironmentVariables を実装するオブジェクトを取得すると、TryGetVariable オブジェクト型を使用して特定の環境変数情報をフェッチする OwnedEnvironmentVariable メソッドが提供されます。 検証後、エラーの理由が含まれている ValidationResult を返します。

テスト プラットフォームは、既定で、SystemEnvironmentVariableProvider を実装して登録します。 このプロバイダーは、現在のすべての環境変数を読み込みます。 それは、最初に登録されたプロバイダーとして最初に実行され、他のすべての ITestHostEnvironmentVariableProvider ユーザー拡張機能に対して既定の環境変数へのアクセスが付与されます。

拡張機能が集中的な初期化を必要としており、ユーザーが async/await パターンを使用する必要がある場合には、Async extension initialization and cleanup を参照できます。 拡張点の間で状態を共有する必要がある場合は、CompositeExtensionFactory<T> セクションを参照できます。

ITestHostProcessLifetimeHandler 拡張

ITestHostProcessLifetimeHandlerアウトプロセス拡張機能で、外部の観点からテスト ホスト プロセスを観察できます。 これにより、テスト対象のコードによって誘発される可能性のあるクラッシュやハングの影響を、拡張機能が受けないようにします。 この拡張点を利用すると、アーキテクチャ セクションで詳しく説明したように、テスト プラットフォームは新しいホストを開始するように求められます。

カスタム ITestHostProcessLifetimeHandler を登録するには、次の API を利用します。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

ファクトリは IServiceProvider を利用して、テスト プラットフォームで提供されるサービス スイートへのアクセスを取得します。

Von Bedeutung

API は登録された順序で呼び出されるため、登録のシーケンスは重要です。

ITestHostProcessLifetimeHandler インターフェイスには、以下のメソッドが含まれます。

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 は、あらゆるITestHostControllersExtension拡張の基盤となるの一種です。 他のすべての拡張点と同様に、IExtension からも継承されます。 そのため、他の拡張機能と同様に、IExtension.IsEnabledAsync API を使用してそれを有効または無効にすることができます。

この API の詳細に注意を払いましょう。

BeforeTestHostProcessStartAsync: このメソッドは、テスト プラットフォームがテスト ホストを開始する前に呼び出されます。

OnTestHostProcessStartedAsync: このメソッドは、テスト ホストの開始直後に呼び出されます。 このメソッドは、テスト ホスト プロセスの結果に関する重要な詳細を提供する ITestHostProcessInformation インターフェイスを実装するオブジェクトを提供します。

Von Bedeutung

このメソッドを呼び出しても、テスト ホストの実行は停止しません。 一時停止する必要がある場合は、インプロセス拡張機能を ITestHostApplicationLifetime として登録し、アウトプロセス拡張機能と同期する必要があります。

OnTestHostProcessExitedAsync: このメソッドは、テスト スイートの実行が完了したときに呼び出されます。 このメソッドは、テスト ホスト プロセスの結果に関する重要な詳細を伝える ITestHostProcessInformation インターフェイスに準拠したオブジェクトを提供します。

ITestHostProcessInformation インターフェイスでは、次の詳細が提供されます。

  • PID: テスト ホストのプロセス ID。
  • ExitCode: プロセスの終了コード。 この値は、OnTestHostProcessExitedAsync メソッド内でのみ使用できます。 OnTestHostProcessStartedAsync メソッド内でアクセスしようとすると、例外が発生します。
  • HasExitedGracefully: テスト ホストがクラッシュしたかどうかを示すブール値。 true の場合は、テスト ホストが正常に終了しなかったことを示します。

TestingPlatformBuilderHook で拡張機能を自動登録する

上記のすべての拡張セクションには、 手動 登録呼び出し (たとえば、 builder.TestHost.AddDataConsumer(...)) が表示されます。 コンシューマーに Main 方法の編集を求めることは、オンボード エクスペリエンスが低下します。 Microsoft.Testing.Platform.MSBuild パッケージは、自動生成されたエントリ ポイントから実行される SelfRegisteredExtensions.AddSelfRegisteredExtensions(builder, args) メソッドを生成することによってこれを解決します。 生成されたメソッドに拡張機能をプラグインするには、NuGet パッケージに次の 2 つの成果物を発送します。

  • 拡張機能を登録するTestingPlatformBuilderHook メソッドを持つパブリック静的AddExtensions クラス。
  • そのクラスを指す <TestingPlatformBuilderHook> 項目を宣言する MSBuild props ファイル。

コンシューマーがパッケージをインストールすると、MSBuild 統合によって項目が取得され、フックへの呼び出しが生成され、コンシューマー側でコードの変更なしで拡張機能が登録されます。

自動登録は、コンシューマーがプロジェクトに Microsoft.Testing.Platform.MSBuild (MSTest、NUnit、xUnit ランナーによって推移的に含まれる) があり、<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint> を設定してオプトアウトしていない場合にのみ機能します。 自動生成されたエントリ ポイントを無効にするコンシューマーは、 Main メソッドから手動登録 API を呼び出す必要があります。

フック クラスを作成する

拡張機能アセンブリに、ユーザーが通常手動で呼び出すのと同じ登録処理を実行する public static class TestingPlatformBuilderHook メソッドを含む AddExtensions(ITestApplicationBuilder, string[]) を追加します。

using Microsoft.Testing.Platform.Builder;

namespace Contoso.MyExtension;

public static class TestingPlatformBuilderHook
{
    public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] arguments)
        => testApplicationBuilder.AddMyExtension();
}

クラス名は TestingPlatformBuilderHook である必要はありません。MSBuild 項目は完全修飾型名でそれを参照するためです。ただし、その名前を使うと、Microsoft.Testing.Extensions.RetryMicrosoft.Testing.Extensions.HotReload などの組み込み拡張機能とのコードの一貫性を保てます。

メソッドは次の手順を実行する必要があります。

  • public staticになってください。
  • Microsoft.Testing.Platform.Builder.ITestApplicationBuilder 型の最初のパラメーターを指定します。
  • string[]型の 2 つ目のパラメーター (テスト ホストに渡されるコマンド ライン引数) があります。 拡張機能に必要がない場合は無視できます。
  • voidを返します。

MSBuild 項目を宣言する

NuGet パッケージ内の buildMultiTargeting/<PackageId>.props の下に props ファイルを発送します。 フック クラスで MSBuild タスクを指す <TestingPlatformBuilderHook> 項目を宣言します。

<Project>
  <ItemGroup>
    <TestingPlatformBuilderHook Include="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
      <DisplayName>Contoso.MyExtension</DisplayName>
      <TypeFullName>Contoso.MyExtension.TestingPlatformBuilderHook</TypeFullName>
    </TestingPlatformBuilderHook>
  </ItemGroup>
</Project>

メタデータは次のとおりです。

  • Include: フックを一意に識別するGUID。 「Include GUID はランダム識別子です」を参照してください。
  • DisplayName: エントリ ポイントの生成時に MSBuild 診断メッセージに表示されるフレンドリ名。 パッケージまたは拡張機能の名前を使用します。
  • TypeFullName: 前に作成した TestingPlatformBuilderHook クラスの完全修飾名。 MSBuild タスクはこれを使用して、生成されたエントリ ポイントに global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args); を出力します。

Include GUID はランダム識別子です

Include属性の GUID は、拡張機能のと同じIExtension.Uid。 これは、MSBuild タスクが NuGet 参照全体でフックの重複を排除し、(一部のよく知られたケースでは)それらを順序付けするために使用する登録識別子です。

新しい拡張機能を作成するときは、まったく新しい GUID を生成し、props ファイルにハードコーディングします。 1 つを生成するいくつかの方法:

  • パワーシェル: [guid]::NewGuid()
  • Visual Studio: Tools>CREate GUID
  • Linux と macOS の場合: uuidgen

Von Bedeutung

Microsoft 製かサード パーティ製かを問わず、別の拡張機能の props ファイルから GUID を決してコピーしないでください。 同じ Include 値を共有する 2 つの拡張機能は重複として扱われます。1 つのフックのみが呼び出されるため、拡張機能はサイレントモードで登録に失敗します。

GUID は一度リリースしたら、永久に不変のものとして扱ってください。 後のリリースで変更しても問題ありませんが、将来のパッケージ バージョンで 古い 値を別のフックに再利用すると、アップグレード中に依存関係グラフに両方のバージョンがあるコンシューマーが混乱する可能性があります。

フックが接続されていることを確認する

Microsoft.Testing.Platform.MSBuild を使用するテスト プロジェクトにパッケージをインストールした後、プロジェクトをビルドし、生成された SelfRegisteredExtensions.g.cs ファイルを obj/<Configuration>/<TargetFramework>/ の下で検査します。 作成したフックへの呼び出しを確認できるはずです。たとえば、次のように表示されます。

public static void AddSelfRegisteredExtensions(this global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder, string[] args)
{
    global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args);
}

呼び出しがない場合は、props ファイルがbuildMultiTargeting/内の (build/ではなく) .nupkg下にパッケージ化されていること、DisplayNameおよびTypeFullNameメタデータが存在していること、およびコンシューマーが<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint>を設定していないことを再確認します。

拡張機能の実行順序

テスト プラットフォームは、テスト フレームワークと、インプロセスまたはアウトプロセスを操作できる任意の数の拡張機能で構成されます。 このドキュメントでは、機能が呼び出されるタイミングの想定について明確にするために、すべての潜在的な拡張ポイントの呼び出しのシーケンスについて説明します。

  1. ITestHostEnvironmentVariableProvider.UpdateAsync : アウトプロセス
  2. ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync : プロセス外
  3. ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync : プロセス外実行
  4. ホストプロセスのテスト開始
  5. ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync : アウトプロセス、このイベントは競合条件によりインプロセス拡張機能のアクションが絡み合う可能性があります。
  6. ITestHostApplicationLifetime.BeforeRunAsync: プロセス内
  7. ITestSessionLifetimeHandler.OnTestSessionStartingAsync: インプロセス
  8. ITestFramework.CreateTestSessionAsync: プロセス内
  9. ITestFramework.ExecuteRequestAsync: インプロセス、このメソッドは 1 回以上呼び出すことができます。 この時点で、テスト フレームワークは IDataConsumer で利用できる情報を IMessageBus に送信します。
  10. ITestFramework.CloseTestSessionAsync: インプロセス
  11. ITestSessionLifetimeHandler.OnTestSessionFinishingAsync: プロセス内
  12. ITestHostApplicationLifetime.AfterRunAsync: インプロセス
  13. インプロセス クリーンアップでは、すべての拡張点で Dispose と IAsyncCleanableExtension を呼び出す必要があります。
  14. ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync : プロセス外
  15. アウトプロセス クリーンアップでは、すべての拡張点で Dispose と IAsyncCleanableExtension を呼び出す必要があります。

拡張機能ヘルパー

テスト プラットフォームには、拡張機能の実装を簡素化するためのヘルパー クラスおよびインターフェースのセットが用意されています。 これらのヘルパーは、開発プロセスを合理化し、拡張機能がプラットフォームの標準に準拠するように設計されています。

拡張機能の非同期初期化とクリーンアップ

ファクトリを介したテスト フレームワークと拡張機能の作成は、同期コンストラクターを使用する標準的な.NET オブジェクト作成メカニズムに準拠しています。 拡張機能が集中的な初期化 (ファイル システムまたはネットワークへのアクセスなど) を必要とする場合、コンストラクターは ではなく void を返すため、コンストラクタで Task パターンを使用することはできません。

そのため、テスト プラットフォームには、単純なインターフェイス経由で async/await パターンを使用して拡張機能を初期化するメソッドが用意されています。 対称性のために、拡張機能がシームレスに実装できるクリーンアップ用の非同期インターフェイスも提供されます。

public interface IAsyncInitializableExtension
{
    Task InitializeAsync();
}

public interface IAsyncCleanableExtension
{
    Task CleanupAsync();
}

IAsyncInitializableExtension.InitializeAsync: このメソッドは、作成ファクトリの後で確実に呼び出されます。

IAsyncCleanableExtension.CleanupAsync: このメソッドは、少なくとも1回、テストセッションの終了時に、既定の または DisposeAsync の前にDisposeに確実に呼び出されます。

Von Bedeutung

標準 Dispose メソッドと同様に、 CleanupAsync を複数回呼び出すことができます。 オブジェクトのCleanupAsyncメソッドが 2 回以上呼び出された場合、オブジェクトは、最初の呼び出しの後すべての呼び出しを無視する必要があります。 オブジェクトは、CleanupAsync メソッドが複数回呼び出される場合、例外をスローしてはなりません。

既定では、テスト プラットフォームは DisposeAsync (使用可能な場合) または Dispose (実装されている場合) を呼び出します。 テスト プラットフォームでは両方の Dispose メソッドが呼び出されるのではなく、実装されている場合には非同期メソッドが優先されることに注意することが重要です。

CompositeExtensionFactory<T>

拡張セクションで説明したように、このテスト プラットフォームでは、インプロセスとアウトプロセスの両方にカスタム拡張機能を組み込むためのインターフェースを実装できます。

各インターフェイスは特定の機能に対応し、.NET設計に従って、このインターフェイスを特定のオブジェクトに実装します。 拡張機能自体は、対応するセクションで詳しく説明されているように、AddXXX の特定の登録 API TestHost または TestHostControllerITestApplicationBuilder オブジェクトを使用して登録できます。

ただし、2 つの拡張機能間で状態を共有する必要がある場合、異なるインターフェースを実装する異なるオブジェクトを実装して登録できるため、共有は困難なタスクになります。 支援がなければ、一方の拡張機能をもう一方の拡張機能に渡して情報を共有する方法が必要になるため、設計は複雑になります。

そのため、テスト プラットフォームでは、同じ型を使用して複数の拡張点を実装する高度なメソッドを用意して、データ共有を簡単なタスクにしています。 必要なのは、1 回のインターフェイス実装で使用するものと同じ API を使用して登録できる、CompositeExtensionFactory<T> を利用することです。

ITestSessionLifetimeHandlerIDataConsumer の両方を実装する型を例に考えてみましょう。 これは一般的なシナリオです。なぜなら、ユーザーはテスト フレームワークから情報を収集し、テスト セッションが終了したら、IMessageBus 内の ITestSessionLifetimeHandler.OnTestSessionFinishingAsync を使用してアーティファクトを送信することが多いからです。

通常、行うべきことはインターフェイスの実装です。

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

型に対して CompositeExtensionFactory<CustomExtension> を作成したら、IDataConsumer のオーバーロードを提供する ITestSessionLifetimeHandler API と CompositeExtensionFactory<T> API の両方に登録できます。

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

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

ファクトリ コンストラクターは、IServiceProvider を使用して、テスト プラットフォームによって提供されるサービスにアクセスします。

テスト プラットフォームは、複合拡張機能のライフサイクルを管理する役割を担います。

インプロセス拡張機能とアウトプロセス拡張機能の両方に対するテスト プラットフォームのサポートにより、拡張点を任意に組み合わせることができないことに注意してください。 拡張機能の作成と利用はホストの種類に依存します。つまり、インプロセス (TestHost) とアウトプロセス (TestHostController) の拡張機能のみグループ化できます。

次の組み合わせが可能です。

  • ITestApplicationBuilder.TestHost の場合は、IDataConsumerITestSessionLifetimeHandler を組み合わせることができます。
  • ITestApplicationBuilder.TestHostControllers の場合は、ITestHostEnvironmentVariableProviderITestHostProcessLifetimeHandler を組み合わせることができます。

IDataConsumerインプロセス 拡張機能であるため、カスタム コンシューマーは builder.TestHost ( CompositeExtensionFactory<T> 経由を含む) によってのみ登録されます。 IDataConsumerbuilder.TestHostControllersを登録するパブリック API はありません。