当前位置:网站首页>使用 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
边栏推荐
- 我是如何失去团队掌控的?
- 构造请求日志分析系统
- Adobe media encoder /Me 2021软件安装包(附安装教程)
- Why do we need software engineering -- looking at a simple project
- 使用“1”个参数调用“DownloadString”时发生异常:“操作超时”
- delphi10的rest.json与system.json的踩坑
- 统计文本中字母的频次(不区分大小写)
- Git代码提交操作,以及git push提示failed to push some refs'XXX'
- Design pattern of facade and mediator
- The samesite problem of cross domain cookie of Chrome browser results in abnormal access to iframe embedded pages
猜你喜欢

Business facade and business rule

Reflection on a case of bus card being stolen and swiped

构造请求日志分析系统

不懂数据库索引的底层原理?那是因为你心里没点b树

websocket+probuf.原理篇

一万四千字分布式事务原理解析,全部掌握你还怕面试被问?

Improvement of maintenance mode of laravel8 update

Win10官方1909版本无法打开windows安全中心中病毒和威胁防护的实时保护解决方案。
![[original] the influence of arm platform memory and cache on the real-time performance of xenomai](/img/cb/0395507ece572556b2bab373f013a5.jpg)
[original] the influence of arm platform memory and cache on the real-time performance of xenomai

京淘项目day09
随机推荐
Awk implements SQL like join operation
Insight -- the application of sanet in arbitrary style transfer
The official 1909 version of win10 cannot open the real-time protection solution of virus and threat protection in windows security center.
不懂数据库索引的底层原理?那是因为你心里没点b树
【原创】ARM平台内存和cache对xenomai实时性的影响
The CPU does this without the memory
Big data algorithm - bloon filter
Code Review最佳实践
Dynamic programming -- state compression DP of set represented by binary
洞察——风格注意力网络(SANet)在任意风格迁移中的应用
手撕算法-手写单例模式
How did I lose control of the team?
What magic things can a line of Python code do?
chrome浏览器跨域Cookie的SameSite问题导致访问iframe内嵌页面异常
Adobe media encoder /Me 2021软件安装包(附安装教程)
awk实现类sql的join操作
深入web workers (上)
聊聊Go代码覆盖率技术与最佳实践
编程界大佬教你:一行Python代码能做出哪些神奇的事情?
awk实现类sql的join操作