首次在WebAPI中写单元测试
xUnit
这次我使用的是xUnit测试框架,而不是VS自带的MSTest框架。在添加新建项目时选择xUnit测试项目就行了。
目前只体验到了一个差别,即xUnit可以使用特性向测试方法传参,而不用在测试方法中一个赋值语句一个个去定义参数,这是比较方便的。
单元测试有一个好处,就是一次性可以获得所测试的很多接口的失败信息。如果使用swagger去测试接口,只能去启动项目,输入密码鉴权,然后一个个发请求。遇到一个错误处理一个接口,比较麻烦。单元测试可以把所有接口的报错信息展示在测试窗口,而且是缓存的,不用启动项目,只需要点击一下测试按钮,就把所有接口测试了。我在迁移接口时,有一个控制器一次性迁移了14个接口,单元测试通过了6个,失败了8个,失败的都列出了错误消息,这就很舒服了。经过了几天的使用,我发现这比到swagger或postman中手动测试接口方便太多了。

这里的失败基本都是迁移数据库结构引起的,我申请修改结构后,就又有几个通过了测试。修改进度如何,看起来很直观。到目前为止几天了,测试仍然没有全部通过,单元测试起到了很好的监控作用。

点击失败的测试,可以看到调用堆栈,跳转到运行失败的那一行代码。这使得修改起来很方便。
单元测试环境准备
我写的是控制器方法中action的单元测试。但是一般来说,控制器和Service层会注入许多服务,而action依赖于这些服务。在使用依赖注入时,单元测试要如何处理这种情况?
可以和ASP.NET core的做法一样。它准备了一个依赖注入容器,那我也准备一个依赖注入容器。WebAPI还构造了一个web主机。但是单元测试是独立运行的,就不需要创建一个web主机了。在单元测试项目种添加了一个TestBase基类,用于创建容器,注册服务,以供测试方法使用。
//测试环境
public class TestBase
{
//依赖注入容器
public IServiceCollection Services;
//从容器获取服务
public IServiceProvider Provider;
public TestBase()
{
//创建容器
Services = new ServiceCollection();
//....注册服务
Provider = Services.BuildServiceProvider();
}
}
然后向容器注册我们需要的服务,比如常见的MemoryCache IWebHostEnvironment ISqlSugarClient XXXService。我们就不需要在测试方法中使用new运算符创建服务类实例,而是可以直接从容器中获得了。
//注册服务层
Services.AddScoped<ISingleWellService, SingleWellService>();
//注册缓存
Services.AddScoped<IMemoryCache, MemoryCache>(service =>
{
return new MemoryCache(new MemoryCacheOptions());
});
//注册SqlSuger
Services.AddScoped<ISqlSugarClient>(service =>
{
return new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = "Data Source=XXX",
DbType = DbType.Oracle,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute,
});
});
//注册环境变量
Services.AddScoped<IWebHostEnvironment, WebHostEnvironment>();
IWebHostEnvironment
为了使用这个接口需要引入包Microsoft.Extensions.DependencyInjection.Abstractions。这个接口一般是WebApplicationBuilder创建的,在涉及文件读写时经常用到。但是单元测试项目中没有builder,我就自己添加了一个IWebHostEnvironment实现类,并注册到容器中。缺点是还要把可能需要的WebAPI项目中的文件,比如模板文件、数据文件也复制到单元测试项目中。
public class WebHostEnvironment : IWebHostEnvironment
{
public string WebRootPath { get => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); }
public IFileProvider WebRootFileProvider { get; set; }
public string EnvironmentName { get; set; }
public string ApplicationName { get; set; }
public string ContentRootPath { get => AppDomain.CurrentDomain.BaseDirectory; }
public IFileProvider ContentRootFileProvider { get; set; }
}
ICurentUser
这个自定义接口一般是在请求处理管道中存储身份验证后的相关信息。单元测试中不同的接口可能需要不同的user,比如一个流程中,不同角色调用同一个接口。ICurentUser同样也是注册到容器中的,然后在service层注入。具体的业务方法中根据这个角色的不同执行不同逻辑。
我比较疑惑的是,单元测试又该怎么注入呢?要注意的是,不同测试方法的ICurentUser是不同的。要知道从容器中获取service,容器自动帮我们挑选了合适够构造方法。但是这里由于角色的不同,不能直接从容器获取准备好的角色存根。难道我们要手动构造service传入controller中吗?我是有听说mokq,但不知道怎么用来模拟多个ICurentUser。
为控制器添加单元测试
添加一个HomeControllerUnitTest类,并继承于前面定义的基类TestBase。我们应该在构造函数中从容器取出相应的服务以供使用。
public class HomeControllerUnitTest:TestBase
{
HomeController homeController;
public HomeControllerUnitTest()
{
//从容器注入服务
homeController = new HomeController(
Provider.GetRequiredService<IHomeService>(),
Provider.GetRequiredService<IWebHostEnvironment>());
}
}
接着,向测试类添加单元Action的测试方法。一般都是三步
- 准备数据 Arrange
- 调用测试方法 Act
- 断言结果 Assert
也就是AAA模式。
[Theory(DisplayName = "测试XXX")]
[InlineData("xxx", "xxx", 1,30)]
public void Test_GetData(string wId, string tId, int page, int rows)
{
var data = homeController.GetData(wId, tId, page, rows).Result;
Assert.True(data.Tag);
}
在实际使用时,我会每添加一个测试,就运行未运行的测试。头一天看一下哪些测试没通过,这里一般是数据库结构不对,然后申请修改数据库。第二天再运行失败的测试验证。不会每次运行全部测试。
首次在WebAPI中写单元测试的更多相关文章
- 【快学springboot】在springboot中写单元测试[Happyjava]
前言 很多公司都有写单元测试的硬性要求,在提交代码的时候,如果单测通不过或者说单元测试各种覆盖率不达标,会被拒绝合并代码.写单元测试,也是保证代码质量的一种方式. junit单元测试 相信绝大多数的J ...
- 【快学springboot】在springboot中写单元测试
前言 很多公司都有写单元测试的硬性要求,在提交代码的时候,如果单测通不过或者说单元测试各种覆盖率不达标,会被拒绝合并代码.写单元测试,也是保证代码质量的一种方式. junit单元测试 相信绝大多数的J ...
- 在Nodejs中贯彻单元测试
在团队合作中,你写好了一个函数,供队友使用,跑去跟你的队友说,你传个A值进去,他就会返回B结果了.过了一会,你队友跑过来说,我传个A值却返回C结果,怎么回事?你丫的有没有测试过啊? 大家一起写个项目, ...
- TDD中的单元测试写多少才够?
测试驱动开发(TDD)已经是耳熟能详的名词,既然是测试驱动,那么测试用例代码就要写在开发代码的前面.但是如何写测试用例?写多少测试用例才够?我想大家在实际的操作过程都会产生这样的疑问. 3月15日,我 ...
- Autofac - MVC/WebApi中的应用
Autofac前面写了那么多篇, 其实就是为了今天这一篇, Autofac在MVC和WebApi中的应用. 一.目录结构 先看一下我的目录结构吧, 搭了个非常简单的架构, IOC(web), IBLL ...
- 在asp.net WebAPI 中 使用Forms认证和ModelValidata(模型验证)
一.Forms认证 1.在webapi项目中启用Forms认证 Why:为什么要在WebAPI中使用Forms认证?因为其它项目使用的是Forms认证. What:什么是Forms认证?它在WebAP ...
- 在WebAPI中自动创建Controller
在MIS系统中,大部分的操作都是基本的CRUD,并且这样的Controller非常多. 为了复用代码,我们常常写一个泛型的基类. public class EntityController<T& ...
- webApi中参数传递
webApi中参数传递 一:无参数的get方法: 前端: function GetNoParam() { //为了统一:我们都采用$.ajax({}) 方法; $.ajax({ url: '/a ...
- 在Android Studio中进行单元测试和UI测试
本篇教程翻译自Google I/O 2015中关于测试的codelab,掌握科学上网的同学请点击这里阅读:Unit and UI Testing in Android Studio.能力有限,如有翻译 ...
- Visual Studio 中的单元测试 UNIT TEST
原文:Visual Studio 中的单元测试 UNIT TEST 注:本文系作者原创,可随意转载,但请注明出处.如实在不愿注明可留空,强烈反对更改原创出处.TDD(Test-Driven Devel ...
随机推荐
- 详解Web应用安全系列(2)注入漏洞之XSS攻击
上一篇介绍了SQL注入漏洞,今天我们来介绍另一个注入漏洞,即XSS跨站脚本攻击.XSS 全称(Cross Site Scripting) 跨站脚本攻击, 是Web应用中常见的漏洞.指攻击者在网页中嵌入 ...
- js-文件读写和上传下载的简单例子01
现下,网络越来越快,浏览器的功能和性能越来越好,所以很多时候,已经不需要一些复杂的框架来实现不是非常复杂的功能. 我们只有在以下情况才会考虑使用框架或者现成的第三方组件: 1.功能复杂,自己写没有必要 ...
- Unicode 和JS中的字符串
计算机内部使用二进制存储数据,只认识0和1两个数字,计算机的世界只有0和1.但我们的世界却充满着文字,如a, b, c.怎样才能让计算机显示文字,供我们使用和交流?只能先把文字转化成数字进行存储,然后 ...
- python学习_PIL的Image模块初步使用
基本介绍: Pillow 是 Python 中较为基础的图像处理库,主要用于图像的基本处理,比如裁剪图像.调整图像大小和图像颜色处理等.与 Pillow 相比,OpenCV 和 Scikit-imag ...
- SpringBoot集成Mongodb文档数据库
添加Maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId& ...
- Servlet之Request和Response的快速上手
阅读提示: 前置内容 MyBatis知识点总结 HTTP和Servlet入门 目录 1.Request和Response概述 2.Request对象 2.1 Request继承体系 2.2 Reque ...
- Aspose Excel 单元格合并后边框显示不全
/// <summary> /// 解决合并后的单元格没有边框,设置合并单元格格式,让合并过的单元格中每一个单元格上都添加上加边框的样式 /// </summary> /// ...
- Eggjs 设置跨域请求 指定地址跨域 nodejs
首先egg自身框架没有直接设置允许跨域请求的功能和接口,所以需要第三方包来设置跨域请求! 先安装第三方包来设置跨域,使用egg-cors // npm npm i egg-cors --save // ...
- 通俗讲解promise
JavaScript 中的 Promise 是一种特殊的对象,它用于解决异步编程中的复杂性问题,特别是回调的问题.我们可以把它比喻成现实生活中的一个"承诺": 想象一下,你 ...
- 妙趣横生:利用Echarts实现SpreadJS引用从属关系的可视化魅力
最新技术资源(建议收藏) https://www.grapecity.com.cn/resources/ 在金融行业,我们经常会有审计审查的需求,对某个计算结果进行审查,但是这个计算结果可能依赖多个单 ...