首页 技术 正文
技术 2022年11月19日
0 收藏 723 点赞 4,659 浏览 4054 个字

在我们码字过程中,单元测试是必不可少的。但在从业过程中,很多开发者却对单元测试望而却步。有些时候并不是不想写,而是常常会碰到下面这些问题,让开发者放下了码字的脚步:

  1. 这个类初始数据太麻烦,你看:new MyService(new User(“test”,1), new MyDAO(new Connection(……)),new ToManyPropsClass(……) …..) 。我:。。。
  2. 这个代码内部逻辑都是和Cookie有关,我单元测试不好整啊,还是得启动到浏览器里一个按钮一个按钮点。
  3. 这个代码内部读了配置文件,单元测试也不能给我整个配置文件啊?
  4. 这个代码主要是验证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 中,你会发现:

关于单元测试的思考–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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,105
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,582
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,429
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,200
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,836
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,919