ASP.NET Core项目如何使用xUnit进行单元测试(asp.net,core,xunit,开发技术)

时间:2024-05-08 06:35:20 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

一、前言

在以前的.NET Framework项目中,我们也写过一些单元测试的项目,而在ASP.NET Core 这种Web或者API应用程序中要做单元测试是很方便的。

这篇文章主要讲解如何使用xUnit对ASP.NET Core应用程序做单元测试。.NET Core中常用的测试工具还有NUnit和MSTest。

xUnit是一个测试框架,可以针对.net/.net core项目进行测试。测试项目需要引用被测试的项目,从而对其进行测试。测试项目同时需要引用xUnit库。测试编写好后,用Test Runner来运行测试。Test Runner可以读取测试代码,并且会知道我们所使用的测试框架,然后执行,并显示结果。目前可用的Test Runner包括vs自带的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等。

xUnit可以支持多种平台的测试:

  • .NET Framework

  • .NET Core

  • .NET Standard

  • UWP

  • Xamarin

二、创建示例项目

为了使示例项目更加的贴近真实的项目开发,这里采用分层的方式创建一个示例项目,创建完成后的项目结构如下图所示:

ASP.NET Core项目如何使用xUnit进行单元测试

下面讲解一下每层的作用,按照从上往下的顺序:

  • TestDemo:从名字就可以看出来,这是一个单元测试的项目,针对控制器进行测试。

  • UnitTest.Data:数据访问,封装与EntityFrameworkCore相关的操作。

  • UnitTest.IRepository:泛型仓储接口,封装基础的增删改查。

  • UnitTest.Model:实体层,定义项目中使用到的所有实体。

  • UnitTest.Repository:泛型仓储接口实现层,实现接口里面定义的方法。

  • UnitTestDemo:ASP.NET Core WebApi,提供API接口。

1、UnitTest.Model

实体层里面只有一个Student类:

usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceUnitTest.Model{publicclassStudent{publicintID{get;set;}publicstringName{get;set;}publicintAge{get;set;}publicstringGender{get;set;}}}

2、UnitTest.Data

里面封装与EF Core有关的操作,首先需要引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包,直接在管理NuGet程序包里面引入,这里不在讲述。

引入相关NuGet包以后,我们创建数据上下文类,该类继承自EF Core的DbContext,里面设置表名和一些属性:

usingMicrosoft.EntityFrameworkCore;usingUnitTest.Model;namespaceUnitTest.Data{///<summary>///数据上下文类///</summary>publicclassAppDbContext:DbContext{///<summary>///通过构造函数给父类构造传参///</summary>///<paramname="options"></param>publicAppDbContext(DbContextOptions<AppDbContext>options):base(options){}publicDbSet<Student>Students{get;set;}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<Student>().ToTable("T_Student");modelBuilder.Entity<Student>().HasKey(p=>p.ID);modelBuilder.Entity<Student>().Property(p=>p.Name).HasMaxLength(32);//添加种子数据modelBuilder.Entity<Student>().HasData(newStudent(){ID=1,Name="测试1",Age=20,Gender="男"},newStudent(){ID=2,Name="测试2",Age=22,Gender="女"},newStudent(){ID=3,Name="测试3",Age=23,Gender="男"});base.OnModelCreating(modelBuilder);}}}

这里采用数据迁移的方式生成数据库,需要在API项目中引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包。引入方式同上。

然后在API项目的appsettings.json文件里面添加数据库链接字符串:

{"Logging":{"LogLevel":{"Default":"Information","Microsoft":"Warning","Microsoft.Hosting.Lifetime":"Information"}},"AllowedHosts":"*",//数据库连接字符串"ConnectionString":{"DbConnection":"InitialCatalog=TestDb;UserId=sa;Password=1234;DataSource=.;ConnectionTimeout=10;"}}

在JSON文件中添加完连接字符串以后,修改Startup类的ConfigureServices方法,在里面配置使用在json文件中添加的连接字符串:

//添加数据库连接字符串services.AddDbContext<AppDbContext>(options=>{options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);});

这样就可以使用数据迁移的方式生成数据库了。

3、UnitTest.IRepository

该项目中使用泛型仓储,定义一个泛型仓储接口:

usingSystem.Collections.Generic;usingSystem.Threading.Tasks;namespaceUnitTest.IRepository{publicinterfaceIRepository<T>whereT:class,new(){Task<List<T>>GetList();Task<int?>Add(Tentity);Task<int?>Update(Tentity);Task<int?>Delete(Tentity);}}

然后在定义IStudentRepository接口继承自IRepository泛型接口:

usingUnitTest.Model;namespaceUnitTest.IRepository{publicinterfaceIStudentRepository:IRepository<Student>{}}

4、UnitTest.Repository

这里是实现上面定义的仓储接口:

usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingUnitTest.Data;usingUnitTest.IRepository;usingUnitTest.Model;namespaceUnitTest.Repository{publicclassStudentRepository:IStudentRepository{privatereadonlyAppDbContext_dbContext;///<summary>///通过构造函数实现依赖注入///</summary>///<paramname="dbContext"></param>publicStudentRepository(AppDbContextdbContext){_dbContext=dbContext;}publicasyncTask<int?>Add(Studententity){_dbContext.Students.Add(entity);returnawait_dbContext.SaveChangesAsync();}publicasyncTask<int?>Delete(Studententity){_dbContext.Students.Remove(entity);returnawait_dbContext.SaveChangesAsync();}publicasyncTask<List<Student>>GetList(){List<Student>list=newList<Student>();list=awaitTask.Run<List<Student>>(()=>{return_dbContext.Students.ToList();});returnlist;}publicasyncTask<int?>Update(Studententity){Studentstudent=_dbContext.Students.Find(entity.ID);if(student!=null){student.Name=entity.Name;student.Age=entity.Age;student.Gender=entity.Gender;_dbContext.Entry<Student>(student).State=Microsoft.EntityFrameworkCore.EntityState.Modified;returnawait_dbContext.SaveChangesAsync();}return0;}}}

5、UnitTestDemo

先添加一个Value控制器,里面只有一个Get方法,而且没有任何的依赖关系,先进行最简单的测试:

usingMicrosoft.AspNetCore.Mvc;namespaceUnitTestDemo.Controllers{[Route("api/[controller]")][ApiController]publicclassValueController:ControllerBase{[HttpGet("{id}")]publicActionResult<string>Get(intid){return$"Parais{id}";}}}

6、TestDemo

我们在添加测试项目的时候,直接选择使用xUnit测试项目,如下图所示:

ASP.NET Core项目如何使用xUnit进行单元测试

这样项目创建完成以后,就会自动添加xUnit的引用:

<ItemGroup><PackageReferenceInclude="Microsoft.NET.Test.Sdk"Version="16.2.0"/><PackageReferenceInclude="xunit"Version="2.4.1"/><PackageReferenceInclude="xunit.runner.visualstudio"Version="2.4.0"/></ItemGroup>

但要测试 ASP.NET Core 应用还需要添加两个 NuGet 包:

Install-PackageMicrosoft.AspNetCore.AppInstall-PackageMicrosoft.AspNetCore.TestHost

上面是使用命令的方式进行安装,也可以在管理NuGet程序包里面进行搜索,然后安装。

千万不要忘记还要引入要测试的项目。最后的项目引入是这样的:

<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netcoreapp3.1</TargetFramework><IsPackable>false</IsPackable></PropertyGroup><ItemGroup><PackageReferenceInclude="Microsoft.AspNetCore.App"Version="2.2.8"/><PackageReferenceInclude="Microsoft.AspNetCore.TestHost"Version="3.1.2"/><PackageReferenceInclude="Microsoft.NET.Test.Sdk"Version="16.2.0"/><PackageReferenceInclude="Newtonsoft.Json"Version="12.0.3"/><PackageReferenceInclude="xunit"Version="2.4.1"/><PackageReferenceInclude="xunit.runner.visualstudio"Version="2.4.0"/><PackageReferenceInclude="coverlet.collector"Version="1.0.1"/></ItemGroup><ItemGroup><ProjectReferenceInclude="..\UnitTest.Model\UnitTest.Model.csproj"/><ProjectReferenceInclude="..\UnitTestDemo\UnitTestDemo.csproj"/></ItemGroup></Project>

都添加完以后,重新编译项目,保证生成没有错误。

三、编写单元测试

单元测试按照从上往下的顺序,一般分为三个阶段:

  • Arrange:准备阶段。这个阶段做一些准备工作,例如创建对象实例,初始化数据等。

  • Act:行为阶段。这个阶段是用准备好的数据去调用要测试的方法。

  • Assert:断定阶段。这个阶段就是把调用目标方法的返回值和预期的值进行比较,如果和预期值一致则测试通过,否则测试失败。

我们在API项目中添加了一个Value控制器,我们以Get方法作为测试目标。一般一个单元测试方法就是一个测试用例。

我们在测试项目中添加一个ValueTest测试类,然后编写一个单元测试方法,这里是采用模拟HTTPClient发送Http请求的方式进行测试:

usingMicrosoft.AspNetCore;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingSystem.Net;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTestDemo;usingXunit;namespaceTestDemo{publicclassValueTests{publicHttpClient_client{get;}///<summary>///构造方法///</summary>publicValueTests(){varserver=newTestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());_client=server.CreateClient();}[Fact]publicasyncTaskGetById_ShouldBe_Ok(){//1、Arrangevarid=1;//2、Act//调用异步的Get方法varresponse=await_client.GetAsync($"/api/value/{id}");//3、AssertAssert.Equal(HttpStatusCode.OK,response.StatusCode);}}}

我们在构造函数中,通过TestServer拿到一个HttpClient对象,用它来模拟Http请求。我们写了一个测试用例,完整演示了单元测试的Arrange、Act和Assert三个步骤。

1、运行单元测试

单元测试用例写好以后,打开“测试资源管理器”:

ASP.NET Core项目如何使用xUnit进行单元测试

在底部就可以看到测试资源管理器了:

ASP.NET Core项目如何使用xUnit进行单元测试

在要测试的方法上面右键,选择“运行测试”就可以进行测试了:

ASP.NET Core项目如何使用xUnit进行单元测试

注意观察测试方法前面图标的颜色,目前是蓝色的,表示测试用例还没有运行过:

ASP.NET Core项目如何使用xUnit进行单元测试

测试用例结束以后,我们在测试资源管理器里面可以看到结果:

ASP.NET Core项目如何使用xUnit进行单元测试

绿色表示测试通过。我们还可以看到执行测试用例消耗的时间。

如果测试结果和预期结果一致,那么测试用例前面图标的颜色也会变成绿色:

ASP.NET Core项目如何使用xUnit进行单元测试

如果测试结果和预期结果不一致就会显示红色,然后需要修改代码直到出现绿色图标。我们修改测试用例,模拟测试失败的情况:

usingMicrosoft.AspNetCore;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingSystem.Net;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTestDemo;usingXunit;namespaceTestDemo{publicclassValueTests{publicHttpClient_client{get;}///<summary>///构造方法///</summary>publicValueTests(){varserver=newTestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());_client=server.CreateClient();}[Fact]publicasyncTaskGetById_ShouldBe_Ok(){//1、Arrangevarid=1;//2、Act//调用异步的Get方法varresponse=await_client.GetAsync($"/api/value/{id}");////3、Assert//Assert.Equal(HttpStatusCode.OK,response.StatusCode);//3、Assert//模拟测试失败Assert.Equal(HttpStatusCode.BadRequest,response.StatusCode);}}}

然后运行测试用例:

ASP.NET Core项目如何使用xUnit进行单元测试

2、调试单元测试

我们也可以通过添加断点的方式在测试用例中进行调试。调试单元测试很简单,只需要在要调试的方法上面右键选择“调试测试”,如下图所示:

ASP.NET Core项目如何使用xUnit进行单元测试

其它操作就跟调试普通方法一样。

除了添加断点调试,我们还可以采用打印日志的方法来快速调试,xUnit可以很方便地做到这一点。我们修改ValueTest类:

usingMicrosoft.AspNetCore;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingSystem.Net;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTestDemo;usingXunit;usingXunit.Abstractions;namespaceTestDemo{publicclassValueTests{publicHttpClient_client{get;}publicITestOutputHelperOutput{get;}///<summary>///构造方法///</summary>publicValueTests(ITestOutputHelperoutputHelper){varserver=newTestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());_client=server.CreateClient();Output=outputHelper;}[Fact]publicasyncTaskGetById_ShouldBe_Ok(){//1、Arrangevarid=1;//2、Act//调用异步的Get方法varresponse=await_client.GetAsync($"/api/value/{id}");//3、Assert//模拟测试失败//Assert.Equal(HttpStatusCode.BadRequest,response.StatusCode);//输出返回信息//OutputvarresponseText=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(responseText);//3、AssertAssert.Equal(HttpStatusCode.OK,response.StatusCode);}}}

这里我们在构造函数中添加了 ITestOutputHelper 参数,xUnit 会将一个实现此接口的实例注入进来。拿到这个实例后,我们就可以用它来输出日志了。运行(注意不是 Debug)此方法,运行结束后在测试资源管理器里面查看:

ASP.NET Core项目如何使用xUnit进行单元测试

点击就可以看到输出的日志了:

ASP.NET Core项目如何使用xUnit进行单元测试

在上面的例子中,我们是使用的简单的Value控制器进行测试,控制器里面没有其他依赖关系,如果控制器里面有依赖关系该如何测试呢?方法还是一样的,我们新建一个Student控制器,里面依赖IStudentRepository接口,代码如下:

usingSystem.Collections.Generic;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingUnitTest.IRepository;usingUnitTest.Model;namespaceUnitTestDemo.Controllers{[Route("api/student")][ApiController]publicclassStudentController:ControllerBase{privatereadonlyIStudentRepository_repository;///<summary>///通过构造函数注入///</summary>///<paramname="repository"></param>publicStudentController(IStudentRepositoryrepository){_repository=repository;}///<summary>///get方法///</summary>///<returns></returns>[HttpGet]publicasyncTask<ActionResult<List<Student>>>Get(){returnawait_repository.GetList();}}}

然后在Startup类的ConfigureServices方法中注入:

publicvoidConfigureServices(IServiceCollectionservices){//添加数据库连接字符串services.AddDbContext<AppDbContext>(options=>{options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);});//添加依赖注入到容器中services.AddScoped<IStudentRepository,StudentRepository>();services.AddControllers();}

在单元测试项目中添加StudentTest类:

usingMicrosoft.AspNetCore;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingNewtonsoft.Json;usingSystem.Collections.Generic;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTest.Model;usingUnitTestDemo;usingXunit;usingXunit.Abstractions;namespaceTestDemo{publicclassStudentTest{publicHttpClientClient{get;}publicITestOutputHelperOutput{get;}publicStudentTest(ITestOutputHelperoutputHelper){varserver=newTestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());Client=server.CreateClient();Output=outputHelper;}[Fact]publicasyncTaskGet_ShouldBe_Ok(){//2、Actvarresponse=awaitClient.GetAsync($"api/student");//Outputstringcontext=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(context);List<Student>list=JsonConvert.DeserializeObject<List<Student>>(context);//AssertAssert.Equal(3,list.Count);}}}

然后运行单元测试:

ASP.NET Core项目如何使用xUnit进行单元测试

可以看到,控制器里面如果有依赖关系,也是可以使用这种方式进行测试的。

Post方法也可以使用同样的方式进行测试,修改控制器,添加Post方法:

usingSystem.Collections.Generic;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingUnitTest.IRepository;usingUnitTest.Model;namespaceUnitTestDemo.Controllers{[Route("api/student")][ApiController]publicclassStudentController:ControllerBase{privatereadonlyIStudentRepository_repository;///<summary>///通过构造函数注入///</summary>///<paramname="repository"></param>publicStudentController(IStudentRepositoryrepository){_repository=repository;}///<summary>///get方法///</summary>///<returns></returns>[HttpGet]publicasyncTask<ActionResult<List<Student>>>Get(){returnawait_repository.GetList();}///<summary>///Post方法///</summary>///<paramname="entity"></param>///<returns></returns>[HttpPost]publicasyncTask<bool>Post([FromBody]Studententity){int?result=await_repository.Add(entity);if(result==null){returnfalse;}else{returnresult>0?true:false;}}}}

在增加一个Post的测试方法:

usingMicrosoft.AspNetCore;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingNewtonsoft.Json;usingSystem.Collections.Generic;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTest.Model;usingUnitTestDemo;usingXunit;usingXunit.Abstractions;namespaceTestDemo{publicclassStudentTest{publicHttpClientClient{get;}publicITestOutputHelperOutput{get;}publicStudentTest(ITestOutputHelperoutputHelper){varserver=newTestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>());Client=server.CreateClient();Output=outputHelper;}[Fact]publicasyncTaskGet_ShouldBe_Ok(){//2、Actvarresponse=awaitClient.GetAsync($"api/student");//Outputstringcontext=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(context);List<Student>list=JsonConvert.DeserializeObject<List<Student>>(context);//AssertAssert.Equal(3,list.Count);}[Fact]publicasyncTaskPost_ShouldBe_Ok(){//1、ArrangeStudententity=newStudent(){Name="测试9",Age=25,Gender="男"};varstr=JsonConvert.SerializeObject(entity);HttpContentcontent=newStringContent(str);//2、Actcontent.Headers.ContentType=newSystem.Net.Http.Headers.MediaTypeHeaderValue("application/json");HttpResponseMessageresponse=awaitClient.PostAsync("api/student",content);stringresponseBody=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(responseBody);//3、AssertAssert.Equal("true",responseBody);}}}

运行测试用例:

ASP.NET Core项目如何使用xUnit进行单元测试

这样一个简单的单元测试就完成了。

我们观察上面的两个测试类,发现这两个类都有一个共同的特点:都是在构造函数里面创建一个HttpClient对象,我们可以把创建HttpClient对象抽离到一个共同的基类里面,所有的类都继承自基类。该基类代码如下:

usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingSystem.IO;usingSystem.Net.Http;usingUnitTestDemo;namespaceTestDemo{///<summary>///基类///</summary>publicclassApiControllerTestBase{///<summary>///返回HttpClient对象///</summary>///<returns></returns>protectedHttpClientGetClient(){varbuilder=newWebHostBuilder()//指定使用当前目录.UseContentRoot(Directory.GetCurrentDirectory())//使用Startup类作为启动类.UseStartup<Startup>()//设置使用测试环境.UseEnvironment("Testing");varserver=newTestServer(builder);//创建HttpClientHttpClientclient=server.CreateClient();returnclient;}}}

然后修改StudentTest类,使该类继承自上面创建的基类:

usingNewtonsoft.Json;usingSystem.Collections.Generic;usingSystem.Net.Http;usingSystem.Threading.Tasks;usingUnitTest.Model;usingXunit;usingXunit.Abstractions;namespaceTestDemo{publicclassStudentTest:ApiControllerTestBase{publicHttpClientClient{get;}publicITestOutputHelperOutput{get;}publicStudentTest(ITestOutputHelperoutputHelper){//varserver=newTestServer(WebHost.CreateDefaultBuilder()//.UseStartup<Startup>());//Client=server.CreateClient();//从父类里面获取HttpClient对象Client=base.GetClient();Output=outputHelper;}[Fact]publicasyncTaskGet_ShouldBe_Ok(){//2、Actvarresponse=awaitClient.GetAsync($"api/student");//Outputstringcontext=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(context);List<Student>list=JsonConvert.DeserializeObject<List<Student>>(context);//AssertAssert.Equal(3,list.Count);}[Fact]publicasyncTaskPost_ShouldBe_Ok(){//1、ArrangeStudententity=newStudent(){Name="测试9",Age=25,Gender="男"};varstr=JsonConvert.SerializeObject(entity);HttpContentcontent=newStringContent(str);//2、Actcontent.Headers.ContentType=newSystem.Net.Http.Headers.MediaTypeHeaderValue("application/json");HttpResponseMessageresponse=awaitClient.PostAsync("api/student",content);stringresponseBody=awaitresponse.Content.ReadAsStringAsync();Output.WriteLine(responseBody);//3、AssertAssert.Equal("true",responseBody);}}}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:ASP.NET Core项目如何使用xUnit进行单元测试的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:提高UI设计效率的8个实用技巧分别是什么下一篇:

15 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18