简介

ABP vNext 框架使用 xUnit 作为单元测试组件,官方的所有模块都编写了大量的 单元/集成测试 确保功能正常。由于 ABP vNext 模块化系统的原因,开发人员在建立单元测试项目的时候需要集成 Volo.Abp.UnitTest 项目,这样在执行单元测试的时候才不会缺少必要组件。

分析

ABP vNext 单元测试相关的类型最核心的是集成测试基类 AbpIntegratedTest 和 MVC 专用测试基类 AbpAspNetCoreIntegratedTestBase,这两个基类核心工作就是初始化 IoC 容器并且初始化整个模块系统,只不过后者对 控制器 相关的组件进行了初始化配置,让开发人员可以针对 控制器 进行单元/集成测试。

从上图可以看到两个基类都继承自 AbpTestBaseWithServiceProvider 基类,在这个基类里面将 IServiceProvider 作为一个抽象成员。这是因为 MVC 和测试基类的 ServiceProvider 来源不一样,一个是 ABP vNext 根据 Application 类已注册 IoC 容器构建的,另一个使用的是 IHost 测试主机内的 ServiceProvider

单元测试执行本质上就是将测试类进行实例化,然后调用对应的单元测试方法,所以测试基类的初始化动作都是放在对应的无参构造函数。

虽然 Volo.Abp.UnitTest 也是单独的一个项目,它的 AbpTestBaseModule 是没有任何动作,仅仅是为了同其他项目保持一致,内部是没有任何代码。

using Volo.Abp.Modularity;

namespace Volo.Abp
{
public class AbpTestBaseModule : AbpModule
{ }
}

集成测试基类

一般来说,我们会直接从 AbpIntegratedTest 继承并实现我们需要的单元测试基类,包括 ABP vNext 官方的默认模版也是这样。集成测试基类的核心代码很简单,就是在无参构造函数的内部进行初始化动作,且在过程中按顺序执行两个生命周期方法。

简易流程图:

public abstract class AbpIntegratedTest<TStartupModule> : AbpTestBaseWithServiceProvider, IDisposable
where TStartupModule : IAbpModule
{
protected IAbpApplication Application { get; } protected override IServiceProvider ServiceProvider => Application.ServiceProvider; protected IServiceProvider RootServiceProvider { get; } protected IServiceScope TestServiceScope { get; } protected AbpIntegratedTest()
{
var services = CreateServiceCollection(); BeforeAddApplication(services); var application = services.AddApplication<TStartupModule>(SetAbpApplicationCreationOptions);
Application = application; AfterAddApplication(services); // 根据已有 IServiceCollection 创建 IoC 容器。
RootServiceProvider = CreateServiceProvider(services);
TestServiceScope = RootServiceProvider.CreateScope(); // 使用子容器对 ABP 模块系统进行初始化。
application.Initialize(TestServiceScope.ServiceProvider);
} // ... 其他代码。
}

上述代码可以看到默认的测试基类并没有直接使用 RootServiceProvider,而是创建了一个子容器给 ABP vNext 使用,这主要是为了后续可以对容器进行销毁操作,具体可一看下面的 Dispose() 方法。

public virtual void Dispose()
{
Application.Shutdown();
TestServiceScope.Dispose();
Application.Dispose();
}

总的来说,测试基类就是构建了一个 IAbpApplication 对象,根据传入的 TStartupModule 模块进入拓扑排序过程,依次执行各个模块的生命周期配置。

MVC 测试基类

针对我们的 Http Api 层,如果需要对 Controller 进行测试的话,就需要从 MVC 测试基类继承编写单元/集成测试,各个类型的关系如下。

针对 AspNetCore 来说,ABP 创建了一个新的 Host 主机,在每次执行测试的时候会启动一个新的 Web 服务器。(并不会创建真实服务,不存在端口占用问题)

在基类当中,ABP 定义了两个属性 ServerClient,它们都是 Mock 了对应的接口,方便后续的单元测试,这里的 ITestServerAccessor 接口是用于 Mock AspNetCoreTestDynamicProxyHttpClientFactory 接口所需要的。

AspNetCoreTestDynamicProxyHttpClientFactory 接口是 ABP 底层进行动态代理所使用的,在请求远程服务的时候会调用这个接口创建 HttpClient 对象。

protected AbpAspNetCoreIntegratedTestBase()
{
var builder = CreateHostBuilder(); _host = builder.Build();
_host.Start(); Server = _host.GetTestServer();
Client = _host.GetTestClient(); ServiceProvider = Server.Services; ServiceProvider.GetRequiredService<ITestServerAccessor>().Server = Server;
}

从 UML 类图当中,可以看到基类定义了几个 GetUrl() 方法,这几个方法是根据 Controller 获取对应的请求路径。

这里我以一个 SampleController 控制器为例,它提供了一个 Index 方法,返回了一个页面内容。针对它来说,我们编写集成测试是这样操作的。

public class SimpleController : AbpController
{
public ActionResult Index()
{
return Content("Index-Result");
}
}

集成测试

public class SimpleController_Tests : AbpAspNetCoreIntegratedTestBase<Startup>
{
protected virtual async Task<HttpResponseMessage> GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url))
{
requestMessage.Headers.Add("Accept-Language", CultureInfo.CurrentUICulture.Name);
var response = await Client.SendAsync(requestMessage);
response.StatusCode.ShouldBe(expectedStatusCode);
return response;
}
} protected virtual async Task<string> GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
{
using (var response = await GetResponseAsync(url, expectedStatusCode))
{
return await response.Content.ReadAsStringAsync();
}
} [Fact]
public async Task ActionResult_ContentResult()
{
var result = await GetResponseAsStringAsync(
GetUrl<SimpleController>(nameof(SimpleController.Index))
); result.ShouldBe("Index-Result");
}
}

EF Core 的集成

在执行单元测试过程中,我们难免会对数据库进行操作。这个时候不可能连接真实数据库,就需要我们在测试基类当中进行一些初始化动作,将底层的数据库链接改为 SQLite 的内存模式。

public class SampleEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection; public override void ConfigureServices(ServiceConfigurationContext context)
{
ConfigureInMemorySqlite(context.Services);
} private void ConfigureInMemorySqlite(IServiceCollection services)
{
// 建立链接并执行迁移。
_sqliteConnection = CreateDatabaseAndGetConnection(); // 使用 SQLite 作为 EF Provider。
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
} public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_sqliteConnection.Dispose();
} private static SqliteConnection CreateDatabaseAndGetConnection()
{
// 使用 SQLite 的内存模式链接字符串。
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open(); var options = new DbContextOptionsBuilder<SampleMigrationsDbContext>()
.UseSqlite(connection)
.Options; // 执行迁移,构建表结构。
using (var context = new SampleMigrationsDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
} return connection;
}
}

总结

ABP 的测试更偏向于集成测试,因为各个功能都依赖于模块,所以在执行单元测试的时候会运行更长的时间。日常开发过程当中,我们更多地还是针对应用层进行测试就可以了,粒度更细的话也可以针对仓储层领域层API 层 编写测试即可。

为了保证项目质量,在开发完成之后编写单元/集成测试是每个开发人员应做的工作。编写单元/集成测试,虽然不能 100% 避免 BUG,但可以保证每次进行业务修改之后接口的正确性。

[Abp vNext 源码分析] - 18. 单元测试的更多相关文章

  1. [Abp vNext 源码分析] - 文章目录

    一.简要介绍 ABP vNext 是 ABP 框架作者所发起的新项目,截止目前 (2019 年 2 月 18 日) 已经拥有 1400 多个 Star,最新版本号为 v 0.16.0 ,但还属于预览版 ...

  2. [Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)

    一.简要介绍 ABP vNext 框架本身就是围绕着 DDD 理念进行设计的,所以在 DDD 里面我们能够见到的实体.仓储.值对象.领域服务,ABP vNext 框架都为我们进行了实现,这些基础设施都 ...

  3. [Abp vNext 源码分析] - 4. 工作单元

    一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...

  4. [Abp vNext 源码分析] - 11. 用户的自定义参数与配置

    一.简要说明 文章信息: 基于的 ABP vNext 版本:1.0.0 创作日期:2019 年 10 月 23 日晚 更新日期:暂无 ABP vNext 针对用户可编辑的配置,提供了单独的 Volo. ...

  5. [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)

    一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...

  6. [Abp vNext 源码分析] - 3. 依赖注入与拦截器

    一.简要说明 ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包.这里与原来的 ABP 框架 ...

  7. [Abp vNext 源码分析] - 2. 模块系统的变化

    一.简要说明 本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 I ...

  8. [Abp vNext 源码分析] - 1. 框架启动流程分析

    一.简要说明 本篇文章主要剖析与讲解 Abp vNext 在 Web API 项目下的启动流程,让大家了解整个 Abp vNext 框架是如何运作的.总的来说 ,Abp vNext 比起 ABP 框架 ...

  9. [Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)

    一.简要介绍 ABP vNext 针对于应用服务层,为我们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块. PS:最近博主也是在恶补 DDD 相关的知识,这里推 ...

随机推荐

  1. Java内存模型(JMM)是什么?JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证

    Java内存模型就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范. Java内存模型是根据英文Java Memo ...

  2. 零信任三大技术之SDP

    SDP概述 SDP Software Defined Perimeter(软件定义边界),2013 年由云安全联盟 CSA提出. SDP 设计基本原则 1.信息隐身:隐藏服务器地址.端口,使之不被扫描 ...

  3. Linux没有ens33解决方案

    一.前言 运行环境:window10+VMware14+Centos7 博主最近遇到一个比较郁闷的问题,在虚拟机上操作Linux系统查看IP的时候,发现没有ens33或者eth0了,试了很多办法都没有 ...

  4. Prometheus时序数据库-磁盘中的存储结构

    Prometheus时序数据库-磁盘中的存储结构 前言 之前的文章里,笔者详细描述了监控数据在Prometheus内存中的结构.而其在磁盘中的存储结构,也是非常有意思的,关于这部分内容,将在本篇文章进 ...

  5. MySQL:单表查询

    在线数据库:http://sqlfiddle.com/ 建表脚本:https://gitee.com/bqzzz/sql/tree/master/Scripts 基本查询 ①.查询所有记录 SELEC ...

  6. MySQL之九---分布式架构(Mycat/DBLE)

    MyCAT基础架构图 双主双从结构 MyCAT基础架构准备 准备环境  环境准备: 两台虚拟机 db01 db02 每台创建四个mysql实例:3307 3308 3309 3310 删除历史环境 p ...

  7. PAT-1102(Invert a Binary Tree)+二叉树的镜像+层次遍历+中序遍历+已知树的结构构树

    Invert a Binary Tree pat-1102 import java.util.Arrays; import java.util.Queue; import java.util.Scan ...

  8. Springboot的监控

    目录 Micrometer 计数器 仪表 摘要 计时器 Prometheus grafana 保存后我们就能在dashboard上看得我我们的监控指标了参考 Spring Boot有个子项目Sprin ...

  9. FreeBSD jail 折腾记(二)

    FreeBSD jail 折腾记(二) 创建jail目录 创建4个 分别是模板 骨架 数据 项目 创建模板目录 mkdir -p /jail/j1 # 然后放入基本目录,上篇说过不再写 创建骨架目录 ...

  10. 树莓派 3/4 安装 FreeBSD

    已盼春来归 已盼春来归 今日去 愿为春来归 盼归春天来了 FreeBSD 的春天在哪里? 树莓派是什么,相信凡是关注了我们的人都不会不知道,但是介于非专业人员需要在此做简要介绍.我们的安卓手机,大部分 ...