十一、Abp vNext 基础篇丨测试
前言
祝大家国庆快乐,本来想国庆之前更新完的,结果没写完,今天把剩下的代码补了一下总算ok了。
本章节也是我们后端日常开发中最重要的一步就是测试,我们经常听到的单元测试、集成测试、UI测试、系统测试,还有就是最常见的(人肉测试),这些理论知识我记得老张有个视频讲了2篇,欢迎大家可以去群里骚扰老张,我找不到那个链接了。
话不多说直接上车!
理论速过
ABP 框架的设计考虑了可测试性。有一些不同级别的自动化测试;
- 单元测试:您通常测试单个类(或几个类一起)。这些测试会很快。但是,您通常需要处理服务依赖项的模拟。
- 集成测试:您通常会测试服务,但这次您不会模拟基本的基础设施和服务,以查看它们是否可以正常协同工作。
- UI 测试:您测试应用程序的 UI,就像用户与您的应用程序交互一样。###
单元测试与集成测试
与单元测试相比,集成测试有一些显着的优势;
- 更容易编写,因为您不需要建立模拟和处理依赖项。
- 测试代码与所有真实的服务和基础设施(包括数据库映射和查询)一起运行,因此它更接近于真实的应用程序测试。
虽然它们有一些缺点;
- 与单元测试相比,它们更慢,因为所有的基础设施都是为每个测试用例准备的。
- 服务中的一个错误可能会导致多个测试用例被破坏,因此在某些情况下可能更难找到真正的问题。
我们建议混合使用:在必要的地方编写单元或集成测试,并且您认为编写和维护它是有效的。
Abp的默认已经帮我们生成了测试模板
- Domain.Tests用于测试您的域层对象(如域服务和实体)。
- Application.Tests用于测试您的应用程序层(如Application Services)。
- EntityFrameworkCore.Tests用于测试您的自定义存储库实现或 EF Core 映射(如果您使用其他数据库提供程序,此项目将有所不同)。
- TestBase 包含一些被其他项目共享/使用的类。
注意:HttpApi.Client.ConsoleTestApp不是自动化测试应用程序。这是一个示例控制台应用程序,展示了如何从 .NET 控制台应用程序使用您的 HTTP API。
单元测试
简单来看一下如何完成一个单元测试,依Blog
实体为例,实体是领域层的一部分,我们应该在Domain.Tests项目中对其进行测试。Blog_Tests在Domain.Tests项目中创建一个类(领域服务同理、)
此测试遵循 AAA(安排-行为-断言)模式;
- 安排部分创建一个Blog实体。
- 行为部分执行我们要为此案例测试的方法。
- 断言部分检查Blog属性是否与我们期望的相同。
public class Blog_Tests
{
[Theory]
[InlineData("aaa")]
[InlineData("bbb")]
public void SetName(string name)
{
var blog = new Blog(Guid.NewGuid(), "test blog", "test");
blog.SetName(name);
blog.Name.ShouldBe(name);
}
[Theory]
[InlineData("aaa")]
[InlineData("bbb")]
public void SetShortName(string name)
{
var blog = new Blog(Guid.NewGuid(), "test blog", "test");
blog.SetShortName(name);
blog.ShortName.ShouldBe(name);
}
}
集成测试
ABP 提供了一个完整的基础设施来编写集成测试。所有 ABP 基础设施和服务都将在您的测试中执行。应用程序启动模板附带为您预先配置的必要基础设施,启动模板配置为对 EF Core使用内存中的 SQLite数据库(对于 MongoDB,它使用Mongo2Go库)。因此,所有配置和查询都是针对真实数据库执行的,您甚至可以测试数据库事务。
种子数据
针对空数据库编写测试是不切实际的。大多数情况下,需要在数据库中获取一些初始数据。
在Bcvp.Blog.Core.TestBase
层的CoreTestDataSeedContributor.cs
中创建种子数据,这样我们就可以使用这些数据来测试了。
public class CoreTestData : ISingletonDependency
{
public Guid Blog1Id { get; } = Guid.NewGuid();
public Guid Blog1Post1Id { get; } = Guid.NewGuid();
public Guid Blog1Post2Id { get; } = Guid.NewGuid();
public Guid Blog1Post1Comment1Id { get; } = Guid.NewGuid();
public Guid Blog1Post1Comment2Id { get; } = Guid.NewGuid();
public string Tag1Name { get; } = "Tag1Name";
public string Tag2Name { get; } = "Tag2Name";
}
public class CoreTestDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly CoreTestData _testData;
private readonly IBlogRepository _blogRepository;
private readonly IPostRepository _postRepository;
private readonly ICommentRepository _commentRepository;
private readonly ITagRepository _tagRepository;
private readonly ICurrentTenant _currentTenant;
public CoreTestDataSeedContributor(
CoreTestData testData,
IBlogRepository blogRepository,
IPostRepository postRepository,
ICommentRepository commentRepository,
ITagRepository tagRepository,
ICurrentTenant currentTenant)
{
_testData = testData;
_blogRepository = blogRepository;
_postRepository = postRepository;
_commentRepository = commentRepository;
_tagRepository = tagRepository;
_currentTenant = currentTenant;
}
public async Task SeedAsync(DataSeedContext context)
{
/* Seed additional test data... */
using (_currentTenant.Change(context?.TenantId))
{
await SeedBlogsAsync();
await SeedPostsAsync();
await SeedCommentsAsync();
await SeedTagsAsync();
}
}
private async Task SeedBlogsAsync()
{
await _blogRepository.InsertAsync(new BlogCore.Blogs.Blog(_testData.Blog1Id, "The First Blog", "blog-1"));
}
private async Task SeedPostsAsync()
{
await _postRepository.InsertAsync(new Post(_testData.Blog1Post1Id, _testData.Blog1Id, "title", "coverImage", "url"));
await _postRepository.InsertAsync(new Post(_testData.Blog1Post2Id, _testData.Blog1Id, "title2", "coverImage2", "url2"));
}
public async Task SeedCommentsAsync()
{
await _commentRepository.InsertAsync(new Comment(_testData.Blog1Post1Comment1Id, _testData.Blog1Post1Id, null, "text"));
await _commentRepository.InsertAsync(new Comment(_testData.Blog1Post1Comment2Id, _testData.Blog1Post1Id, _testData.Blog1Post1Comment1Id, "text"));
}
public async Task SeedTagsAsync()
{
await _tagRepository.InsertAsync(new Tag(Guid.NewGuid(), _testData.Blog1Id, _testData.Tag1Name, 10));
await _tagRepository.InsertAsync(new Tag(Guid.NewGuid(), _testData.Blog1Id, _testData.Tag2Name));
}
}
小插曲
开头我说中间出了点问题,这里我说一下,首先是我有2个接口名字写错了,Resharper应该自动帮我加Async后缀,但是有2个接口没有很奇怪。
另一个问题之前我们开发的时候在用户操作上我们调用的UserLookupService<IdentityUsers>
,这个用法是错误的,UserLookupService
是基于IUser的一个扩展操作类public abstract class UserLookupService<TUser, TUserRepository>
,我们应该集成IUser然后继承它来写,代码我也补充上了,如果你是从第一章开始跟这个小插曲正好可以让你重新复习一遍开发流程也挺好的,但是如果不是单元测试我还真没发现这个坑(我也是第一次用UserLookupService 之前都是直接调用IdentityUserManager),所以单元测试还是非常重要的。
完结撒花
最后撒花,整个基础篇系列到此应该就结束了,从框架介绍到功能开发到测试,我们全方位对ABP进行了一次使用教学,虽然很多知识点没有用到,但是整篇文章操作下来,入门肯定是ok了。
后面我会开始对ABP独立的知识点进行讲解其中会涉及源码应用场景等多种案例,目前的安排是模块化->授权->eventbus->考虑中 如果你对那块知识点感兴趣可以联系我,大家一起边写边学。
联系作者:加群:867095512 @MrChuJiu
项目源码地址:https://github.com/BaseCoreVueProject/ABPvNext.Blog.Core
十一、Abp vNext 基础篇丨测试的更多相关文章
- 六、Abp vNext 基础篇丨文章聚合功能上
介绍 9月开篇讲,前面几章群里已经有几个小伙伴跟着做了一遍了,遇到的问题和疑惑也都在群里反馈和解决好了,9月咱们保持保持更新.争取10月份更新完基础篇. 另外番外篇属于 我在abp群里和日常开发的问题 ...
- Abp vNext 基础篇丨分层架构
介绍 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解. 领域驱动设计 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与 ...
- 七、Abp vNext 基础篇丨文章聚合功能下
介绍 不好意思这篇文章应该早点更新的,这几天在忙CICD的东西没顾得上,等后面整好了CICD我也发2篇文章讲讲,咱们进入正题,这一章来补全剩下的 2个接口和将文章聚合进行完善. 开工 上一章大部分业务 ...
- Abp vNext 基础篇丨领域构建
介绍 我们将通过例⼦介绍和解释⼀些显式规则.在实现领域驱动设计时,应该遵循这些规则并将其应⽤到解决⽅案中. 领域划分 首先我们先对比下Blog.Core和本次重构设计上的偏差,可以看到多了一个博客管理 ...
- 五、Abp vNext 基础篇丨博客聚合功能
介绍 业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大. 开工 应用层 根据第三章分层架构里面讲到的现在我们模型 ...
- 八、Abp vNext 基础篇丨标签聚合功能
介绍 本章节先来把上一章漏掉的上传文件处理下,然后实现Tag功能. 上传文件 上传文件其实不含在任何一个聚合中,它属于一个独立的辅助性功能,先把抽象接口定义一下,在Bcvp.Blog.Core.App ...
- 十、Abp vNext 基础篇丨权限
介绍 本章节来把接口的权限加一下 权限配置和使用 官方地址:https://docs.abp.io/en/abp/latest/Authorization 下面这种代码可能我们日常开发都写过,ASP. ...
- 九、Abp vNext 基础篇丨评论聚合功能
介绍 评论本来是要放到标签里面去讲的,但是因为上一章东西有点多了,我就没放进去,这一章单独拿出来,内容不多大家自己写写就可以,也算是对前面讲解的一个小练习吧. 相关注释我也加在代码上面了,大家看看代码 ...
- Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计
说明 Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲 缘起 本篇文章缘起 ...
随机推荐
- Hexo搭建个人静态博客网站
前言 前段时间博客园整改,许多博客无法查看,偶然的机会接触到了许多博客框架,可用来快速搭建一个静态博客网站:最后选择使用hexo,看了不少大佬的教程,觉得挺有意思的,于是也总结了一下自己的搭建步骤,可 ...
- Django图片上传和前端展示
1 - 模型 class Book(models.Model): #定义图书模型 book_name = models.CharField(max_length=100,verbose_name='书 ...
- 【曹工杂谈】Maven底层容器Plexus Container的前世今生,一代芳华终落幕
Maven底层容器Plexus Container的前世今生,一代芳华终落幕 前言 说实话,我非常地纠结,大家平时只是用Maven,对于内部的实现其实也不关心,我现在非要拉着大家给大家讲.这就有个问题 ...
- C语言中volatile、register、const、static、extern、 auto关键字的作用
一.volatile详解 volatile的本意是"易变的" 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据.当要求使用volat ...
- 记录一下Vray5中文汉化版本中导出EXR或vrimg多通道文件的那些坑和解决方法
最近在给一个培训机构代课,学生英语基础差,就安装了Vray5的中文版,噩梦从此开始. 做过合成的都知道,需要输出多通道到NUKE或者AE中进行合成,通常情况下把多个pass分成不同的文件对硬盘反复读写 ...
- Vue Abp vNext获取当前登录用户
系统默认提供了获取当前用户的api方法 https://localhost:44364/api/identity/my-profile 手工实现方法:abp后台获取当前用户需要在AppService应 ...
- JDK7&JDK9处理异常新特性
1.JDK7新特性是在 try (定义对象,作用域就是try方法体) 复制一个文件实例: 复制文件的原理: 先从硬盘写出到内存中,创建文件输入流对象 FileInputStream fis; 中间是在 ...
- Linux上项目部署在home目录中无法访问的问题
在Linux上开发一个Web项目,使用nginx作为Web服务器.在nginx的配置文件中添加一个server,root路径写的是放在home目录中的项目目录的路径.打开浏览器访问,提示错误:403 ...
- PyCharm--帮助文档
PyCharm官方文档翻译 PyCharm快捷键
- 【分布式微服务企业快速架构】SpringCloud分布式、微服务、云架构快速开发平台源码
鸿鹄云架构[系统管理平台]是一个大型 企业.分布式.微服务.云架构的JavaEE体系快速研发平台,基于 模块化.微服务化.原子化.热部署的设计思想,使用成熟领先的无商业限制的主流开源技术 (Sprin ...