关于单元测试的思考--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最佳实践 本文档提供了一套旨在帮助创建最佳 ...
随机推荐
- 超实用的JavaScript代码段 Item5 --图片滑动效果实现
先上图 鼠标滑过那张图,显示完整的哪张图,移除则复位: 简单的CSS加JS操作DOM实现: <!doctype html> <html> <head> <me ...
- python3[爬虫实战] 使用selenium,xpath爬取京东手机
使用selenium ,可能感觉用的并不是很深刻吧,可能是用scrapy用多了的缘故吧.不过selenium确实强大,很多反爬虫的都可以用selenium来解决掉吧. 思路: 入口: 关键字搜索入口 ...
- http2.0与http1.X的区别
此文只是方便重看,原文在:http://www.mamicode.com/info-detail-1199706.html 1.1 HTTP应用场景 http诞生之初主要是应用于web端内容获取,那时 ...
- 安装ipset车祸现场
基础环境centos6.8内核2.6.32. 本身服务器带的是6.11ipset版本,业务需求,需要升级到6.32 ipset-6.32 ./configure 报错 configure: error ...
- Python 文件读写的三种模式和区别
#coding=utf-8 #__author:Administrator #__time:2018/5/9 13:14 #__file_name:text1 import io #能调用方法的一定是 ...
- left join,right join,inner join,full join之间的区别
参考 https://www.cnblogs.com/assasion/p/7768931.html https://blog.csdn.net/rongbo_j/article/details/46 ...
- 【费马小定理】BZOJ3260 跳
Description 从(0,0)走到(n,m),没走过一个点(x,y)贡献为C(x,y),求最小贡献和. Solution 让我们guess一下 走的路线一定是先走长的一边再走短的一边,两条直线 ...
- 【强连通分量】Bzoj1051 HAOI2006 受欢迎的牛
Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认 ...
- 【线段树】Bzoj1798 [AHOI2009] 维护序列
Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2 ...
- .NET 创建 classlib时,netcoreapp2.0与netstandard2.0的区别
最近单位在开发一个新项目,在技术选型的时候,我们决定后台代码全部使用 dot net core来进行开发. 当项目引用公司之前的一个类库的时候,总是出现缺少XX组件的错误,所以我们检查了所有的类库,将 ...