首次在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 ...
随机推荐
- vue中手写table的升降序
有些时候,我们总是无可避免的需要自己去手撸一些东西,因为需求总是在不断的变化.例如,最开始的需求,我们只是在首页展示一个数据列表,此时,我们可能直接就自己手写了一个table,后来,突然增加了一个需求 ...
- 09-Python模块
导入模块 通过import导入模块 import time #导入模块time time.sleep(50) #睡眠50s 导入模块并重命名 import time as t #导入模块time重命名 ...
- 防止unordered_map 被卡方法
codeforces 上看到的,mark 一下代码.原作者:neal,原链接:https://codeforces.com/blog/entry/62393 struct custom_hash { ...
- NB-IoT,LoRA,WIFI,蓝牙,Zigbee,MQTT,CoAP之间的关系
--- title: file_name date: 2020-06-22 07:26:20 categories: tags: - iot - wifi - Bluetooth - MQTT - c ...
- Spring注解之依赖注入@Autowired和@Resource
Spring常见的DI方式 字段注入(Field Injection) 在字段上使用@Autowired/Resource注解 字段注入是日常开发中使用最多的一种注入方式,它的实现代码如下: @Aut ...
- yb课堂 ECMAScript 6常见语法快速入门 《三十一》
什么是ES 6 ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015).它的目标是使用JavaScri ...
- 推荐一款Python接口自动化测试数据提取分析神器!
1.引言 在处理JSON数据时,我们常常需要提取.筛选或者变换数据.手动编写这些操作的代码不仅繁琐,而且容易出错.Python作为一个功能强大的编程语言,拥有丰富的库和工具来处理这些数据.今天,将介绍 ...
- 解决方案 | onenote无法同步,显示:证书错误,应用程序在加载SSL库是遇到内部错误。
解决方案:一般是公司网络或者学校网络的问题,更换手机使用的数据流量热点无线网络即可.
- 网易数帆开源贡献获业界肯定,轻舟API网关获OSCAR尖峰开源技术创新奖
2020年10月16日,由中国信息通信研究院主办的"2020开源产业大会"在北京线下与线上同步召开,主办方在会上公布了"OSCAR尖峰开源奖项"各个奖项的评选结 ...
- 第五节 JMeter基础-初级登录【断言的好处】
声明:本文所记录的仅本次操作学习到的知识点,其中商城IP错误,请自行更改. 1.认识JMeter (1)断言 预期结果和实际结果的比较,如果不一样,断言失败. 2.注册 (1)直接复制[登录]粘贴一下 ...