在 xunit 测试项目中使用依赖注入

  • A+
所属分类:.NET技术
摘要

之前写过几篇 xunit 依赖注入的文章,今天这篇文章将结合我在 .NET Conf 上的分享,更加系统的分享一下在测试中的应用案例。


在 xunit 测试项目中使用依赖注入

Intro

之前写过几篇 xunit 依赖注入的文章,今天这篇文章将结合我在 .NET Conf 上的分享,更加系统的分享一下在测试中的应用案例。

之所以想分享这个话题是因为我觉得在我们开发过程中测试是非常重要的一部分,高质量项目的一个重要指标就是测试覆盖率,同时依赖注入已经是一个现代化应用中不可缺少的一部分,我们的 .NET Core 也是从一开始就集成了依赖注入,依赖注入对于测试项目也是不能缺席的。

xunit 是 .net 里目前使用的最多的测试组件,Xunit.DependencyInjection 是大师写的一个 xunit 依赖注入的扩展,它是基于微软的 GenericHost(通用主机) 来实现的,使用它我们可以很轻松的实现依赖注入,很好的和 .NET Core 做集成。

How it works

那它是如何工作的呢?我们一起来看一下它的执行流程,它的执行流程分为四步

首先需要构建一个 Host,然后启动这个 Host,启动完成后执行测试用例,最后终止这个 Host

在 xunit 测试项目中使用依赖注入

Host 又是如何构建的呢?我们一起看一下,Host 的构建也是分为四步

第一步,创建一个 HostBuilder,大多数情况下我们不需要用这个方法,使用默认的实现就好

第二步,Host 配置,对 Host 做一些自定义配置

第三步,服务配置,注册需要的服务

第四步,Configure,可以做一些初始化的配置,比如配置初始化以及测试数据的初始化等

在 xunit 测试项目中使用依赖注入

我们可以在测试项目里创建一个 Startup 类来控制 Host 的构建过程

示例

接着我们来看一些实际的测试示例,示例分为三部分,首先是一些基本用法,然后是和其他组件的集成,最后是一些扩展用法

在 xunit 测试项目中使用依赖注入

Get Started

首先来看一下 Startup 的用法,这个 Startup 和 asp.net core 里的 Startup 是很像的,无论是使用方式上还是实现上都是类似的,有兴趣的可以看一下源码对比一下,我们来看一下使用方式,通过下面的示例来感受一下

在 xunit 测试项目中使用依赖注入

如果你只需要注册服务,直接在 Startup 中添加一个 ConfigureServices 方法,在这个方法中注册自己需要的服务即可,和 asp.net core 并无太多不同

如果你需要做一些初始化的工作,可以加一个 Configure 方法,在这个方法中实现自己的初始化逻辑就可以了,如果初始化的时候需要获取注入的服务实例,直接作为方法参数就可以,类似于 asp.net core 中 Configure 方法,只是不需要配置 Http 请求管道

如果你需要使用的配置,需要使用 Configuration,可以在 ConfigureHost 方法中通过 ConfigureHostConfiguration 扩展方法注册自己的配置

如果需要在注册服务的时候用到配置,可以在 ConfigureServices 方法中添加一个 HostBuildContext 的参数,HostBuilderContext 中的 Configuration 对象就是在 ConfigureHost 中注册的配置

如果需要在 Configure 方法中使用配置,直接添加一个 IConfiguration 的方法参数就可以了

我们再来看一下,如何在测试用例中使用注入的服务,一般情况下我们会直接通过构造器注入,在构造方法中添加需要注入的服务即可,除此之外我们还可以通过方法参数注入,结合 InlineDataMemeberData 使用,来看一下这个示例

在 xunit 测试项目中使用依赖注入

IoC/AOP Integration

接着我们来看一下和其他组件的集成,AutoFac 是一个很流行的 IOC 组件,AspectCore 是柠檬大佬写的一个 AOP 框架,我们以这两个为例子来看一下如何集成第三方的依赖注入和 AOP 组件,前面我们已经提到它是基于微软的 GenericHost 实现的,而 asp.net core 从 3.0 开始也是基于 GenericHost 实现的,所以在 asp.net core 里怎么集成,在这里也是一样的,来看一下示例,只需要使用对应的 ServiceProviderFactory 就可以了,是不是很简单呢

在 xunit 测试项目中使用依赖注入

在 xunit 测试项目中使用依赖注入

Test Server Integration

然后我们来看一下如何和 TestServer 做集成,TestServer 主要用于集成测试,使用 TestServer 的好处在于它是基于内存进行交互的没有真正的 HTTP 请求和 TCP 链接,会非常的高效,而且也不会监听某一个端口,所以不会有端口权限的问题。

TestServer 的使用主要有两步,首先是服务的注册,可以使用 IHostBuilderIWebHostBuilderUseTestServer 扩展方法注册 TestServer,可以使用 IHostGetTestClient 扩展方法来注册和 TestServer 进行交互的 HttpClient

服务注册好之后就可以在测试用例里通过注入的 HttpClient 请求 API 或页面了,可以参考这个例子

在 xunit 测试项目中使用依赖注入

Extensions

Hosted Service

然后我们来看一些扩展用法,IHostedService 可以用来实现一些初始化的操作或者后台服务,我们可以使用 IHostedService 来实现对应用的 Ready 检查,应用 Ready 之后再开始执行测试用例,这在有些场景下是很有用的

我们在 k8s 中部署的应用一般都会有一个 HealthCheck/ReadinessCheck 的接口来供 k8s 的 liveness/readiness 探针来探测应用的状态,只有应用 Ready 之后才会对外部提供服务

这个示例就是一个使用 IHostedService 来实现等待应用 Ready 后再开始执行测试用例的一个 demo

在 xunit 测试项目中使用依赖注入

注意:这里的等待不能在 StartupConfigure 方法中执行,因为 Configure 的执行是在调用 Host 的 StartAsync 方法之前执行的,而此时 webServer 还没有启动,所以是不能获取到 TestClient 的,而我们通过 HostedService 就可以在 Web Server 启动之后再执行我们的等待 Ready 逻辑

ITestOutputHelperAccessor

在测试中如果想要输出一个日志的话只能借助于 ITestOutputHelper 来输出,直接使用 Console.Write[Line] 是看不到任何输出的,ITestOutputHelper 只能在测试用例中使用,在测试服务中是不能使用的,Xunit.DependencyInjection 提供了一个 ITestOutputHelperAccessor 的服务,类似于 IHttpContextAccessor,我们可以借助它来在自定义的服务中获取 ITestOutputHelper 来输出日志

这里是一个简单的示例

在 xunit 测试项目中使用依赖注入

Logging

再来看一个 OutputHelperAccessor 的实际应用,Xunit.DependencyInjection 提供了一个 Logging 的扩展,使得我们可以把测试过程中的日志输出出来,更好的帮助我们调试

集成方式也比较简单,可以参考这个示例,引用 Xunit.DependencyInjection.Logging 之后,在 LoggerFactory 中注册 XunitTestOutputLoggerProvider 即可

可以看到我们的日志直接输出出来了,默认的日志级别是 Information ,所以 Debug 级别的日志没有输出出来,有需要的话可以在注册的时候提供一个委托来控制是否要输出日志

在 xunit 测试项目中使用依赖注入

Project Template

为了方便大家使用,我们提供了一个项目模板,可以通过一个命令就可以直接创建好一个测试项目,会包含一个默认的 Startup 不再需要自己去写方法了,使用的时候只需要根据需要做删减就可以了

默认的 TargetFramework 使用的是 netcoreapp3.1,可以通过 -f/--franework 指定自己想要使用的目标框架,比如说想要生成 net 5.0 的项目只需要指定 -f net5.0 就可以了

在 xunit 测试项目中使用依赖注入

生成的内容如下所示:

在 xunit 测试项目中使用依赖注入

More

最后列出来了一些可能会有帮助的链接,第一个是项目的源代码,第二个是 PPT 中所有示例的源代码,后面的是使用到的 Nuget 包。

这个 xunit 扩展的代码实现是非常值得学习的,有很多和 asp.net core 的实现是很像的,有需要的可以去看看源码学习一下。

希望我的分享对大家有所帮助,大家在使用过程中有遇到任何问题都可以随时联系我或者直接在 Github 上建 issue。

Reference