XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.XUnit

【DLL】提供方便快捷的测试,无需编写Main方法,可直接添加特性在类或方法上进行测试

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using XFEExtension.NetCore.XUnit.Assertions;
using XFEExtension.NetCore.XUnit.Execution;

namespace XFEExtension.NetCore.XUnit.Compatibility;

/// <summary>
/// 提供 3.x 源代码迁移所需的旧测试基类和辅助方法;新代码应直接使用 <see cref="XFERunner"/>、
/// <see cref="Assert"/> 和 <see cref="Attributes.BenchmarkAttribute"/>。
/// </summary>
[Obsolete("Use XFERunner, Assert and BenchmarkAttribute. XFECode will be removed in XUnit 5.0.")]
public abstract class XFECode
{
    /// <summary>
    /// 使用当前进程命令行参数运行生成注册表中的测试,并把结果写入 <see cref="Environment.ExitCode"/>。
    /// </summary>
    /// <returns>表示整个运行完成的任务。</returns>
    [Obsolete("Use generated XFERunner.RunAsync entry points.")]
    public static async Task RunTest() => Environment.ExitCode = await XFERunner.RunAsync(Environment.GetCommandLineArgs().Skip(1).ToArray()).ConfigureAwait(false);

    /// <summary>
    /// 对委托执行一次墙钟计时;该兼容方法不会校准、预热或统计采样,不能替代正式基准。
    /// </summary>
    /// <param name="action">要执行一次的同步委托;为空时仅测量空调用路径。</param>
    /// <param name="autoOutPut">是否把格式化时间写入控制台。</param>
    /// <param name="timerName">控制台输出中使用的计时器名称。</param>
    /// <returns>该次调用测得的墙钟时间。</returns>
    [Obsolete("One-shot timing is not a benchmark. Use BenchmarkAttribute and --benchmarks.")]
    public static TimeSpan CTime(Action? action, bool autoOutPut = true, string timerName = "unnamed")
    {
        var elapsed = Measure(action);
        if (autoOutPut)
            Console.WriteLine($"Name: {timerName}\tElapsed: {Format(elapsed)} (one-shot measurement; not a statistical benchmark)");
        return elapsed;
    }

    /// <summary>
    /// 完整等待异步委托并执行一次墙钟计时;该兼容方法不能替代正式基准。
    /// </summary>
    /// <param name="action">要执行并等待一次的异步委托;为空时只测量计时器路径。</param>
    /// <param name="autoOutPut">是否把格式化时间写入控制台。</param>
    /// <param name="timerName">控制台输出中使用的计时器名称。</param>
    /// <returns>结果为该次异步调用墙钟时间的任务。</returns>
    [Obsolete("One-shot timing is not a benchmark. Use BenchmarkAttribute and --benchmarks.")]
    public static async Task<TimeSpan> CTimeAsync(Func<Task>? action, bool autoOutPut = true, string timerName = "unnamed")
    {
        var stopwatch = Stopwatch.StartNew();
        if (action is not null)
            await action().ConfigureAwait(false);
        stopwatch.Stop();
        if (autoOutPut)
            Console.WriteLine($"Name: {timerName}\tElapsed: {Format(stopwatch.Elapsed)} (one-shot measurement; not a statistical benchmark)");
        return stopwatch.Elapsed;
    }

    /// <summary>
    /// 兼容旧代码并验证条件为真。
    /// </summary>
    /// <param name="condition">要验证的条件。</param>
    /// <param name="message">失败时使用的可选消息。</param>
    /// <returns>断言成功时始终返回 <see langword="true"/>。</returns>
    /// <exception cref="XFEAssertionException"><paramref name="condition"/> 为 <see langword="false"/>。</exception>
    [Obsolete("Use Assert.True.")]
    public static bool Assert(bool condition, string? message = null)
    {
        global::XFEExtension.NetCore.XUnit.Assertions.Assert.True(condition, message);
        return true;
    }

    /// <summary>
    /// 兼容旧代码并验证条件为假。
    /// </summary>
    /// <param name="condition">要验证的条件。</param>
    /// <param name="message">失败时使用的可选消息。</param>
    /// <returns>断言成功时始终返回 <see langword="true"/>。</returns>
    /// <exception cref="XFEAssertionException"><paramref name="condition"/> 为 <see langword="true"/>。</exception>
    [Obsolete("Use Assert.False.")]
    public static bool AssertF(bool condition, string? message = null)
    {
        global::XFEExtension.NetCore.XUnit.Assertions.Assert.False(condition, message);
        return true;
    }

    /// <summary>
    /// 兼容旧代码并验证期望值与实际值相等。
    /// </summary>
    /// <typeparam name="T">参与比较的值类型。</typeparam>
    /// <param name="expected">期望值。</param>
    /// <param name="actual">实际值。</param>
    /// <param name="message">失败时使用的可选消息。</param>
    /// <returns>断言成功时始终返回 <see langword="true"/>。</returns>
    /// <exception cref="XFEAssertionException">两个值不相等。</exception>
    [Obsolete("Use Assert.Equal.")]
    public static bool AssertE<T>(T expected, T actual, string? message = null)
    {
        global::XFEExtension.NetCore.XUnit.Assertions.Assert.Equal(expected, actual, message);
        return true;
    }

    /// <summary>
    /// 在线程池中并行启动指定次数的同步操作,并可选择等待全部任务。
    /// </summary>
    /// <param name="action">每个任务执行的同步操作。</param>
    /// <param name="count">要启动的任务数。</param>
    /// <param name="autoWaitAll">是否在返回前等待全部任务完成。</param>
    /// <returns>包含全部已启动任务的列表。</returns>
    protected static async Task<List<Task>> Circle(Action action, int count, bool autoWaitAll = false)
    {
        var tasks = Enumerable.Range(0, count).Select(_ => Task.Run(action)).ToList();
        if (autoWaitAll)
            await Task.WhenAll(tasks).ConfigureAwait(false);
        return tasks;
    }

    /// <summary>
    /// 按顺序在线程池中执行指定次数的同步操作。
    /// </summary>
    /// <param name="action">每次执行的同步操作。</param>
    /// <param name="count">顺序执行次数。</param>
    /// <returns>表示全部操作完成的任务。</returns>
    protected static async Task CircleOrderly(Action action, int count)
    {
        for (var i = 0; i < count; i++)
            await Task.Run(action).ConfigureAwait(false);
    }

    /// <summary>
    /// 可选显示默认提示,然后等待用户按下任意键。
    /// </summary>
    /// <param name="showText">是否显示默认提示文本。</param>
    protected static void Pause(bool showText = true)
    {
        if (showText)
            Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

    /// <summary>
    /// 显示指定提示并等待用户按下任意键。
    /// </summary>
    /// <param name="showText">等待按键前显示的文本。</param>
    protected static void Pause(string showText)
    {
        Console.WriteLine(showText);
        Console.ReadKey();
    }

    /// <summary>
    /// 等待用户按下指定控制台按键。
    /// </summary>
    /// <param name="consoleKey">用于继续执行的按键。</param>
    protected static void Pause(ConsoleKey consoleKey)
    {
        Console.WriteLine($"Press {consoleKey} to continue...");
        while (Console.ReadKey().Key != consoleKey) { }
    }

    /// <summary>
    /// 显示指定提示并等待用户按下指定控制台按键。
    /// </summary>
    /// <param name="consoleKey">用于继续执行的按键。</param>
    /// <param name="showText">等待按键前显示的文本。</param>
    protected static void Pause(ConsoleKey consoleKey, string showText)
    {
        Console.WriteLine(showText);
        while (Console.ReadKey().Key != consoleKey) { }
    }

    private static TimeSpan Measure(Action? action)
    {
        var stopwatch = Stopwatch.StartNew();
        action?.Invoke();
        stopwatch.Stop();
        return stopwatch.Elapsed;
    }

    private static string Format(TimeSpan elapsed) => elapsed.TotalNanoseconds switch
    {
        >= 1_000_000_000 => $"{elapsed.TotalSeconds:F3} s",
        >= 1_000_000 => $"{elapsed.TotalMilliseconds:F3} ms",
        >= 1_000 => $"{elapsed.TotalMicroseconds:F3} us",
        _ => $"{elapsed.TotalNanoseconds:F3} ns"
    };
}