十一、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放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲 缘起 本篇文章缘起 ...
随机推荐
- 学ant(2)——path
1.path是ant内置的一种datatype,作用是声明路径之类的东西,在官方的manual中也叫做Path-like Structures,一般是这样声明的 <pathelement loc ...
- Powershell免杀从入门到实践
转载https://www.jianshu.com/p/fb078a99e0d8 前言 文章首发于Freebuf 在之前发布的一篇 渗透技巧之Powershell实战思路 中,学习了powershel ...
- k8s garbage collector分析(1)-启动分析
k8s garbage collector分析(1)-启动分析 garbage collector介绍 Kubernetes garbage collector即垃圾收集器,存在于kube-contr ...
- DataTable 增加、修改、删除
using System; using System.Data; using System.Windows.Forms; using DotNet.Utilities; namespace Windo ...
- JS006. 详解自执行函数原理与数据类型的快速转换 (声明语句、表达式、运算符剖析)
今天的主角: Operator Description 一元正值符 " + "(MDN) 一元运算符, 如果操作数在之前不是number,试图将其转换为number. 圆括号运算符 ...
- Java链表练习题小结
链表 链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).一个链表节点至少包含一个 数据域和 ...
- 路由懒加载---Vue Router
一.什么是懒加载? 懒加载也就是延迟加载或者按需加载,即在需要的时候进行加载. 二.为什么在Vue路由中使用懒加载? 像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常 ...
- Android系统编程入门系列之应用数据文件化保存
应用中关于数据的持久化保存,不管是简单的SharedPreferences还是数据库SQLiteDatabase,本质上都是将数据保存到系统的某种类型的文件中.因此可以直接使用java.io.File ...
- spark相关介绍-提取hive表(一)
本文环境说明 centos服务器 jupyter的scala核spylon-kernel spark-2.4.0 scala-2.11.12 hadoop-2.6.0 本文主要内容 spark读取hi ...
- # Zombie Gunship Survival(僵尸炮艇生存)GG修改器修改教程
Zombie Gunship Survival(僵尸炮艇生存)GG修改器修改教程 1.修改伤害,打击范围,武器冷却时间,子弹容量 测试手机机型:华为畅享7 系统版本:Android7.0 是否ROOT ...