关于单元测试的思考--Asp.Net Core单元测试最佳实践
在我们码字过程中,单元测试是必不可少的。但在从业过程中,很多开发者却对单元测试望而却步。有些时候并不是不想写,而是常常会碰到下面这些问题,让开发者放下了码字的脚步:
- 这个类初始数据太麻烦,你看:new MyService(new User("test",1), new MyDAO(new Connection(......)),new ToManyPropsClass(......) .....) 。我:。。。
- 这个代码内部逻辑都是和Cookie有关,我单元测试不好整啊,还是得启动到浏览器里一个按钮一个按钮点。
- 这个代码内部读了配置文件,单元测试也不能给我整个配置文件啊?
- 这个代码主要是验证WebAPI入口得模型绑定,必须得调用一次啊?
这些问题确实存在,但它们阻止不了我们那颗要写单元测试的心。单元测试的优点很多,你或许可以不管。但至少能让你从那些需要在浏览器里点击10多下的操作里解脱出来。本文从一个简单的逻辑测试出发,慢慢拉开测试的大幕,让你爱上测试。文章主要是传播一些单元测试的理念,其次才是介绍asp.net core中的单元测试。
本文使用的环境为asp.net core 2.1 webapi,代码可以直接下载:https://github.com/yubaolee/DotNetCoreUnitTestSamples 为了方便阅读,以一个最简单的逻辑为例:
public class UserService{
public bool CheckLogin(UserInfo user)
{
return user.Name == user.Password; //登录逻辑,为了看着舒服,少点
}
}
public class UserInfo{
public string Name { get; set; }
public string Password { get; set; }
}
测试的WebAPI控制器如下:
public class ValuesController : ControllerBase
{
private UserService _service; public ValuesController(UserService service)
{
_service = service;
} [HttpGet]
[Route("checklogin")]
public bool CheckLogin([FromQuery]UserInfo user)
{
return _service.CheckLogin(user);
}
}
都已准备完毕,那么,开始我们的表演吧:
普通业务的单元测试
public class TestService
{
private UserService _service; [SetUp]
public void Init()
{
var server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
_service = server.Host.Services.GetService<UserService>();
}
[Test]
public void TestLogin()
{
bool result = _service.CheckLogin(new UserInfo { Name = "yubao", Password = "yubao" });
Assert.IsTrue(result);
}
}
在做业务测试过程中要善于使用注入功能,而不是使用new对象的方式,比如这里的Host.Services.GetService,防止出现new MyService(new User("test",1), new MyDAO(new Connection(......)),new ToManyPropsClass(......) .....)这种尴尬。用的越多你就越能体会这种做法的好处。我在openauth.net中使用的是autofac的AutofacServiceProvider。
测试Controller
很多时候我们需要测试顶层的controller(八成是controller里混的有业务逻辑)。这时我们可以快速的写出下面的测试代码:
public class TestController
{
private ValuesController _controller; [SetUp]
public void Init()
{
var server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
_controller = server.Host.Services.GetService<ValuesController>();
}
[Test]
public void TestLogin()
{
bool result = _controller.CheckLogin(new UserInfo{Name = "yubao",Password = "yubao"});
Assert.IsTrue(result);
}
}
这段代码在JAVA spring mvc框架下是没有问题的,但在asp.net core 中,你会发现:

获取不到controller?spring mvc的理念就是万物皆服务,哪怕是一个controller也是一个普通的服务。但微软不喜欢这样,默认时它要掌控controller的生死(The Subtle Perils of Controller Dependency Injection in ASP.NET Core MVC 有人在声讨微软了)。所以我们不能通过普通的ServicCollection来注入和获取它,除非你指明Controller As Service,如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
这时即可顺利测试通过。
测试含有HTTP上下文的业务逻辑,比如Cookie、URL中的QueryString
在平时的代码过程中,常常会和HTTP上下文HttpContext打交道,最常见的如request、response、cookie、querystring等,比如我们新的逻辑:
public class UserService
{
private IHttpContextAccessor _httpContextAccessor; public UserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
} public bool IsLogin()
{
return _httpContextAccessor.HttpContext.Request.Cookies["username"] != null;
}
}
这时如何测试呢?马丁福勒在他的大作《企业应用架构模式》中明确指出“测试桩”的概念,来应对这种情况。各种Mock框架应运而生。比如我最喜欢的Moq:
public class TestCookie
{
private UserService _service; [SetUp]
public void Init()
{
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
httpContextAccessorMock.Setup(x => x.HttpContext.Request.Cookies["username"]).Returns("yubaolee"); var server = new TestServer(WebHost.CreateDefaultBuilder()
.ConfigureServices(u =>u.AddScoped(x =>httpContextAccessorMock.Object))
.UseStartup<Startup>());
_service = server.Host.Services.GetService<UserService>();
}
[Test]
public void TestLogin()
{
bool result = _service.IsLogin();
Assert.IsTrue(result);
}
}
测试一次HTTP请求
有时我们需要测试Mvc框架的模型绑定,看看一次客户端的请求是否能被正确解析,亦或者测试WebAPI入口的一些Filter AOP等是否被正确触发,这时就需要测试一次HTTP请求。从严格意义上来讲这种测试已经脱离的单元测试的范畴,属于集成测试。但这种测试代码可以节省我们大量的重复劳动。asp.net core中可以通过TestServer快速实现这种模拟:
public class TestHttpRequest
{
private TestServer _testServer; [SetUp]
public void Init()
{
_testServer = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());
}
[Test]
public void TestLogin()
{
var client = _testServer.CreateClient();
var result = client.GetStringAsync("/api/values/checklogin?name=yubao&password=yubao");
Console.WriteLine(result.Result);
}
}
在进行单元测试的过程中,测试的理念(或者TDD的思维?)异常重要,它能帮助你构建和谐优美的代码。
G
关于单元测试的思考--Asp.Net Core单元测试最佳实践的更多相关文章
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- Asp.Net Core 单元测试正确姿势
背景 ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,并且默认注入了很多服务,具体可以参考 官方文档, 相信只要使用过依赖注入框架的同学,都会对此有不同深入的理解,在此无需赘言. ...
- 使用 xUnit 编写 ASP.NET Core 单元测试
还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊..NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP ...
- 基于Jenkins Pipeline的ASP.NET Core持续集成实践
最近在公司实践持续集成,使用到了Jenkins的Pipeline来提高团队基于ASP.NET Core API服务的集成与部署效率,因此这里总结一下. 一.关于持续集成与Jenkins Pipelin ...
- ASP.NET Core MVC TagHelper实践HighchartsNET快速图表控件-开源
ASP.NET Core MVC TagHelper最佳实践HighchartsNET快速图表控件支持ASP.NET Core. 曾经在WebForms上写过 HighchartsNET快速图表控件- ...
- asp.net core的docker实践
如果centos中没有安装和docker和.net core镜像,先安装docker和asp.net core 镜像 安装dockeryum -y install docker-io 启动 Docke ...
- 使用Docker部署ASP.NET Core应用程序实践
前言 最近把很火的Docker给看了,于是就磨拳擦掌要去实践一下.于是就拿之前一个aps.net core的项目(已被停止)去练手.该项目之前在ubuntu14.04上确保可以正常运行,所以docke ...
- Dependency injection in .NET Core的最佳实践
我们知道依赖注入(DI)是一种实现对象及其协作者或依赖关系之间松散耦合的技术. ASP.NET Core包含一个简单的内建容器来支持构造器注入. 我们试图将DI的最佳实践带到.NET Core应用程序 ...
- [转]ASP.NET MVC 4 最佳实践宝典
原文:http://www.cnblogs.com/sonykings/archive/2013/05/30/3107531.html ASP.NET MVC最佳实践 本文档提供了一套旨在帮助创建最佳 ...
随机推荐
- DOS系统常用命令
前言: DOS命令是DOS操作系统使用的命令.DOS操作系统是一种磁盘操作系统,从Windows95.98到今天的Windows10都内置有DOS操作系统.可以通过"win+R", ...
- web网络协议
一.OSI七层模型 OSI参考模型是国际标准化组织ISO(International Standards Organization )制定的模型,把计算机与计算机之间的通信分成七个互相连接的协议层 ...
- JSON数据格式转换
json格式 (JavaScipt Object Notation):是一种数据交换格式!json语法规则:01.对象表现形式 key:value 键值对02.如果有多个数据,之间使用逗号隔开 k1: ...
- [SQL Server]用 C# 在 LinqPad 建立 Linked Server 跨服务器数据库操作
在涉及老项目数据迁移的时候,数据库结构已经完全发生变化,而且需要对老数据进行特殊字段的处理,而且数据量较大,使用Navicat导出单表之后,一个表数据大概在100多万的样子,直接导出SQL执行根本行不 ...
- QM3_Statistics Concepts and Market Returns
Basic Concepts Terms Descriptive Statistics Describes the important aspects of large data sets. 统计 概 ...
- Java 读书笔记 (六) 引用类型
Java里使用long类型的数据要在数值后面加上L,否则会作为整型解析. 引用类型 引用类型是一个对象类型,它的值是指向内存空间的引用,就是地址, 所指向的内存中保存着变量所表示的一个值或一组值. i ...
- SQL2008全部数据导出导入两种方法
方法一:生成脚本导出导入sql2008全部数据第一步,右键要导出的数据库,任务--生成脚本 第二步,在设置脚本编写选项处,点击--高级(A),选择要编写脚本的数据的类型为:架构和数据 如果找不到 要编 ...
- get.go
//获取空间文件 )) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err ...
- python九九
学了有一段时间了,才发现自己连九九乘法表都写不出,好好笑,哈哈. 代码实现: for i in range(1,10): for j in range(1,i+1): print('%dx%d=%-2 ...
- Linux下可以ping ip地址但无法ping域名解决方法
分析:当前系统无法解决域名至ip地址故障. 步骤阅读 2 三:解决过程: 1.分析dns故障: 2.物理机可以ping 地址,但无法ping域名: 3.检查/etc/resolv.conf: 注: ( ...