当前位置:网站首页>使用 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
边栏推荐
- 一次公交卡被“盗刷”事件带来的思考
- 我是如何失去团队掌控的?
- graph generation model
- [C + + learning notes] how about the simple use of the C + + standard library STD:: thread?
- Code Review Best Practices
- Don't treat exceptions as business logic, which you can't afford
- Do not understand the underlying principle of database index? That's because you don't have a B tree in your heart
- C语言I博客作业03
- C language I blog assignment 03
- 快速上手Git
猜你喜欢
Using thread communication to solve the problem of cache penetrating database avalanche
华为HCIA笔记
编程界大佬教你:一行Python代码能做出哪些神奇的事情?
AC86U kx上网
Three steps, one pit, five steps and one thunder, how to lead the technical team under the rapid growth?
大数据算法——布隆过滤器
【解决方案】分布式定时任务解决方案
三步一坑五步一雷,高速成长下的技术团队怎么带?
阿里terway源码分析
Don't treat exceptions as business logic, which you can't afford
随机推荐
Web安全(四)---XSS攻击
websocket+probuf.原理篇
Awk implements SQL like join operation
Technical debt is a lack of real understanding of business functions- daverupert.com
Improvement of maintenance mode of laravel8 update
The CPU does this without the memory
Adobe Prelude /Pl 2020软件安装包(附安装教程)
In the age of screen reading, we suffer from attention deficit syndrome
Using pipe() to improve code readability in pandas
Don't treat exceptions as business logic, which you can't afford
[C + + learning notes] how about the simple use of the C + + standard library STD:: thread?
看一遍就理解,图解单链表反转
年薪90万程序员不如月入3800公务员?安稳与高收入,到底如何选择?
Insight -- the application of sanet in arbitrary style transfer
How to deal with data leakage and deletion related to business life and death?
GrowingIO 响应式编程探索和实践
某618大促项目的复盘总结
建议患者自杀,OpenAI警告:GPT-3用于医疗目的风险太高
使用 Xunit.DependencyInjection 改造测试项目
Vscode configuration