学习·进步
老张的哲学
不定期更新的
日常
在平时的开发中,我们很少会关注到测试的问题,更别说集成测试了,除非是公司有硬性要求或者是自己的开源项目中,为了整体架构的完整性,需要用测试来做辅助点缀,而更多的也仅仅是单元测试(说的就是我自己????),最近在写书的时候才进一步考虑到这一点,如何在一个ASP.NET Core框架中,引入集成测试呢?
这里我结合这三年开源的经验,总结了一些心得,给大家分享一下,如果有更好的建议,欢迎在评论区进行留言哟。
PS:单元测试就不说了,比较简单,最多就是依赖注入和MOCK的问题,不会的话也可以留言。
方案一:万物皆可Mock
在软件测试当中,我们经常,甚至是到处都会用到mock来处理对象实例化的问题,在单元测试中,mock十分常见,毕竟是为了测试一个小模块,其他的就不需要考虑,直接mock就行了,如果在集成测试的时候,如何测试接口呢,比如BlogController如何使用?我在blog.core项目中,就是这么使用到的,示例代码如下:
Mock<IBlogArticleServices> mockBlogSev = new Mock<IBlogArticleServices>();Mock<ILogger<BlogController>> mockLogger = new Mock<ILogger<BlogController>>();BlogController blogController;private IBlogArticleServices blogArticleServices;DI_Test dI_Test = new DI_Test();public BlogController_Should(){mockBlogSev.Setup(r => r.Query());var container = dI_Test.DICollections();blogArticleServices = container.Resolve<IBlogArticleServices>();blogController = new BlogController(mockLogger.Object);}
说句实话,这并非是集成测试,这种写法可能比较低端,通过mock配合new,创建了控制器,然后调用接口,看起来不是很高大上,而且集成测试本来就是要测试整体性,不能把所有的参数都mock吧。同时官方好像也说过,不要到处使用mock。
而且,这种方案,也要考虑如何使用依赖注入的问题!
所以这种方案做集成测试我给:
⭐⭐
方案二:实例化TestServer对象
这种是比较常见的,也是微软官方架构项目eShopOnContainers的推荐方案,简单来说,就是微软提供了一个TestSever的类,为我们提供一个类似WebHost的宿主服务器,只不过是测试服务器,那如何测试Controller控制器呢,示例代码如下:
public TestServer CreateServer(){var path = Assembly.GetAssembly(typeof(CatalogScenariosBase)).Location;var hostBuilder = new WebHostBuilder().UseContentRoot(Path.GetDirectoryName(path)).ConfigureAppConfiguration(cb =>{cb.AddJsonFile("appsettings.json", optional: false).AddEnvironmentVariables();}).UseStartup<Startup>();var testServer = new TestServer(hostBuilder);testServer.Host.MigrateDbContext<CatalogContext>((context, services) =>{var env = services.GetService<IWebHostEnvironment>();var settings = services.GetService<IOptions<CatalogSettings>>();var logger = services.GetService<ILogger<CatalogContextSeed>>();new CatalogContextSeed().SeedAsync(context, env, settings, logger).Wait();}).MigrateDbContext<IntegrationEventLogContext>((_, __) => { });return testServer;}
可以看到,通过new TestServer()的方式,生成一个服务器,就可以发起请求了,核心的还是我们的WebHostBuilder。
至于如何调用就更简单了,直接对server发起HttpClient请求即可:
using (var server = CreateServer()){var response = await server.CreateClient().GetAsync(Get.ItemById(1));response.EnsureSuccessStatusCode();}
这种是很简单的,而且也不用考虑mock的问题,毕竟用的直接就是web项目的WebHost宿主机Builder来构建的。
但是有一个很致命的问题,我们在.NET5以后,使用Autofac做依赖注入的容器,而且ConfigureServices也是没有返回值的,这样在使用上面的TestServer,就会报错,提示找不到Autofac服务。
但是如果你查看eShopOnContainers的源码后,就知道他们还是将ConfigureServices做了返回值处理:
public IServiceProvider ConfigureServices(IServiceCollection servic{// 自定义服务扩展services.AddAppInsight(Configuration)// and so on....AddCustomMVC(Configuration);// 使用Autofac依赖注入容器var container = new ContainerBuilder();container.Populate(services);return new AutofacServiceProvider(container.Build());}
如果你能接受这种依赖注入的方式的话,也是可以使用这种方案的,这是一个注意点,要知道。
所以这种方案做集成测试我给:
⭐⭐⭐⭐
方案三:使用.UseTestServer()
除了上面的这种方式,还有一种方式,也是官方提供的,比较类似,也是通过创建宿主机服务器的形式,不过是新的HostBuilder的ConfigureWebHostDefaults方式创建的,示例代码如下:
public static IHostBuilder GetTestHost()
{return new HostBuilder()//替换autofac作为DI容器.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseTestServer().UseStartup<Startup>();}).ConfigureAppConfiguration((host, builder) =>{builder.SetBasePath(Directory.GetCurrentDirectory());builder.AddJsonFile("appsettings.json", optional: true);builder.AddEnvironmentVariables();});
}
既然上面说了我们不能单独处理自定义容器,我们就和之前一样,指定就好,设计思路和我们的WebApi中的Program.cs特别像,然后使用起来就更加简单了:
using var server = await ArticleScenariosBase.GetTestHost().StartAsync();// Action 发起接口请求var response = await server.GetTestClientWithToken().GetAsync("/api/blogs?page=1&pageSize=5");// Assert 确保接口状态码是200response.EnsureSuccessStatusCode();
这种方案不仅兼容了第二种方案的优点,而且对之前我们设计的Autofac依赖注入容器没有做任何的修改。
所以这种方案做集成测试我给:
⭐⭐⭐⭐⭐
编者按:Blog.Core开源三周年
【原料】
个人开源项目Blog.Core马上就已经开源三周年了,经过许许多多的小伙伴功能努力的结果,希望给ASP.NET Core在国内的推广,提供一个落地级别的案例。
【制法】
A、累计提交上千次Commit;
B、配合前、后、认证、鉴权一体化方案;
C、不完全统计,被60+公司使用中;
【调味】
1.希望更多的小伙伴参与并提交PR。
2.希望更多的公司和组织使用,提供宝贵生产意见。
3.希望可以得到组织的孵化,让项目更进一步,有意者可以联系我。
Tips: 九月新内容,敬请期待。
HAPPY EVEY DAY!