首次在WebAPI中写单元测试

xUnit

这次我使用的是xUnit测试框架,而不是VS自带的MSTest框架。在添加新建项目时选择xUnit测试项目就行了。

目前只体验到了一个差别,即xUnit可以使用特性向测试方法传参,而不用在测试方法中一个赋值语句一个个去定义参数,这是比较方便的。

单元测试有一个好处,就是一次性可以获得所测试的很多接口的失败信息。如果使用swagger去测试接口,只能去启动项目,输入密码鉴权,然后一个个发请求。遇到一个错误处理一个接口,比较麻烦。单元测试可以把所有接口的报错信息展示在测试窗口,而且是缓存的,不用启动项目,只需要点击一下测试按钮,就把所有接口测试了。我在迁移接口时,有一个控制器一次性迁移了14个接口,单元测试通过了6个,失败了8个,失败的都列出了错误消息,这就很舒服了。经过了几天的使用,我发现这比到swagger或postman中手动测试接口方便太多了。

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

点击失败的测试,可以看到调用堆栈,跳转到运行失败的那一行代码。这使得修改起来很方便。

单元测试环境准备

我写的是控制器方法中action的单元测试。但是一般来说,控制器和Service层会注入许多服务,而action依赖于这些服务。在使用依赖注入时,单元测试要如何处理这种情况?

可以和ASP.NET core的做法一样。它准备了一个依赖注入容器,那我也准备一个依赖注入容器。WebAPI 还构造了一个web主机。但是单元测试是独立运行的,就不需要创建一个web主机了。在单元测试项目种添加了一个TestBase基类,用于创建容器,注册服务,以供测试方法使用。

csharp 复制代码
//测试环境
public class TestBase
{
	//依赖注入容器
	public IServiceCollection Services;
	//从容器获取服务
	public IServiceProvider Provider;

	public TestBase()
	{
		//创建容器
		Services = new ServiceCollection();
		//....注册服务
		Provider = Services.BuildServiceProvider();
	}
}

然后向容器注册我们需要的服务,比如常见的MemoryCache IWebHostEnvironment ISqlSugarClient XXXService。我们就不需要在测试方法中使用new运算符创建服务类实例,而是可以直接从容器中获得了。

csharp 复制代码
//注册服务层
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项目中的文件,比如模板文件、数据文件也复制到单元测试项目中。

csharp 复制代码
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。我们应该在构造函数中从容器取出相应的服务以供使用。

csharp 复制代码
public class HomeControllerUnitTest:TestBase
    {
        HomeController homeController;
        public HomeControllerUnitTest()
        {
			//从容器注入服务
            homeController = new HomeController(
				Provider.GetRequiredService<IHomeService>(), 
				Provider.GetRequiredService<IWebHostEnvironment>());
        }
}

接着,向测试类添加单元Action的测试方法。一般都是三步

  • 准备数据 Arrange
  • 调用测试方法 Act
  • 断言结果 Assert

也就是AAA模式。

csharp 复制代码
[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);
}

在实际使用时,我会每添加一个测试,就运行未运行的测试。头一天看一下哪些测试没通过,这里一般是数据库结构不对,然后申请修改数据库。第二天再运行失败的测试验证。不会每次运行全部测试。

相关推荐
王解14 小时前
Jest项目实战(4):将工具库顺利迁移到GitHub的完整指南
单元测试·github
Devil枫1 天前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
小袁在上班1 天前
Python 单元测试中的 Mocking 与 Stubbing:提高测试效率的关键技术
python·单元测试·log4j
测试19981 天前
外包干了2年,快要废了。。。
自动化测试·软件测试·python·面试·职场和发展·单元测试·压力测试
安冬的码畜日常1 天前
【The Art of Unit Testing 3_自学笔记06】3.4 + 3.5 单元测试核心技能之:函数式注入与模块化注入的解决方案简介
笔记·学习·单元测试·jest
王解1 天前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
程序员小雷2 天前
软件测试基础:单元测试与集成测试
python·功能测试·selenium·测试工具·单元测试·集成测试·压力测试
王解2 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
程序员雷叔2 天前
外包功能测试就干了4周,技术退步太明显了。。。。。
功能测试·测试工具·面试·职场和发展·单元测试·测试用例·postman
安冬的码畜日常2 天前
【玩转 Postman 接口测试与开发2_005】第六章:Postman 测试脚本的创建(上)
javascript·测试工具·单元测试·postman·bdd·chai