AbpvNext源码分析1
ABPvNext框架使用xUnit作为单元测试组件,官方的所有模块都编写了大量的单元/集成测试确保功能正常。由于ABPvNext模块化系统的原因,开发人员在建立单元测试项目的时候需要集成Volo.Abp.UnitTest项目,这样在执行单元测试的时候才不会缺少必要组件。
分析ABPvNext单元测试相关的类型最核心的是集成测试基类AbpIntegratedTest和MVC专用测试基类AbpAspNetCoreIntegratedTestBase,这两个基类核心工作就是初始化IoC容器并且初始化整个模块系统,只不过后者对控制器相关的组件进行了初始化配置,让开发人员可以针对控制器进行单元/集成测试。
从上图可以看到两个基类都继承自AbpTestBaseWithServiceProvider基类,在这个基类里面将IServiceProvider作为一个抽象成员。这是因为MVC和测试基类的ServiceProvider来源不一样,一个是ABPvNext根据Application类已注册IoC容器构建的,另一个使用的是IHost测试主机内的ServiceProvider。
单元测试执行本质上就是将测试类进行实例化,然后调用对应的单元测试方法,所以测试基类的初始化动作都是放在对应的无参构造函数。
虽然Volo.Abp.UnitTest也是单独的一个项目,它的AbpTestBaseModule是没有任何动作,仅仅是为了同其他项目保持一致,内部是没有任何代码。
usingVolo.Abp.Modularity;namespaceVolo.Abp{publicclassAbpTestBaseModule:AbpModule{}}集成测试基类
一般来说,我们会直接从AbpIntegratedTest继承并实现我们需要的单元测试基类,包括ABPvNext官方的默认模版也是这样。集成测试基类的核心代码很简单,就是在无参构造函数的内部进行初始化动作,且在过程中按顺序执行两个生命周期方法。
简易流程图:
start=start:开始单元测试op1=operation:BeforeAddApplication()op2=operation:注册模块服务op3=operation:AfterAddApplication()op4=operation:模块初始化op5=operation:执行单元测试end=end:销毁相关资源
start-op1-op2-op3-op4-op5-end
publicabstractclassAbpIntegratedTestTStartupModule:AbpTestBaseWithServiceProvider,IDisposablewhereTStartupModule:IAbpModule{protectedIAbpApplicationApplication{get;}protectedoverrideIServiceProviderServiceProvider=Application.ServiceProvider;protectedIServiceProviderRootServiceProvider{get;}protectedIServiceScopeTestServiceScope{get;}protectedAbpIntegratedTest(){varservices=CreateServiceCollection();BeforeAddApplication(services);varapplication=services.AddApplicationTStartupModule(SetAbpApplicationCreationOptions);Application=application;AfterAddApplication(services); //根据已有IServiceCollection创建IoC容器。RootServiceProvider=CreateServiceProvider(services);TestServiceScope=RootServiceProvider.CreateScope(); //使用子容器对ABP模块系统进行初始化。application.Initialize(TestServiceScope.ServiceProvider);}//...其他代码。}
上述代码可以看到默认的测试基类并没有直接使用RootServiceProvider,而是创建了一个子容器给ABPvNext使用,这主要是为了后续可以对容器进行销毁操作,具体可一看下面的Dispose()方法。
publicvirtualvoidDispose(){Application.Shutdown();TestServiceScope.Dispose();Application.Dispose();}
总的来说,测试基类就是构建了一个IAbpApplication对象,根据传入的TStartupModule模块进入拓扑排序过程,依次执行各个模块的生命周期配置。
MVC测试基类针对我们的HttpApi层,如果需要对Controller进行测试的话,就需要从MVC测试基类继承编写单元/集成测试,各个类型的关系如下。
AbpTestBaseWithServiceProvider#ServiceProvider#GetService():T#GetRequiredService():TAbpIntegratedTestTStartupModule#BeforeAddApplication(IServiceCollectionservice)#SetAbpApplicationCreationOptions(AbpApplicationCreationOptionsoptions)#AfterAddApplication(IServiceCollectionservices)#CreateServiceProvider(IServiceCollectionservice)+Dispose()AbpAspNetCoreIntegratedTestBaseTStartup#TestServerServer#HttpClientClient-IHosthost#CreateHostBuilder():IHostBuilder#ConfigureServices(HostBuilderContextcontext,IServiceCollectionservices)#GetUrl_OfType_TController():string#GetUrl_OfType_TController(stringactionName):string#GetUrl_OfType_TController(stringactionName,objectqueryStringParamsAsAnonymousObject):string+Dispose()IDisposeinterfaceIDispose
针对AspNetCore来说,ABP创建了一个新的Host主机,在每次执行测试的时候会启动一个新的Web服务器。(并不会创建真实服务,不存在端口占用问题)
在基类当中,ABP定义了两个属性Server和Client,它们都是Mock了对应的接口,方便后续的单元测试,这里的ITestServerAccessor接口是用于MockAspNetCoreTestDynamicProxyHttpClientFactory接口所需要的。
AspNetCoreTestDynamicProxyHttpClientFactory接口是ABP底层进行动态代理所使用的,在请求远程服务的时候会调用这个接口创建HttpClient对象。
protectedAbpAspNetCoreIntegratedTestBase(){varbuilder=CreateHostBuilder();_host=builder.Build();_host.Start();Server=_host.GetTestServer();Client=_host.GetTestClient();ServiceProvider=Server.Services;ServiceProvider.GetRequiredServiceITestServerAccessor().Server=Server;}
从UML类图当中,可以看到基类定义了几个GetUrl()方法,这几个方法是根据Controller获取对应的请求路径。
这里我以一个SampleController控制器为例,它提供了一个Index方法,返回了一个页面内容。针对它来说,我们编写集成测试是这样操作的。
publicclassSimpleController:AbpController{publicActionResultIndex(){returnContent("Index-Result");}}
集成测试
publicclassSimpleController_Tests:AbpAspNetCoreIntegratedTestBaseStartup{protectedvirtualasyncTaskHttpResponseMessageGetResponseAsync(stringurl,HttpStatusCodeexpectedStatusCode=HttpStatusCode.OK){using(varrequestMessage=newHttpRequestMessage(HttpMethod.Get,url)){requestMessage.Headers.Add("Accept-Language",CultureInfo.CurrentUICulture.Name);varresponse=awaitClient.SendAsync(requestMessage);response.StatusCode.ShouldBe(expectedStatusCode);returnresponse;}}protectedvirtualasyncTaskstringGetResponseAsStringAsync(stringurl,HttpStatusCodeexpectedStatusCode=HttpStatusCode.OK){using(varresponse=awaitGetResponseAsync(url,expectedStatusCode)){returnawaitresponse.Content.ReadAsStringAsync();}}[Fact]publicasyncTaskActionResult_ContentResult(){varresult=awaitGetResponseAsStringAsync(GetUrlSimpleController(nameof(SimpleController.Index)));result.ShouldBe("Index-Result");}}EFCore的集成
在执行单元测试过程中,我们难免会对数据库进行操作。这个时候不可能连接真实数据库,就需要我们在测试基类当中进行一些初始化动作,将底层的数据库链接改为SQLite的内存模式。
publicclassSampleEntityFrameworkCoreTestModule:AbpModule{privateSqliteConnection_sqliteConnection;publicoverridevoidConfigureServices(ServiceConfigurationContextcontext){ConfigureInMemorySqlite(context.Services);}privatevoidConfigureInMemorySqlite(IServiceCollectionservices){//建立链接并执行迁移。_sqliteConnection=CreateDatabaseAndGetConnection();//使用SQLite作为EFProvider。services.ConfigureAbpDbContextOptions(options={options.Configure(context={context.DbContextOptions.UseSqlite(_sqliteConnection);});});}publicoverridevoidOnApplicationShutdown(ApplicationShutdownContextcontext){_sqliteConnection.Dispose();}privatestaticSqliteConnectionCreateDatabaseAndGetConnection(){//使用SQLite的内存模式链接字符串。varconnection=newSqliteConnection("DataSource=:memory:");connection.Open();varoptions=newDbContextOptionsBuilderSampleMigrationsDbContext().UseSqlite(connection).Options;//执行迁移,构建表结构。using(varcontext=newSampleMigrationsDbContext(options)){context.GetServiceIRelationalDatabaseCreator().CreateTables();}returnconnection;}}总结
ABP的测试更偏向于集成测试,因为各个功能都依赖于模块,所以在执行单元测试的时候会运行更长的时间。日常开发过程当中,我们更多地还是针对应用层进行测试就可以了,粒度更细的话也可以针对仓储层、领域层、API层编写测试即可。
为了保证项目质量,在开发完成之后编写单元/集成测试是每个开发人员应做的工作。编写单元/集成测试,虽然不能%避免BUG,但可以保证每次进行业务修改之后接口的正确性。
预览时标签不可点收录于话题#个上一篇下一篇转载请注明:http://www.sonphie.com/jbzl/14220.html