共用方式為


為 Microsoft.Testing.Platform 開發擴充套件

本文涵蓋了 Microsoft.Testing.Platform 在測試框架本身之外的可擴充性點。 關於測試框架的建立,請參見 「建構測試框架」。

關於完整的擴充點摘要及進程/出程概念,請參見「建立自訂擴充」。

擴展性點

測試平台提供了額外的擴充點,讓您可以自訂平台和測試框架的行為。 這些擴充點是可選的,可用於增強測試體驗。

擴充功能

備註

當您擴展此 API 時,自訂擴展將同時存在於測試宿主進程的內部和外部。

如架構區段所討論的,初始步驟涉及建立 以註冊測試框架和擴充功能。

var builder = await TestApplication.CreateBuilderAsync(args);

方法接受名為 的字串陣列 ()。 這些參數可用於將命令列選項傳遞給測試平台的所有元件 (包括內建元件、測試框架和擴充功能),從而允許自訂其行為。

通常,傳遞的參數是在標準 方法中接收的參數。 但是,如果主機環境不同,則可以提供任意參數清單。

參數前面必須加上雙破折號 。 例如: 。

如果測試框架或擴充點等元件希望提供自訂命令列選項,則可以透過實作 介面來實作。 然後可以透過 屬性的註冊處理站向 註冊此實作,如下所示:

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

在提供的範例中, 是 介面的實作,該介面包含以下成員和資料類型:

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

如所觀察到的, 擴充了 介面。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

的執行順序為:

表示「ICommandLineOptionsProvider」介面的執行順序的圖表。

讓我們檢查一下 api 及其含義:

:此方法用於檢索元件提供的所有選項。 每個 都需要指定以下屬性:

:這是選項名稱的呈現方式,不包含破折號。 例如,篩選器將由使用者作為 使用。

:這是選項的描述。 當使用者將 作為參數傳遞給應用程式建立器時,其就會顯示。

:選項的元數是指定該選項或指令時可以傳遞的值的數量。 目前可用的參數數量有:

  • :表示參數元數為零。
  • :表示參數元數為零或一。
  • :表示參數元數為零或以上。
  • :表示參數元數為一或以上。
  • :表示參數元數恰好為一。

有關範例,請參閱 System.CommandLine 元數表。

:此屬性表示該選項可供使用,但在 叫用時不會顯示在描述中。

:此方法用於驗證使用者提供的參數。

例如,如果您有一個名為 的參數,該參數表示我們的自訂測試框架的並行程度,則使用者可能會輸入 。 在這種情況下,值 將無效,因為預計其並行度為 或更高。 透過使用 ,您可以執行預先驗證並在必要時傳回錯誤訊息。

上述範例的可能實作可能是:

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

:此方法作為最後一個方法呼叫,允許進行全域一致性檢查。

例如,假設我們的測試框架能夠產生測試結果報告,並將其儲存到檔案中。 使用 選項存取此功能,並使用 指定檔案名稱。 在這種情況下,如果使用者僅提供 選項而不指定檔案名稱,則驗證會失敗,因為沒有檔案名稱就無法產生報告。 上述範例的可能實作可能是:

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

請注意, 方法提供了 服務,用於擷取平台本身解析的參數資訊。

擴充功能

是處理序內擴充功能,可以在測試工作階段之前和之後執行程式碼。

若要註冊自訂 ,請使用下列 API:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

工廠利用 IServiceProvider 來存取測試平台提供的服務套件。

這很重要

註冊順序很重要,因為 API 是按照註冊順序呼叫的。

介面包含以下方法:

public interface ITestSessionLifetimeHandler : ITestHostExtension
{
    Task OnTestSessionStartingAsync(
        SessionUid sessionUid,
        CancellationToken cancellationToken);

    Task OnTestSessionFinishingAsync(
        SessionUid sessionUid,
        CancellationToken cancellationToken);
}

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

public interface ITestHostExtension : IExtension
{
}

是 的一種類型,當作所有測試主機擴充功能的基礎。 與所有其他擴充點一樣,它也繼承自 IExtension。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

請考慮此 API 的以下詳細資訊:

:此方法在測試工作階段開始之前呼叫並接收 物件,該物件為目前測試工作階段提供不透明識別碼。

:此方法在測試工作階段完成後調用,確保測試框架已完成所有測試的執行,並向平台報告所有相關資料。 通常,在此方法中,擴充功能會使用 將自訂資產或資料傳輸到共用平台匯流排。 此方法還可以向任何自訂處理序外擴充功能發出測試工作階段已結束的訊號。

最後,這兩個 API 都會使用 ,擴充功能預期應遵循該項參數。

如果您的擴充功能需要密集初始化並且需要使用 async/await 模式,您可以參考 。 如果需要在擴充點之間共用狀態,可以參考 區段。

擴充功能

是處理序內擴充功能,可以在所有事情之前執行程式碼,就像可以存取測試主機的假設主程式的第一行一樣。

若要註冊自訂 ,請使用以下 API:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestApplicationLifecycleCallbacks(
    static serviceProvider
    => new CustomTestApplicationLifecycleCallbacks());

工廠利用 IServiceProvider 來存取測試平台提供的服務套件。

這很重要

註冊順序很重要,因為 API 是按照註冊順序呼叫的。

介面包含以下方法:

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

    Task AfterRunAsync(
        int exitCode,
        CancellationToken cancellation);
}

public interface ITestHostExtension : IExtension
{
}

是 的一種類型,當作所有測試主機擴充功能的基礎。 與所有其他擴充點一樣,它也繼承自 IExtension。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

:此方法可作為測試主機的初始聯繫點,並且是處理序內擴充功能執行功能的第一個機會。 如果某個功能設計為跨兩種環境執行,則它通常用於與任何相應的處理序外擴充功能建立連接。

例如,內建的懸掛傾印功能由處理序內和處理序外擴充功能組成,而此方法用於與擴充功能的處理序外元件交換資訊。

:此方法是結束 之前的最後一個呼叫,它提供了 。 它應該僅用於清理工作,並通知測試主機即將終止的任何相應的處理序外擴充功能。

最後,這兩個 API 都會使用 ,擴充功能預期應遵循該項參數。

擴充功能

是一個處理序內擴充功能,能夠訂閱和接收由及其擴充功能推送到 IMessageBus 的 資訊。

這個擴充點至關重要,因為它使開發人員能夠收集和處理測試工作階段期間產生的所有資訊。

若要註冊自訂 ,請使用以下 API:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

工廠利用 IServiceProvider 來存取測試平台提供的服務套件。

這很重要

註冊順序很重要,因為 API 是按照註冊順序呼叫的。

介面包含以下方法:

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

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

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

是 的一種類型,當作所有測試主機擴充功能的基礎。 與所有其他擴充點一樣,它也繼承自 IExtension。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

:此屬性將回傳此擴充功能預期使用的 清單。 它對應於 。 值得注意的是, 可以沒有任何問題地訂閱源自不同 執行個體的多種類型。

:每當目前取用者訂閱的類型的資料推送到 時,就會觸發該方法。 它會接收 ,提供有關資料負載的生產者以及 負載本身的詳細資訊。 正如您所看到的, 是一個通用預留位置介面,其中包含一般資訊資料。 推送不同類型 的能力代表取用者需要打開類型本身,以將其切為正確的類型並存取特定資訊。

需要 elaborates 由產生的 的使用者範例實作可以是:

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 採用 ,擴充功能應該遵循它。

這很重要

在 方法內直接處理負載是至關重要的。 IMessageBus 可以管理同步和非同步處理,並與測試框架協調執行。 雖然取用過程是完全非同步的,並且在撰寫本文時不會封鎖 IMessageBus.Push,但這是一個實作詳細資訊,將來可能會因未來的需求而改變。 但是,平台確保該方法始終被呼叫一次,從而消除了對複雜同步的需求,並管理消費者的可擴展性。

警告

當在中,配合使用 及 ITestHostProcessLifetimeHandler 時,至關重要的是忽略執行 ITestSessionLifetimeHandler.OnTestSessionFinishingAsync 後收到的任何資料。 是處理累積資料並將新資訊傳輸到 IMessageBus 的最後機會,因此,超出此點消耗的任何資料將無法由擴充功能使用。

如果您的擴充功能需要密集初始化並且需要使用 async/await 模式,您可以參考 。 如果需要在擴充點之間共用狀態,可以參考 區段。

擴充功能

是處理序外擴充功能,使您能夠為測試主機建立自訂環境變數。 利用此擴充點可確保測試平台將啟動具有適當環境變數的新主機,如架構區段中詳述。

若要註冊自訂 ,請使用下列 API:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

工廠利用 IServiceProvider 來存取測試平台提供的服務套件。

這很重要

註冊順序很重要,因為 API 是按照註冊順序呼叫的。

介面包含以下方法和類型:

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

是 的一種,可作為所有測試主機控制器擴充功能的基礎。 與所有其他擴充點一樣,它也繼承自 IExtension。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

考慮此 API 的詳細資訊:

:此更新 API 提供了 物件的執行個體,您可以從中呼叫 或 方法。 使用 時,必須傳遞類型為 的物件,該物件需要以下規範:

  • :環境變數的名稱。
  • :環境變數的值。
  • :這指示環境變數是否包含不應記錄,或透過 存取的敏感資訊。
  • :決定其他 擴充功能是否可以修改該值。

:該方法是在所有已註冊的 執行個體之 方法被呼叫後才被呼叫。 它可讓您驗證環境變數的設定是否正確。 它需要實作 的物件,該物件提供了使用 物件類型獲取特定環境變數資訊的 方法。 驗證後,您將傳回包含任何失敗原因的 。

備註

測試平台預設實作並註冊 。 此提供者會載入所有目前環境變數。 作為第一個註冊的提供者,它先行執行,為所有其他 使用者擴充功能提供存取預設環境變數的權限。

如果您的擴充功能需要密集初始化並且需要使用 async/await 模式,您可以參考 。 如果需要在擴充點之間共用狀態,可以參考 區段。

擴充功能

是處理序外擴充功能,可讓您從外部角度觀察測試主機處理序。 這可確保您的擴充功能不受測試程式碼可能造成的潛在當機或停止回應的影響。 使用此擴充點將提示測試平台啟動新主機,如架構區段詳述。

若要註冊自訂 ,請使用下列 API:

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

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

工廠利用 IServiceProvider 來存取測試平台提供的服務套件。

這很重要

註冊順序很重要,因為 API 是按照註冊順序呼叫的。

介面包含以下方法:

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

是 的一種,可作為所有測試主機控制器擴充功能的基礎。 與所有其他擴充點一樣,它也繼承自 IExtension。 因此,與任何其他擴充功能一樣,您可以選擇使用 API 啟用或停用它。

請考慮此 API 的以下詳細資訊:

:此方法在測試平台啟動測試主機之前呼叫。

:測試主機啟動後立即呼叫此方法。 此方法提供一個實作 介面的物件,該介面會提供有關測試主機處理序結果的關鍵詳細資訊。

這很重要

呼叫此方法不會停止測試主機的執行。 如果您需要暫停它,您應該註冊一個處理序內擴充功能,例如 ,並將其與處理序外擴充功能同步。

:當測試套件執行完成時呼叫此方法。 此方法提供了一個符合 介面的物件,該物件傳達有關測試主機處理序結果的重要細節。

介面提供下列詳細資訊:

  • :測試主機的處理序識別碼。
  • :處理序的結束程式碼。 該值僅在 方法內可用。 嘗試在 方法內存取它將會導致例外狀況。
  • :一個布林值,指示測試主機是否當機。 如果為 True,則表示測試主機沒有正常地退出。

擴充功能執行順序

測試平台由測試框架和任意數量的擴充功能組成,這些擴充功能可以在處理序內或處理序外執行。 本文件概述了對所有潛在擴充點的呼叫順序,以清楚地說明預計何時叫用某個功能:

  1. ITestHostEnvironmentVariableProvider.UpdateAsync:處理序外
  2. ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync:處理序外
  3. ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync:處理序外
  4. 測試主機程序啟動
  5. ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync:在處理序外,此事件可以根據競爭條件將處理序內擴充功能的操作交織在一起。
  6. ITestApplicationLifecycleCallbacks.BeforeRunAsync:處理序內
  7. ITestSessionLifetimeHandler.OnTestSessionStartingAsync:處理中
  8. ITestFramework.CreateTestSessionAsync:進程內
  9. ITestFramework.ExecuteRequestAsync:在處理序內,可以呼叫此方法一次或多次。 此時,測試框架將向 IDataConsumer 可以使用的 IMessageBus 傳輸訊息。
  10. ITestFramework.CloseTestSessionAsync:處於處理中
  11. ITestSessionLifetimeHandler.OnTestSessionFinishingAsync:進程內
  12. ITestApplicationLifecycleCallbacks.AfterRunAsync:在處理序內
  13. 處理序內清理涉及在所有擴充點上呼叫 dispose 和 IAsyncCleanableExtension。
  14. ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync:處理序外
  15. 處理序外清理涉及在所有擴充點上呼叫 dispose 和 IAsyncCleanableExtension。

擴充功能輔助工具

測試平台提供了一組輔助類別和介面來簡化擴充功能的實作。 這些輔助工具的設計目的是簡化開發流程,並確保擴充功能遵循平台的標準。

擴充功能的非同步初始化和清理

透過工廠建立測試框架及擴充功能,遵循標準的 .NET 物件建立機制,該機制使用同步建構器。 如果擴充功能需要密集的初始化 (例如存取檔案系統或網路),則它不能在建構函式中使用 async/await 模式,因為建構函式會傳回無效,而不是 。

因此,測試平台提供了一種透過簡單的介面使用非同步/等待模式初始化擴充功能的方法。 為了保持對稱,它還提供了一個非同步介面以進行清理,讓擴充功能能夠順利地加以實作。

public interface IAsyncInitializableExtension
{
    Task InitializeAsync();
}

public interface IAsyncCleanableExtension
{
    Task CleanupAsync();
}

:這個方法保證會在創建工廠之後叫用。

:此方法在測試工作階段終止期間,確保至少被執行一次,並在預設或之前進行。

這很重要

與標準 方法類似, 可以多次叫用。 如果多次呼叫物件的 方法,則該物件必須忽略第一次呼叫之後的所有呼叫。 如果多次呼叫該物件的 方法,則該物件不得擲回例外狀況。

備註

預設情況下,測試平台會呼叫 (如果它是可用的),否則會呼叫 (如果它已實作)。 需要注意的是,測試平台不會同時呼叫兩種釋放方法,而是會優先使用非同步的方法(如果已經實作)。

CompositeExtensionFactoryT

如擴充功能區段所述,測試平台使您能夠實作介面以在處理序內和處理序外合併自訂擴充功能。

每個介面針對特定功能,根據 .NET 設計,你會在特定物件中實作這個介面。 您可以使用 中的特定註冊 API 或 中的 物件來註冊擴充功能本身,如相應區段中詳述。

但是,如果您需要在兩個擴充功能之間共用狀態,由於您可以實作和註冊不同介面所需的不同物件,這使得共用成為一項具有挑戰性的任務。 如果沒有任何幫助,您將需要一種將一個擴充功能傳遞給另一個擴充功能以共用資訊的方法,這使得設計變得複雜。

因此,測試平台提供了一種複雜的方法來使用相同類型實作多個擴充點,使資料共用成為一項簡單的工作。 您需要做的就是使用 ,然後可以使用與單一介面實現相同的 API 進行註冊。

例如,考慮一個同時實作 和 的類型。 這是一種常見的情境,因為您通常會想要從測試框架收集資訊,然後在測試工作階段結束時,您將使用 中的 來分派您的成品。

您應該做的是正常實作介面:

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

一旦為您的類型建立了 ,您就可以使用 和 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) 擴充功能分組在一起。

可能有以下組合:

  • 對於 ,您可以組合 和 。
  • 對於 ,您可以組合 和 。