zoukankan      html  css  js  c++  java
  • ASP.NET Core搭建多层网站架构【12-xUnit单元测试之集成测试】

    2020/02/01, ASP.NET Core 3.1, VS2019, xunit 2.4.1, Microsoft.AspNetCore.TestHost 3.1.1

    摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【12-xUnit单元测试之集成测试】
    使用xUnit借助TestServer进行集成测试,在单元测试中对WebApi的每个接口进行测试

    文章目录

    此分支项目代码

    本章节介绍了使用xUnit借助TestServer进行集成测试,在单元测试中对WebApi的每个接口进行测试

    新建单元测试

    在tests解决方案文件夹下新建xUnit单元测试,记得存放在实际tests路径下,取名WebApiTests

    添加包引用

    WebApiTests单元测试添加Microsoft.AspNetCore.TestHost包引用:

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.1" />
      <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
      <PackageReference Include="xunit" Version="2.4.1" />
      <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
      <PackageReference Include="coverlet.collector" Version="1.2.0">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
    </ItemGroup>
    

    添加Microsoft.AspNetCore.TestHost包(3.1.1)引用,其他默认的包升级到最新,具体版本参考上面
    WebApiTests单元测试添加对MS.WebApi的引用

    接口测试

    建立TestServerHost

    WebApiTests单元测试中添加TestHostBuild.cs类,这是整个集成测试的核心部分:

    using Autofac.Extensions.DependencyInjection;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Options;
    using MS.Component.Jwt;
    using MS.Component.Jwt.UserClaim;
    using MS.WebApi;
    using System.Net.Http;
    
    namespace WebApiTests
    {
        public static class TestHostBuild
        {
            public static readonly JwtService jwtService = new JwtService(Options.Create(new JwtSetting
            {
                Audience = "MS.Audience",
                Issuer = "MS.WebHost",
                LifeTime = 1440,
                SecurityKey = "MS.WebHost SecurityKey"//此处内容需和服务器appsettings.json中保持一致
            }));
            public static readonly UserData userData = new UserData
            {
                Account = "test",
                Email = "test@qq.com",
                Id = 1,
                Name = "测试用户",
                Phone = "123456789111",
                RoleDisplayName = "testuserRole",
                RoleName = "testuser"
            };//测试用户的数据,也可以改成真实的数据,看需求
    
            public static IHostBuilder GetTestHost()
            {
                //代码和网站Program中CreateHostBuilder代码很类似,去除了AddNlogService以免跑测试生成很多日志
                //如果网站并没有使用autofac替换原生DI容器,UseServiceProviderFactory这句话可以去除
                //关键是webBuilder中的UseTestServer,建立TestServer用于集成测试
                return new HostBuilder()
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())//替换autofac作为DI容器
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                    .UseTestServer()//关键时多了这一行建立TestServer
                    .UseStartup<Startup>();
                });
            }
            /// <summary>
            /// 生成带token的httpclient
            /// </summary>
            /// <param name="host"></param>
            /// <returns></returns>
            public static HttpClient GetTestClientWithToken(this IHost host)
            {
                var client = host.GetTestClient();
                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {GenerateToken()}");//把token加到Header中
                return client;
            }
    
            /// <summary>
            /// 生成jwt令牌
            /// </summary>
            /// <returns></returns>
            public static string GenerateToken()
            {
                return jwtService.BuildToken(jwtService.BuildClaims(userData));
            }
        }
    }
    
    • new了一个JwtService,用于生成token,其中JwtSetting的设置要和服务器保持一致(也可以直接从appsettings.json中读取)
    • new了一个UserData,用于测试的伪造数据,如果有需求也可以改成数据库中的真实数据
    • 其中GetTestHost方法中的代码和网站Program中CreateHostBuilder代码很类似
    • 去除了AddNlogService以免跑测试生成很多日志
    • 如果网站并没有使用autofac替换原生DI容器,UseServiceProviderFactory这句话可以去除
    • 关键是webBuilder中的UseTestServer,建立TestServer用于集成测试
    • 获得Host该怎么写,可以看官方的测试用例,以上的代码和之后的测试用例就是我参考官方的写出来的
    • GetTestClientWithToken方法就是获得一个带token的httpclient,基于GetTestClient方法而来的
    • 如有不需要带token的请求,也可以直接用GetTestClient方法

    构建接口返回值对象

    WebApiTests单元测试中添加ApiResult.cs类:

    namespace WebApiTests
    {
        public class ApiResult<T>
        {
            public int status { get; set; }
            public T data { get; set; }
        }
    }
    

    由于我们的接口返回值统一包装了一层,所以构建了ApiResult用于反序列化接口返回值对象

    编写接口的测试用例

    WebApiTests单元测试中添加RoleControllerTest.cs类,这是Role接口的测试用例:

    using Microsoft.AspNetCore.TestHost;
    using Microsoft.Extensions.Hosting;
    using MS.Common.Extensions;
    using MS.Entities;
    using MS.Models.ViewModel;
    using MS.WebCore.Core;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Xunit;
    
    namespace WebApiTests
    {
        public class RoleControllerTest
        {
            const string _testUrl = "/role/";
            const string _mediaType = "application/json";
            readonly Encoding _encoding = Encoding.UTF8;
    
            [Theory]
            [InlineData(1222538617050763264)]
            public async Task Delete_Id_ReturnResult(long id)
            {
                //arrange
                string url = $"{_testUrl}?id={id.ToString()}";// url:  /role/?id=11111111
                using var host = await TestHostBuild.GetTestHost().StartAsync();//启动TestServer
    
                //act
                var response = await host.GetTestClientWithToken().DeleteAsync(url);//调用Delete接口
                var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult>>();//获得返回结果并反序列化
    
                //assert
                Assert.Equal(result.data.IsSucceed, string.IsNullOrWhiteSpace(result.data.Message));
            }
            [Fact]
            public async Task Post_CreateRole_ReturnTrue()
            {
                //arrange
                RoleViewModel viewModel = new RoleViewModel
                {
                    Name = "RoleForPostTest",
                    DisplayName = "RoleForPostTest"
                };
                StringContent content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);//定义post传递的参数、编码和类型
                using var host = await TestHostBuild.GetTestHost().StartAsync();//启动TestServer
    
                //act
                var response = await host.GetTestClientWithToken().PostAsync(_testUrl, content); //调用Post接口
                var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult<Role>>>();//获得返回结果并反序列化
    
                //assert
                Assert.True(result.data.IsSucceed);
    
                //测完把添加的删除
                await Delete_Id_ReturnResult(result.data.Result.Id);
            }
    
            [Fact]
            public async Task Put_UpdateRole_ReturnTrue()
            {
                //arrange
                RoleViewModel viewModel = new RoleViewModel
                {
                    Name = "RoleForPutTest",
                    DisplayName = "RoleForPutTest"
                };
                StringContent content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);//定义put传递的参数、编码和类型
                using var host = await TestHostBuild.GetTestHost().StartAsync();//启动TestServer
                var response = await host.GetTestClientWithToken().PostAsync(_testUrl, content);//先添加一个用于更新测试 
                viewModel.Id = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult<Role>>>().data.Result.Id;
                content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);
    
                //act
                response = await host.GetTestClientWithToken().PutAsync(_testUrl, content);
                var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult>>();
    
                //assert
                Assert.True(result.data.IsSucceed);
    
                //测完把添加的删除
                await Delete_Id_ReturnResult(viewModel.Id);
            }
        }
    }
    
    • 用于测试的参数准备好后,启动TestServer
    • 从TestServer中获取我们自定义带Token的HttpClient用于接口测试请求
    • 有个名为Delete_Id_ReturnResult的测试,使用了参数,所以改为Theory特性而不是Fact,继而给出了InlineData用作默认参数测试

    打开测试管理器,运行测试,测试都通过:

    项目完成后,如下图所示

    说明

    • 以上便是模拟服务端和客户端通信,从而集成测试整个网站的接口
    • 如果不想对整个网站集成测试,而只是测试某个服务、组件,可以考虑使用moq
    • 说一个不太正规的偏门办法,可以在单元测试中自己new一个依赖注入容器,自己注册服务,然后在测试用例里自己解析,也一样可以做到测试组件的目的
  • 相关阅读:
    设计与声明
    字符串匹配算法——KMP、BM、Sunday
    红黑树——原理
    Linux命令——监视相关
    资源管理
    排序算法——QuickSort、MergeSort、HeapSort(C++实现)
    智能指针——使用与实现
    进程间通信——实现
    构造/析构/赋值运算
    物理内存管理
  • 原文地址:https://www.cnblogs.com/kasnti/p/12246180.html
Copyright © 2011-2022 走看看