Microsoft.Testing.Platform Services

测试平台为测试框架和扩展点提供有价值的服务。 这些服务满足常见需求,例如访问配置、分析和检索命令行参数、获取日志记录工厂以及访问日志记录系统等。 IServiceProvider 为测试平台实现 服务定位器模式

IServiceProvider 直接从基类库派生而来。

namespace System
{
    public interface IServiceProvider
    {
        object? GetService(Type serviceType);
    }
}

测试平台提供了方便的扩展方法来访问已知服务对象。 所有这些方法都存储在命名空间中的静态类中 Microsoft.Testing.Platform.Services

public static class ServiceProviderExtensions
{
    public static TService GetRequiredService<TService>(
        this IServiceProvider provider)

    public static TService? GetService<TService>(
        this IServiceProvider provider)

    public static IMessageBus GetMessageBus(
        this IServiceProvider serviceProvider)

    public static IConfiguration GetConfiguration(
        this IServiceProvider serviceProvider)

    public static ICommandLineOptions GetCommandLineOptions(
        this IServiceProvider serviceProvider)

    public static ILoggerFactory GetLoggerFactory(
        this IServiceProvider serviceProvider)

    public static IOutputDevice GetOutputDevice(
        this IServiceProvider serviceProvider)

    // ... and more
}

扩展点暴露的大多数注册工厂提供对IServiceProvider的访问。例如,当注册测试框架时,IServiceProvider会作为参数传递给工厂方法。

ITestApplicationBuilder RegisterTestFramework(
    Func<IServiceProvider, ITestFrameworkCapabilities> capabilitiesFactory,
    Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework> adapterFactory);

在前面的代码中,capabilitiesFactoryadapterFactory 都提供 IServiceProvider 作为参数。

IConfiguration 服务

IConfiguration 接口可以使用 IServiceProvider 来检索,并且会提供对测试框架和任何扩展点的配置设置的访问。 默认情况下,从以下项中加载这些配置:

  • 环境变量
  • 位于入口点程序集附近名为 [assemblyName].testingplatformconfig.json 的 JSON 文件。

优先顺序保持,这意味着如果在环境变量中找到配置,则不会处理 JSON 文件。

接口由一组简单的键值对字符串构成:

public interface IConfiguration
{
    string? this[string key] { get; }
}

JSON 配置文件

JSON 文件遵循分层结构。 若要访问子属性,需要使用 : 分隔符。 例如,请考虑一种潜在的测试框架配置:

{
  "CustomTestingFramework": {
    "DisableParallelism": true
  }
}

代码片段如下所示:

IServiceProvider serviceProvider = null; // Get the service provider...

var configuration = serviceProvider.GetConfiguration();

if (bool.TryParse(configuration["CustomTestingFramework:DisableParallelism"], out var value) && value is true)
{
    // ...
}

对于数组,例如:

{
  "CustomTestingFramework": {
    "Engine": [
      "ThreadPool",
      "CustomThread"
    ]
  }
}

访问第一个元素(“ThreadPool”)的语法为:

IServiceProvider serviceProvider = null; // Get the service provider...

var configuration = serviceProvider.GetConfiguration();

var fistElement = configuration["CustomTestingFramework:Engine:0"];

环境变量

: 分隔符不适用于所有平台上的环境变量分层键。 __(双下划线):

  • 所有平台都支持。 例如,: 分隔符不被 Bash 支持,但 __ 是被支持的分隔符。
  • 自动替换为 :

例如,可以按如下方式设置环境变量(此示例适用于 Windows):

setx CustomTestingFramework__DisableParallelism=True

创建ITestApplicationBuilder时,你可以选择不使用配置源的环境变量。

var options = new TestApplicationOptions();

options.Configuration.ConfigurationSources.RegisterEnvironmentVariablesConfigurationSource = false;

var builder = await TestApplication.CreateBuilderAsync(args, options);

ICommandLineOptions 服务

该服务 ICommandLineOptions 用于获取有关平台已分析的命令行选项的详细信息。 可用的 API 包括:

public interface ICommandLineOptions
{
    bool IsOptionSet(string optionName);

    bool TryGetOptionArgumentList(
        string optionName, 
        out string[]? arguments);
}

ICommandLineOptions可以通过某些 API(例如 ICommandLineOptionsProvider)获取,也可以通过扩展方法serviceProvider.GetCommandLineOptions() 检索它的实例。

ICommandLineOptions.IsOptionSet(string optionName):此方法允许验证是否已指定特定选项。 指定 optionName时,省略 -- 前缀。 例如,如果用户输入 --myOption,则只需传递 myOption

ICommandLineOptions.TryGetOptionArgumentList(string optionName, out string[]? arguments):此方法使你能够检查是否已设置特定选项,如果是,则检索相应的值或值(如果参数个数是多个)。 与前面的情况类似,optionName 应当提供而不带有 -- 前缀。

ILoggerFactory 服务

测试平台附带生成日志文件的集成日志记录系统。 可以通过运行 --help 命令来查看日志记录选项。 可从中选择的选项包括:

--diagnostic                             Enable the diagnostic logging. The default log level is 'Trace'. The file will be written in the output directory with the name log_[MMddHHssfff].diag
--diagnostic-filelogger-synchronouswrite Force the built-in file logger to write the log synchronously. Useful for scenario where you don't want to lose any log (i.e. in case of crash). Note that this is slowing down the test execution.
--diagnostic-output-directory            Output directory of the diagnostic logging, if not specified the file will be generated inside the default 'TestResults' directory.
--diagnostic-output-fileprefix           Prefix for the log file name that will replace '[log]_.'
--diagnostic-verbosity                   Define the level of the verbosity for the --diagnostic. The available values are 'Trace', 'Debug', 'Information', 'Warning', 'Error', and 'Critical'

从编码的角度来说,要记录信息,需要从ILoggerFactory中获取IServiceProvider。 API ILoggerFactory 如下所示:

public interface ILoggerFactory
{
    ILogger CreateLogger(string categoryName);
}

public static class LoggerFactoryExtensions
{
    public static ILogger<TCategoryName> CreateLogger<TCategoryName>(this ILoggerFactory factory);
}

记录器工厂允许你使用 ILogger API 创建CreateLogger对象。 还有一个方便的 API 接受泛型参数,该参数将用作类别名称。

public interface ILogger
{
    Task LogAsync<TState>(
        LogLevel logLevel, 
        TState state, 
        Exception? exception, 
        Func<TState, Exception?, string> formatter);

    void Log<TState>(
        LogLevel logLevel,
        TState state, 
        Exception? exception, 
        Func<TState, Exception?, string> formatter);

    bool IsEnabled(LogLevel logLevel);
}

public interface ILogger<out TCategoryName> : ILogger
{
}

public static class LoggingExtensions
{
    public static Task LogCriticalAsync(this ILogger logger, string message);
    public static Task LogDebugAsync(this ILogger logger, string message);
    public static Task LogErrorAsync(this ILogger logger, Exception ex);
    public static Task LogErrorAsync(this ILogger logger, string message, Exception ex);
    public static Task LogErrorAsync(this ILogger logger, string message);
    public static Task LogInformationAsync(this ILogger logger, string message);
    public static Task LogTraceAsync(this ILogger logger, string message);
    public static Task LogWarningAsync(this ILogger logger, string message);
    public static void LogCritical(this ILogger logger, string message);
    public static void LogDebug(this ILogger logger, string message);
    public static void LogError(this ILogger logger, Exception ex);
    public static void LogError(this ILogger logger, string message, Exception ex);
    public static void LogError(this ILogger logger, string message);
    public static void LogInformation(this ILogger logger, string message);
    public static void LogTrace(this ILogger logger, string message);
    public static void LogWarning(this ILogger logger, string message);
}

ILogger创建的ILoggerFactory对象提供用于不同级别日志记录信息的API。 这些日志记录级别包括:

public enum LogLevel
{
    Trace,
    Debug,
    Information,
    Warning,
    Error,
    Critical,
    None,
}

下面是如何使用日志记录 API 的示例:

...
IServiceProvider provider = null; // Get the service provider...

var factory = provider.GetLoggerFactory();

var logger = factory.CreateLogger<TestingFramework>();

// ...

if (logger.IsEnabled(LogLevel.Information))
{
    await logger.LogInformationAsync(
        $"Executing request of type '{context.Request}'");
}

// ...

请记住,为防止不必要的分配,您应使用 API 检查级别是否ILogger.IsEnabled(LogLevel)

IMessageBus 服务

消息总线服务是促进测试框架与其扩展之间信息交换的中心机制。

测试平台的消息总线采用 发布-订阅模式

共享总线的总体结构如下所示:

一张图片,表示各种扩展与消息总线的交互。

如图所示,其中包括一个扩展和一个测试框架,有两种潜在的操作:将信息推送到总线或从总线使用信息。

IMessageBus 满足了向总线推送操作的要求,并且 API 为:

public interface IMessageBus
{
    Task PublishAsync(
        IDataProducer dataProducer, 
        IData data);
}

public interface IDataProducer : IExtension
{
    Type[] DataTypesProduced { get; }
}

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

请考虑以下有关参数的详细信息:

  • IDataProducerIDataProducer 向消息总线传达它可以提供的信息Type,并通过继承基接口 IExtension 来建立所有权。 这意味着不能不分青红皂白地将数据推送到消息总线;必须提前声明生成的数据类型。 如果推送意外数据,将触发异常。

  • IData:此接口充当占位符,只需提供描述性详细信息,例如名称和说明。 接口不会透露数据的性质,这是有意的。 这意味着测试框架和扩展可以将任何类型的数据推送到总线,并且此数据可由任何已注册的扩展或测试框架本身使用。

此方法有助于信息交换过程的演变,避免因扩展不熟悉新数据而造成重大变更。 它允许不同版本的扩展和测试框架根据其相互理解以和谐方式运行

总线的另一端称为消费者,消费者订阅了特定类型的数据,并因此可以使用它。

重要

始终使用等待调用 PublishAsync。 如果你不这样做,测试平台和扩展可能无法正确处理 IData ,这可能会导致细微的故障。 仅当你从等待返回后,你才能确信 IData 已在消息总线上排队等待处理。 无论你正在处理哪个扩展点,请确保在退出扩展之前已等待所有 PublishAsync 调用。 例如,如果要实现 testing framework,则不应对 Complete调用 ,直到已等待该特定请求的所有 PublishAsync 调用为止。

IOutputDevice 服务

测试平台囊括了输出设备的概念,允许测试框架和扩展通过将任何类型的数据传输到当前使用的显示系统来呈现信息。

输出设备的最传统示例是控制台输出。

注释

虽然测试平台旨在支持自定义 输出设备,但目前,此扩展点不可用。

要将数据传输到输出设备,必须从IOutputDevice获取IServiceProvider

API 包括:

public interface IOutputDevice
{
    Task DisplayAsync(
        IOutputDeviceDataProducer producer, 
        IOutputDeviceData data);
}

public interface IOutputDeviceDataProducer : IExtension
{
}

public interface IOutputDeviceData
{
}

IOutputDeviceDataProducer扩展IExtension并向输出设备提供发送者的信息。

IOutputDeviceData 用作占位符接口。 背后的 IOutputDevice 概念是容纳更复杂的信息,而不仅仅是彩色文本。 例如,它可以是一个可以以图形方式表示的复杂对象。

默认情况下,测试平台为 IOutputDeviceData 对象提供传统的彩色文本模型:

public class TextOutputDeviceData : IOutputDeviceData
{
    public TextOutputDeviceData(string text)
    public string Text { get; }
}

public sealed class FormattedTextOutputDeviceData : TextOutputDeviceData
{
    public FormattedTextOutputDeviceData(string text)
    public IColor? ForegroundColor { get; init; }
    public IColor? BackgroundColor { get; init; }
}

public sealed class SystemConsoleColor : IColor
{
    public ConsoleColor ConsoleColor { get; init; }
}

下面是一个示例,说明如何将彩色文本与活动的输出设备一起使用:

IServiceProvider provider = null; // Get the service provider...

var outputDevice = provider.GetOutputDevice();

await outputDevice.DisplayAsync(
    this, 
    new FormattedTextOutputDeviceData($"TestingFramework version '{Version}' running tests with parallelism of {_dopValue}")
    {
        ForegroundColor = new SystemConsoleColor
        {
            ConsoleColor = ConsoleColor.Green
        }
    });

除了标准使用彩色文本之外,IOutputDeviceIOutputDeviceData 的主要优点是输出设备完全独立,并且对用户来说是未知的。 这允许开发复杂的用户界面。 例如,实现显示测试进度的 实时 Web 应用程序是完全可行的。

IPlatformInformation 服务

提供有关平台的信息,例如:名称、版本、提交哈希和生成日期。