学习ASP.NET Core(10)-全局日志与xUnit系统测试

上一篇我们先容了数据塑形,HATEOAS和内容协商,并在制器方式中完成了对应功效的添加;本章我们将先容日志和测试相关的观点,并添加对应的功效

一、全局日志

在第一章先容项目结构时,有提到.NET Core启动时默认加载了日志服务,且在appsetting.json文件设置了一些日志的设置,凭据设置的日志品级的差别可以举行差别级别的信息的显示,但它无法做到输出牢固花样的log信息至内陆磁盘或是数据库,以是需要我们自己手动实现,而我们可以借助日志框架实现。

ps:在第7章节中我们纪录的是数据处置层方式挪用的日志信息,这里纪录的则是ASP.NET Core WebAPI层级的日志信息,两者有所差异

1、引入日志框架

.NET程序中常用的日志框架有log4net,serilog 和Nlog,这里我们使用Serilog来实现相关功效,在BlogSystem.Core层使用NuGet安装Serilog.AspNetCore,同时还需要搜索Serilog.Skins安装希望支持的功效,这里我们希望添加对文件和控制台的输出,以是选择安装的是Serilog.Skins.File和Serilog.Skins.Console

学习ASP.NET Core(10)-全局日志与xUnit系统测试

需要注重的是Serilog是不受appsetting.json的日志设置影响的,且它可以凭据命名空间重写纪录级别。另有一点需要注重的是需要手动对Serilog工具举行资源的释放,否则在系统运行时代,无法打开日志文件。

2、系统添加

在BlogSystem.Core项目中添加一个Logs文件夹,并在Program类中举行Serilog工具的添加和使用,如下:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

3、全局添加

1、这个时刻实在系统已经使用Serilog替换了系统自带的log工具,如下图,Serilog会凭据相关信息举行高亮显示:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

2、这个时刻问题就来了,我们怎么才气举行全局的添加呢,总不能一个方式一个方式的添加吧?还记得之前我们先容AOP时提到的过滤器Filter吗?ASP.NET Core中一共有五类过滤器,划分是:

  • 授权过滤器Authorization Filter:优先级最高,用于确定用户是否获得授权。若是请求未被授权,则授权过滤器会使管道短路;
  • 资源过滤器Resource Filter:授权后运行,会在Authorization之后,Model Binding之前执行,可以实现类似缓存的功效;
  • 方式过滤器Action Filter:在控制器的Action方式执行之前和之后被挪用,可以更改通报给操作的参数或更改从操作返回的效果;
  • 异常过滤器Exception Filter:当Action方式执行历程中泛起了未处置的异常,将会进入这个过滤器举行统一处置;
  • 效果过滤器Result Filter:执行操作效果之前和之后运行,仅在action方式乐成执行后才运行;

过滤器的详细执行顺序如下:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

3、这里我们可以借助异常过滤器实现全局日志功效的添加;在在BlogSystem.Core项目添加一个Filters文件夹,添加一个名为ExceptionFilter的类,继续IExceptionFilter接口,这里是参考老张的哲学的简化版本,实现如下:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Serilog;
using System;

namespace BlogSystem.Core.Filters
{
    public class ExceptionsFilter : IExceptionFilter
    {
        private readonly ILogger<ExceptionsFilter> _logger;

        public ExceptionsFilter(ILogger<ExceptionsFilter> logger)
        {
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            try
            {
                //错误信息
                var msg = context.Exception.Message;
                //错误客栈信息
                var stackTraceMsg = context.Exception.StackTrace;
                //返回信息
                context.Result = new InternalServerErrorObjectResult(new { msg, stackTraceMsg });
                //纪录错误日志
                _logger.LogError(WriteLog(context.Exception));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                //记得释放,否则运行时无法打开日志文件
                Log.CloseAndFlush();
            }

        }

        //返回500错误
        public class InternalServerErrorObjectResult : ObjectResult
        {
            public InternalServerErrorObjectResult(object value) : base(value)
            {
                StatusCode = StatusCodes.Status500InternalServerError;
            }
        }

        //自界说花样内容
        public string WriteLog(Exception ex)
        {
            return $"【异常信息】:{ex.Message} \r\n 【异常类型】:{ex.GetType().Name} \r\n【客栈挪用】:{ex.StackTrace}";
        }
    }
}

4、在Startup类的ConfigureServices方式中举行异常处置过滤器的注册,如下:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

5、我们在控制器方式中抛出一个异常,划分查看效果如下,若是以为信息太多,可调整日志纪录级别:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

学习ASP.NET Core(10)-全局日志与xUnit系统测试

学习ASP.NET Core(10)-全局日志与xUnit系统测试

二、系统测试

这里我们从测试的种别出发,领会下测试相关的内容,并添加相关的测试(先容内容大部门来自微软官方文档,为了更易明白,从小我私家习惯的角度举行了修改,若有形容不当之处,可在谈论区指出)

1、测试说明及分类

1、自动测试是确保软件应用程序根据作者期望执行操作的一种绝佳方式。软件应用有多种类型的测试,包罗单元测试、集成测试、Web测试、负载测试和其他测试。单元测试用于测试小我私家软件的组件或方式,并不包罗如数据库、文件系统和网络资源类的基础结构测试。

固然我们可以使用编写测试的最佳方式,如测试驱动开发(TDD)所指的先编写单元测试,再编写该单元测试要检查的代码,就好比先编写书籍的纲领,再编写书籍。其主要目的是为了辅助开发职员编写更简朴,更具可读性的高效代码。两者区别如下(来自Edison Zhou)

学习ASP.NET Core(10)-全局日志与xUnit系统测试

2、以深度(测试的仔细水平)和广度(测试的笼罩水平)区分, 测试分类如下(此处内容来自solenovex):

学习ASP.NET Core(10)-全局日志与xUnit系统测试

Unit Test 单元测试:它可以测试一个类或者一个类的某个功效,但其笼罩水平较低;

Integration Test 集成测试:它的仔细水平没有单元测试高,然则有较好的笼罩水平,它可以测试功效的组合,以及像数据库或文件系统这样的外部资源;

Subcutaneous Test 皮下测试 :其作用区域为UI层的下一层,有较好的笼罩水平,然则深度欠佳;

UI测试:直接从UI层举行测试,笼罩水平很高,然则深度欠佳

3、在编写单元测试时,只管不要引入基础结构依赖项,这些依赖项会降低测试速率,使测试加倍懦弱,我们应当将其保留供集成测试使用。可以通过遵照显示依赖项原则和使用依赖项注入制止应用程序中的这些依赖项,还可以将单元测试保留在单独的项目中与集成测试相星散,以确保单元测试项目没有引用或依赖于基础结构包。

总结下常用的单元测试和集成测试,单元测试会与外部资源隔离,以保证效果的一致性;而集成测试会依赖外部资源,且笼罩面更广。

2、测试的目的及特征

1、为什么需要测试?我们从以单元测试为例从4个方面举行说明:

  • 时间人力成本:举行功效测试时,通常涉及打开应用程序,执行一系列需要遵照的步骤来验证预期的行为,这意味着测试职员需要领会这些步骤或联系熟悉该步骤的人来获取效果。对于细微的更改或者是较大的更改,都需要重复上述历程,而单元测试只需要按一下按钮即可运行,无需测试职员领会整个系统,测试效果也取决于测试运行程序而非测试职员。
  • 防止错误回归:程序更改后有时会泛起旧功效异常的问题,以是测试时不仅要测试新功效还要确保旧功效的正常运行。而单元测试可以确保在更改一行代码后重新运行整套测试,确保新代码不会损坏现有的功效。
  • 可执行性:在给定某个输入的情形下,特定方式的作用或行为可能不会很明显。好比,输入或通报空缺字符串、null后,该方式会有怎样的行为?而当我们使用一套命名准确的单元测试,并清晰的注释给定的输入和预期输出,那么它将可以验证其有效性。
  • 削减代码耦合:当代码慎密耦适时,会难以举行单元测试,以是以确立单元测试为目的时,会在一定水平上要求我们注重代码的解耦

2、优质的测试需要相符哪些特征,同样以单元测试为例:

  • 快速:成熟的项目会举行数千次的单元测试,以是应当破费异常少的时间来运行单元测试,一般来说在几毫秒
  • 自力:单元测试应当是自力的,可以单独运行,不依赖文件系统或数据库等外部因素
  • 可重复:单元测试的效果应当保持一致,即运行时代不举行更改,返回的效果应该相同
  • 自检查:测试应当在没有人工交互的情形下,自动检测是否通过
  • 实时:编写单元测试不应该破费过多的时间,若是破费时间较长,应当思量另外一种更易测试的设计

在详细的执行时,我们应当遵照一些最佳实践规则,详细请参考微软官方文档单元测试最佳做法

3、xUnit框架先容

常用的单元测试框架有MSTestxUnitNUnit,这里我们以xUnit为例举行相关的说明

3.1、测试操作

首先我们要明确若何编写测试代码,一般来说,测试分为三个主要操作:

如何在微信小程序中使用骨架屏

  • Arrange:意为放置或准备,这里可以凭据需求举行工具的确立或相关的设置;
  • Act:意为操作,这里可以执行获取生产代码返回的效果或者是设置属性;
  • Assert:意为断言,这里可以用来判断某些项是否按预期举行,即测试通过照样失败

3.2、Assert类型

Assert时通常会对差别类型的返回值举行判断,而在xUnit中是支持多种返回值类型的,常用的类型如下:

boolean:针对方式返回值为bool的效果,可以判断效果是true或false

string:针对方式返回值为string的效果,可以判断效果是否相等,是否以某字符串开头或末端,是否包罗某些字符,并支持正则表达式

数值型:针对方式返回值为数值的效果,可以判断数值是否相等,数值是否在某个区间内,数值是否为null或非null

Collection:针对方式返回值为聚集的效果,可以针对聚集内所有元素或至少一个元素判断其是否包罗某某字符,两个聚集是否相等

ObjectType:针对方式返回值为某种类型的情形,可以判断是否为预期的类型,一个类是否继续于另一个类,两个类是否为统一实例

Raised event:针对事宜是否执行的情形,可以判断方式内部是否执行了预期的事宜

3.3、常用特征

在xUnit中另有一些常用的特征,可作用于方式或类,如下:

[Fact]:用来标注该方式为测试方式

[Trait(“Name”,”Value”)]:用来对测试方式举行分组,支持标注多个差别的组名

[Fact(Skip=”忽略说明…”)]:用来修饰需要忽略测试的方式

3.4 、性能相关

在测试时我们应当注重性能上的问题,针对一个工具供多个方式使用的情形,我们可以使用共享上下文

  • 针对一个工具供统一类中的多个方式使用时,可以将该工具提取出来,使用IClassFixture 工具将其注入到组织函数中
  • 针对一个工具供多个测试类使用的情形,可以使用ICollectionFixture 工具和[CollectionDefinition(“…”)]界说该工具

需要注重在使用IClassFixtureICollectionFixture工具时应当制止多个测试方式之间相互影响的情形

3.5、数据驱动测试

在举行测试方式时,通常我们会指定输入值和输出值,如希望多测试几种情形,我们可以界说多个测试方式,但这显然不是一个最佳的实现;在合理的情形下,我们可以将参数和数据星散,若何实现?

  • 方式一:使用[Theory]替换[Fact],将输入输出参数提取为方式参数,并使用多个[InlineData(“输入参数”,”输出参数)]来标注方式
  • 方式二:使用[Theory]替换[Fact],针对测试方式新增一个测试数据类,该类包罗一个静态属性IEumerable<object[]>,将数据封装为一个list后赋值给该属性,并使用[MemberData(nameof(数据类的属性),MemberType=typeof(数据类))]标注测试方式即可;
  • 方式三:使用外部数据如数据库数据/Excel数据/txt数据等,实在现原理与方式二相同,只是多了一个数据获取封装为list的步骤;
  • 方式四:自界说一个Attribute,继续自DataAttribute,实现其对应的方式,使用yield返回object类型的数组;使用时只需要在测试方式上方添加[Theory][自界说Attribute]即可

4、测试项目添加

4.1、添加测试项目

首先我们右键项目解决方案选择添加一个项目,输入选择xUnit后举行添加,项目命名为BlogSystem.Core.Test,如下:

学习ASP.NET Core(10)-全局日志与xUnit系统测试

项目添加完成后我们需要添加对测试项目的引用,在解决方案中右击依赖项选择添加BlogSystem.Core;这里我们预期对Controller举行测试,但后续有可能会添加其他项目的测试,以是我们确立一个Controller_Test文件夹保证项目结构相对清晰。

4.2、添加测试方式

在BlogSystem.Core.Test项目的Controller_Test文件夹下新建一个命名为UserController_Should的方式;在微软的《单元测试的最佳做法》文档中有提到,测试命名应该包罗三个部门:①被测试方式的名称②测试的方案③方案预期行为;实际使用时也可以对照测试的方式举行命名,这里我们先不思量最佳命名原则,仅对照测试方式举行命名,如下:

using Xunit;

namespace BlogSystem.Core.Test.Controller_Test
{
    public class UserController_Should
    {
        [Fact]
        public void Register_Test()
        {
            
        }
    }
}

4.3、方案选择

1、在举行测试时,我们可以凭据实际情形使用以下方案来举行测试:

  • 方案一:直接new一个Controller工具,挪用其Action方式直接举行测试;适用于Controller没有其他依赖项的情形;
  • 方案二:当有多个依赖项时,可以借助工具来模拟实例化时的依赖项,如Moq就是一个很好的工具;固然这需要一定的学习成本;
  • 方案三:模拟Http请求的方式来挪用API举行测试;NuGet中的Microsoft.AspNetCore.TestHost就支持这类情形;
  • 方案四:自界说方式实例化所有依赖项;将测试历程种需要用到的工具放到容器中并加载,实在现较为庞大;

这里我们以测试UserController为例,其组织函数包罗了接口服务实例和HttpContext工具实例,Action方式内部又有数据库毗邻操作,从严酷意义上来讲测试这类方式已经脱离了单元测试的范围,属于集成测试,但这类测试一定水平上可以节约我们大量的重复劳动。这里我们选择方案三举行相关的测试。

2、若何使用TestHost工具?先来看看它的事情流程,首先它会确立一个IHostBuilder工具,并用它确立一个TestServer工具,TestServer工具可以确立HttpClient工具,该工具支持发送及响应请求,如下图所示(来自solenovex):

学习ASP.NET Core(10)-全局日志与xUnit系统测试

在实验使用该工具的历程中我们会发现一个问题,确立IHostBuilder工具时需要指明类似Startup的设置项,由于这里是测试环境,以是实际上会与BlogSystem.Core中的设置类StartUp存在一定的差异,因而这里我们需要为测试新确立一个Startup设置类。

4.4、方式实现

1、我们在测试项目中添加名为TestServerFixture 的类和名为TestStartup的类,TestServerFixture 用来确立HttpClient工具并做一些准备事情,TestStartup类为设置类。然后使用Nuget安装Microsoft.AspNetCore.TestHost;TestServerFixture 和TestStartup实现如下:

using Autofac.Extensions.DependencyInjection;
using BlogSystem.Core.Helpers;
using BlogSystem.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;

namespace BlogSystem.Core.Test
{
    public static class TestServerFixture
    {
        public static IHostBuilder GetTestHost()
        {
            return Host.CreateDefaultBuilder()
           .UseServiceProviderFactory(new AutofacServiceProviderFactory())//使用autofac作为DI容器
           .ConfigureWebHostDefaults(webBuilder =>
           {
               webBuilder.UseTestServer()//确立TestServer——测试的要害
               .UseEnvironment("Development")
               .UseStartup<TestStartup>();
           });
        }

        //天生带token的httpclient
        public static HttpClient GetTestClientWithToken(this IHost host)
        {
            var client = host.GetTestClient();
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {GenerateJwtToken()}");//把token加到Header中
            return client;
        }

        //天生JwtToken
        public static string GenerateJwtToken()
        {
            TokenModelJwt tokenModel = new TokenModelJwt { UserId = userData.Id, Level = userData.Level.ToString() };
            var token = JwtHelper.JwtEncrypt(tokenModel);
            return token;
        }

        //测试用户的数据
        private static readonly User userData = new User
        {
            Account = "jordan",
            Id = new Guid("9CF2DAB5-B9DC-4910-98D8-CBB9D54E3D7B"),
            Level = Level.普通用户
        };

    }
}
using Autofac;
using Autofac.Extras.DynamicProxy;
using BlogSystem.Common.Helpers;
using BlogSystem.Common.Helpers.SortHelper;
using BlogSystem.Core.AOP;
using BlogSystem.Core.Filters;
using BlogSystem.Core.Helpers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace BlogSystem.Core.Test
{
    public class TestStartup
    {
        private readonly IConfiguration _configuration;

        public TestStartup(IConfiguration configuration)
        {
            _configuration = GetConfig(null);
            //通报Configuration工具
            JwtHelper.GetConfiguration(_configuration);
        }

        public void ConfigureServices(IServiceCollection services)
        {
            //控制器服务注册
            services.AddControllers(setup =>
            {
                setup.ReturnHttpNotAcceptable = true;//开启不存在请求花样则返回406状态码的选项
                var jsonOutputFormatter = setup.OutputFormatters.OfType<SystemTextJsonOutputFormatter>()?.FirstOrDefault();//不为空则继续执行
                jsonOutputFormatter?.SupportedMediaTypes.Add("application/vnd.company.hateoas+json");
                setup.Filters.Add(typeof(ExceptionsFilter));//添加异常过滤器
            }).AddXmlDataContractSerializerFormatters()//开启输出输入支持XML花样

            //jwt授权服务注册
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(x =>
            {
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true, //验证密钥
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["JwtTokenManagement:secret"])),

                    ValidateIssuer = true, //验证发行人
                    ValidIssuer = _configuration["JwtTokenManagement:issuer"],

                    ValidateAudience = true, //验证订阅人
                    ValidAudience = _configuration["JwtTokenManagement:audience"],

                    RequireExpirationTime = true, //验证过时时间
                    ValidateLifetime = true, //验证生命周期
                    ClockSkew = TimeSpan.Zero, //缓冲过时时间,纵然设置了过时时间,也要思量过时时间+缓冲时间
                };
            });

            //注册HttpContext存取器服务
            services.AddHttpContextAccessor();

            //自界说判断属性隐射关系
            services.AddTransient<IPropertyMappingService, PropertyMappingService>();

            services.AddTransient<IPropertyCheckService, PropertyCheckService>();
        }

        //configureContainer接见AutoFac容器天生器
        public void ConfigureContainer(ContainerBuilder builder)
        {
            //获取程序集并注册,接纳每次请求都确立一个新的工具的模式
            var assemblyBll = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.BLL.dll"));
            var assemblyDal = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.DAL.dll"));

            builder.RegisterAssemblyTypes(assemblyDal).AsImplementedInterfaces().InstancePerDependency();

            //注册阻挡器
            builder.RegisterType<LogAop>();
            //对目的类型启用动态署理,并注入自界说阻挡器阻挡BLL
            builder.RegisterAssemblyTypes(assemblyBll).AsImplementedInterfaces().InstancePerDependency()
           .EnableInterfaceInterceptors().InterceptedBy(typeof(LogAop));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler(builder =>
                {
                    builder.Run(async context =>
                    {
                        context.Response.StatusCode = 500;
                        await context.Response.WriteAsync("Unexpected Error!");
                    });
                });
            }

            app.UseRouting();

           //添加认证中间件
            app.UseAuthentication();

            //添加授权中间件
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

        private IConfiguration GetConfig(string environmentName)
        {
            var path = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;

            IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(path)
               .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            if (!string.IsNullOrWhiteSpace(environmentName))
            {
                builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true);
            }

            builder = builder.AddEnvironmentVariables();

            return builder.Build();
        }
    }
}

2、这里对UserController中的注册、登录、获取用户信息方式举行测试,实际上这里的断言并不严谨,会发生什么结果?请继续往下看

using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace BlogSystem.Core.Test.Controller_Test
{
    public class UserController_Should
    {
        const string _mediaType = "application/json";
        readonly Encoding _encoding = Encoding.UTF8;


        /// <summary>
        /// 用户注册
        /// </summary>
        [Fact]
        public async Task Register_Test()
        {
            // 1、Arrange
            var data = new RegisterViewModel { Account = "test", Password = "123456", RequirePassword = "123456" };

            StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);

            using var host = await TestServerFixture.GetTestHost().StartAsync();//启动TestServer

            // 2、Act
            var response = await host.GetTestClient().PostAsync($"http://localhost:5000/api/user/register", content);

            var result = await response.Content.ReadAsStringAsync();

            // 3、Assert
            Assert.DoesNotContain("用户已存在", result);
        }

        /// <summary>
        /// 用户登录
        /// </summary>
        [Fact]
        public async Task Login_Test()
        {
            var data = new LoginViewModel { Account = "jordan", Password = "123456" };

            StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);

            var host = await TestServerFixture.GetTestHost().StartAsync();//启动TestServer

            var response = await host.GetTestClientWithToken().PostAsync($"http://localhost:5000/api/user/Login", content);

            var result = await response.Content.ReadAsStringAsync();

            Assert.DoesNotContain("账号或密码错误!", result);
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        [Fact]
        public async Task UserInfo_Test()
        {
            string id = "jordan";

            using var host = await TestServerFixture.GetTestHost().StartAsync();//启动TestServer

            var client = host.GetTestClient();

            var response = await client.GetAsync($"http://localhost:5000/api/user/{id}");

            var result = response.StatusCode;

            Assert.True(Equals(HttpStatusCode.OK, result)|| Equals(HttpStatusCode.NotFound, result));
        }
    }
}

4.5、异常及解决

1、添加完上述的测试方式后,我们使用打开Visual Studio自带的测试资源管理器,点击运行所有测试,发现提醒错误无法加载BLL?在原先的BlogSystem.Core的StartUp类中我们是加载BLL和DAL项目的dll来到达解耦的目的,以是做了一个将dll输出到Core项目bin文件夹的动作,然则在测试项目的TestStarup类中,我们是无法加载到BLL和DAL的。我实验将BLL和DAL同时输出到两个路径下,但未找到对应的方式,以是这里我接纳了最简朴的解决方式,测试项目添加了对DAL和BLL的引用。再次运行,如下图,似乎乐成了??

学习ASP.NET Core(10)-全局日志与xUnit系统测试

2、我们在测试方式内部打上断点,右击测试方式,选择调试测试,效果发现response参数为空,只应Assert不严谨导致看上去没有问题;在种种查找后,我终于找到领会决办法,在TestStarup类的ConfigureServices方式内部service.AddControllers方式最后加上这么一句话即可解决 .AddApplicationPart(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.Core.dll")))

学习ASP.NET Core(10)-全局日志与xUnit系统测试

3、再次运行测试方式,乐成!然则又发现了另外一个问题,这里我们只是测试,然则数据库中却泛起了我们测试添加的test账号,若何解决?我们可以使用Microsoft.EntityFrameworkCore.InMemory库 ,它支持使用内存数据库举行测试,这里暂未添加,有兴趣的同伙可以自行研究。

本章完~

本人知识点有限,若文中有错误的地方请实时指正,利便人人更好的学习和交流。

本文部门内容参考了网络上的视频内容和文章,仅为学习和交流,视频地址如下:

老张的哲学,系列教程一目录:.netcore+vue 前后端星散

我想吃晚饭,ASP.NET Core搭建多层网站架构【12-xUnit单元测试之集成测试】

solenovex,使用 xUnit.NET 对 .NET Core 项目举行单元测试

solenovex,ASP.NET Core Web API 集成测试

微软官方文档,.NET Core 和 .NET Standard 中的单元测试

Edison Zhou,.NET单元测试的艺术

声明

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/15481.html