关于单元测试的思考--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最佳实践 本文档提供了一套旨在帮助创建最佳 ...
随机推荐
- Windows下安装配置go
基于 go 1.9.2 + liteIDE + windows10 先安装 go 安装包,作用如同安装框架. 默认安装路径是 C:\Go,通常都会更改,但这会影响到后面设置环境变量,因此更改到哪里请记 ...
- 阿里云oss挂载到linux本地文件系统
对象存储 OSS 阿里云对象存储服务 (OSS) 是一种高度可伸缩且安全可靠的云对象存储服务,让您可以存储.备份和归档大量数据.阿里云 OSS 是一种简单易用的服务,让您每秒能处理数百万请求,它还支持 ...
- SVN使用教程2017.10.6
http://www.cnblogs.com/mq0036/p/5250198.html
- 优雅地实现CSS Animation delay心得
话不多说直接开讲: 1.需求: 等待元素A的动画加载完,再加载B元素的动画(下图中A为大熊猫,B为下方卡片) 先来看下最后的效果啦: 2.初始思路: 在B元素的动画属性上加上delay(延迟,使得这个 ...
- yii2.0 集成/引入第三方sdk
首先下载自己要使用的sdk包放到vendor文件夹下面:我以接入ping++为例子如下: 然后在入口文件出引入文件的配置文件: 下面就是在控制器使用了: 下面就可以根据自己要使用的的文件以及方法正常调 ...
- RabbitMQ 集群与网络分区(理论知识)
关于network partition网络设备故障导致的网络分裂.比如,存在A\B\C\D\E五个节点,A\B处于同一子网,B\C\D处于另外一子网,中间通过交换机相连.若两个子网间的交换机故障了即发 ...
- BZOJ_4003_[JLOI2015]城池攻占_可并堆
BZOJ_4003_[JLOI2015]城池攻占_可并堆 Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 ...
- selenium处理iframe定位于切换问题解决办法
首先还是围绕以下几个方面来看: 1.什么是iframe? 2.为什么我们要定位iframe? 3.我们怎样定位iframe,与切换iframe? 1.什么是iframe? ♦ b/s架构都使用ifra ...
- JAVA基础第三章-类与对象、抽象类、接口
业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...
- 代码转换为html显示
需要将代码转换为 html 使其显示好看一些,可以在这里进行装换: https://tohtml.com/ http://hilite.me/