当前位置:网站首页>使用 Xunit.DependencyInjection 改造测试项目
使用 Xunit.DependencyInjection 改造测试项目
2020-11-07 21:03:00 【程序猿欧文】
使用 Xunit.DependencyInjection 改造测试项目
Intro
这篇文章拖了很长时间没写,之前也有介绍过 Xunit.DependencyInjection 这个项目,这个项目是由大师写的一个 Xunit 基于微软 GenericHost 和 依赖注入实现的一个扩展库,可以让你更方便更容易的在测试项目里实现依赖注入,而且我觉得另外一点很好的是可以更好的控制操作流程,比如很多在启动测试之前去做的初始化操作,更好用的流程控制。
最近把我们公司的测试项目大多基于 Xunit.DependencyInjection 改造了,使用效果很好。
最近把我的测试项目从原来自己手动启动一个 Web Host 改成了基于 Xunit.DepdencyInjection 来使用,同时也是为我们公司的一个项目的集成测试的更新做准备,用起来很香~
我觉得 Xunit.DependencyInjection 解决了我两个很大的痛点,一个是依赖注入的代码写起来不爽,一个是更简单的流程控制处理,下面大概介绍一下
XUnit.DependencyInjection 工作流程
Xunit.DepdencyInjection 主要的流程在 DependencyInjectionTestFramework 中,详见 https://github.com/pengweiqhca/Xunit.DependencyInjection/blob/7.0/Xunit.DependencyInjection/DependencyInjectionTestFramework.cs
首先会去尝试寻找项目中的 Startup ,这个 Startup 很类似于 asp.net core 中的 Startup,几乎完全一样,只是有一点不同, Startup 不支持依赖注入,不能像 asp.net core 中那样注入一个 IConfiguration 对象来获取配置,除此之外,和 asp.net core 的 Startup 有着一样的体验,如果找不到这样的 Startup 就会认为没有需要依赖注入的服务和特殊的配置,直接使用 Xunit 原有的 XunitTestFrameworkExecutor,如果找到了 Startup 就从 Startup 约定的方法中配置 Host,注册服务以及初始化配置流程,最后使用 DependencyInjectionTestFrameworkExecutor 执行我们的 test case.
源码解析
源码使用了 C#8 的一些新语法,代码十分简洁,下面代码使用了可空引用类型:
DependencyInjectionTestFramework源码
public sealed class DependencyInjectionTestFramework : XunitTestFramework{ public DependencyInjectionTestFramework(IMessageSink messageSink) : base(messageSink) { } protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) { IHost? host = null; try { // 获取 Startup 实例 var startup = StartupLoader.CreateStartup(StartupLoader.GetStartupType(assemblyName)); if (startup == null) return new XunitTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); // 创建 HostBuilder var hostBuilder = StartupLoader.CreateHostBuilder(startup, assemblyName) ?? new HostBuilder().ConfigureHostConfiguration(builder => builder.AddInMemoryCollection(new Dictionary<string, string> { { HostDefaults.ApplicationKey, assemblyName.Name } })); // 调用 Startup 中的 ConfigureHost 方法配置 Host StartupLoader.ConfigureHost(hostBuilder, startup); // 调用 Startup 中的 ConfigureServices 方法注册服务 StartupLoader.ConfigureServices(hostBuilder, startup); // 注册默认服务,构建 Host host = hostBuilder.ConfigureServices(services => services .AddSingleton(DiagnosticMessageSink) .TryAddSingleton<ITestOutputHelperAccessor, TestOutputHelperAccessor>()) .Build(); // 调用 Startup 中的 Configure 方法来初始化 StartupLoader.Configure(host.Services, startup); // 返回 testcase executor,准备开始跑测试用例 return new DependencyInjectionTestFrameworkExecutor(host, null, assemblyName, SourceInformationProvider, DiagnosticMessageSink); } catch (Exception e) { return new DependencyInjectionTestFrameworkExecutor(host, e, assemblyName, SourceInformationProvider, DiagnosticMessageSink); } }}
StarpupLoader源码
public static Type? GetStartupType(AssemblyName assemblyName){ var assembly = Assembly.Load(assemblyName); var attr = assembly.GetCustomAttribute<StartupTypeAttribute>(); if (attr == null) return assembly.GetType($"{assemblyName.Name}.Startup"); if (attr.AssemblyName != null) assembly = Assembly.Load(attr.AssemblyName); return assembly.GetType(attr.TypeName) ?? throw new InvalidOperationException($"Can't load type {attr.TypeName} in '{assembly.FullName}'");}public static object? CreateStartup(Type? startupType){ if (startupType == null) return null; var ctors = startupType.GetConstructors(); if (ctors.Length != 1 || ctors[0].GetParameters().Length != 0) throw new InvalidOperationException($"'{startupType.FullName}' must have a single public constructor and the constructor without parameters."); return Activator.CreateInstance(startupType);}public static IHostBuilder? CreateHostBuilder(object startup, AssemblyName assemblyName){ var method = FindMethod(startup.GetType(), nameof(CreateHostBuilder), typeof(IHostBuilder)); if (method == null) return null; var parameters = method.GetParameters(); if (parameters.Length == 0) return (IHostBuilder)method.Invoke(startup, Array.Empty<object>()); if (parameters.Length > 1 || parameters[0].ParameterType != typeof(AssemblyName)) throw new InvalidOperationException($"The '{method.Name}' method of startup type '{startup.GetType().FullName}' must without parameters or have the single 'AssemblyName' parameter."); return (IHostBuilder)method.Invoke(startup, new object[] { assemblyName });}public static void ConfigureHost(IHostBuilder builder, object startup){ var method = FindMethod(startup.GetType(), nameof(ConfigureHost)); if (method == null) return; var parameters = method.GetParameters(); if (parameters.Length != 1 || parameters[0].ParameterType != typeof(IHostBuilder)) throw new InvalidOperationException($"The '{method.Name}' method of startup type '{startup.GetType().FullName}' must have the single 'IHostBuilder' parameter."); method.Invoke(startup, new object[] { builder });}public static void ConfigureServices(IHostBuilder builder, object startup){ var method = FindMethod(startup.GetType(), nameof(ConfigureServices)); if (method == null) return; var parameters = method.GetParameters(); builder.ConfigureServices(parameters.Length switch { 1 when parameters[0].ParameterType == typeof(IServiceCollection) => (.........
版权声明
本文为[程序猿欧文]所创,转载请带上原文链接,感谢
https://my.oschina.net/mikeowen/blog/4707688
边栏推荐
猜你喜欢

Web API系列(三)统一异常处理

The official 1909 version of win10 cannot open the real-time protection solution of virus and threat protection in windows security center.

年薪90万程序员不如月入3800公务员?安稳与高收入,到底如何选择?

聊聊Go代码覆盖率技术与最佳实践

From technology to management, the technology of system optimization is applied to enterprise management

Three steps, one pit, five steps and one thunder, how to lead the technical team under the rapid growth?

Design pattern of facade and mediator

What kind of technical ability should a programmer who has worked for 1-3 years? How to improve?

What magic things can a line of Python code do?

How to choose a good company
随机推荐
Let's talk about the locks in the database
编程界大佬教你:一行Python代码能做出哪些神奇的事情?
低代码 vs 模型驱动,它们之间到底是什么关系?
How did I lose control of the team?
技术总监7年自述——如何选择一家好公司
Technical debt is a lack of real understanding of business functions- daverupert.com
Awk implements SQL like join operation
easyui dialog“缓存问题”
三步一坑五步一雷,高速成长下的技术团队怎么带?
Don't treat exceptions as business logic, which you can't afford
Deep into web workers (1)
C language I blog assignment 03
On the coverage technology and best practice of go code
Web安全(四)---XSS攻击
Code Review Best Practices
Stack bracket matching
高级并发编程系列九(Lock接口分析)
bgfx编译教程
Using thread communication to solve the problem of cache penetrating database avalanche
快速上手Git