前言

祝大家国庆快乐,本来想国庆之前更新完的,结果没写完,今天把剩下的代码补了一下总算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 基础篇丨测试的更多相关文章

  1. 六、Abp vNext 基础篇丨文章聚合功能上

    介绍 9月开篇讲,前面几章群里已经有几个小伙伴跟着做了一遍了,遇到的问题和疑惑也都在群里反馈和解决好了,9月咱们保持保持更新.争取10月份更新完基础篇. 另外番外篇属于 我在abp群里和日常开发的问题 ...

  2. Abp vNext 基础篇丨分层架构

    介绍 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解. 领域驱动设计 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与 ...

  3. 七、Abp vNext 基础篇丨文章聚合功能下

    介绍 不好意思这篇文章应该早点更新的,这几天在忙CICD的东西没顾得上,等后面整好了CICD我也发2篇文章讲讲,咱们进入正题,这一章来补全剩下的 2个接口和将文章聚合进行完善. 开工 上一章大部分业务 ...

  4. Abp vNext 基础篇丨领域构建

    介绍 我们将通过例⼦介绍和解释⼀些显式规则.在实现领域驱动设计时,应该遵循这些规则并将其应⽤到解决⽅案中. 领域划分 首先我们先对比下Blog.Core和本次重构设计上的偏差,可以看到多了一个博客管理 ...

  5. 五、Abp vNext 基础篇丨博客聚合功能

    介绍 业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大. 开工 应用层 根据第三章分层架构里面讲到的现在我们模型 ...

  6. 八、Abp vNext 基础篇丨标签聚合功能

    介绍 本章节先来把上一章漏掉的上传文件处理下,然后实现Tag功能. 上传文件 上传文件其实不含在任何一个聚合中,它属于一个独立的辅助性功能,先把抽象接口定义一下,在Bcvp.Blog.Core.App ...

  7. 十、Abp vNext 基础篇丨权限

    介绍 本章节来把接口的权限加一下 权限配置和使用 官方地址:https://docs.abp.io/en/abp/latest/Authorization 下面这种代码可能我们日常开发都写过,ASP. ...

  8. 九、Abp vNext 基础篇丨评论聚合功能

    介绍 评论本来是要放到标签里面去讲的,但是因为上一章东西有点多了,我就没放进去,这一章单独拿出来,内容不多大家自己写写就可以,也算是对前面讲解的一个小练习吧. 相关注释我也加在代码上面了,大家看看代码 ...

  9. Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计

    说明 Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲 缘起 本篇文章缘起 ...

随机推荐

  1. new[]/delete[]与new/delete区别

    new[]/delete[]与new/delete完全不同-动态对象数组创建通过new[] 完成-动态对象数组的销毁通过delete[]完成-new[]/delete[]能够被重载,进而改变内存管理方 ...

  2. Spring之JDBC Template

    时间:2017-2-5 18:16 --Spring对不同持久化技术的支持Spring为各种支持的持久化技术都提供了简单操作的模板和回调.ORM持久化技术:    JDBC:        org.s ...

  3. Java抽象类(abstract)

    抽象类和接口 一.抽象类 1.什么是抽象类? 类和类之间具有共同的特征,将这些特征提取出来,形成的就是抽象类. 类到对象是实例化,对象到类是抽象. 抽象类和具体类是相对的概念."抽象&quo ...

  4. 15-SpringCloud Stream

    Stream是什么及Binder介绍 官方文档1 官方文档2 Cloud Stream中文指导手册 什么是Spring Cloud Stream? 官方定义Spring Cloud Stream是一个 ...

  5. thinkphp5.x在函数禁用的情况下绕过

    描述 测试的时候发现一个thinkphp的网站,有tp5的漏洞但无法执行命令,但没机会进行后续测试,所有在这里自己搭建环境进行复现一下. 使用的是tp5.0.16 一开始使用网上的payload打一直 ...

  6. GoLang设计模式3 - 抽象工厂模式

    之前我们介绍了工厂设计模式,现在我们再看一下抽象工厂设计模式.抽象工程模式顾名思义就是对工厂模式的一层抽象,也是创建型模式的一种,通常用来创建一组存在相关性的对象. UML类图大致如下: 类图比较复杂 ...

  7. 7-31 堆栈操作合法性 (20 分) PTA

    7-31 堆栈操作合法性 (20 分)   假设以S和X分别表示入栈和出栈操作.如果根据一个仅由S和X构成的序列,对一个空堆栈进行操作,相应操作均可行(如没有出现删除时栈空)且最后状态也是栈空,则称该 ...

  8. DOM对象入门

    1.概念 2.script最好是放在后面,等html的文档内容加载完毕,不然获取不到 3.事件基本操作 第一种绑定事件html和js耦合度高,用第二种 4.灯开关事件使用

  9. 自定义cs程序安装界面

    http://www.crifan.com/achieve_fixed_install_path_while_install_software_when_make_msi_installer/ 制作C ...

  10. 从输入 URL 到展现页面的全过程

    总体分为以下几个过程 DNS解析 TCP连接 发送HTTP请求 服务器处理请求并返回HTTP报文 浏览器解析渲染页面 连接结束 DNS解析 域名到ip地址转换 TCP连接 HTTP连接是基于TCP连接 ...