ASP.NET Core 集成测试

2022年3月30日 1400点热度 0人点赞 0条评论
内容纲要

包:

Microsoft.AspNetCore.Mvc.Testing
Microsoft.AspNetCore.TestHost
Moq

集成测试可在包含应用支持基础结构(如数据库、文件系统和网络)的级别上确保应用组件功能正常。 ASP.NET Core 通过将单元测试框架与测试 Web 主机和内存中测试服务器结合使用来支持集成测试。

集成测试确认两个或更多应用组件一起工作以生成预期结果,可能包括完整处理请求所需的每个组件。

这些更广泛的测试用于测试应用的基础结构和整个框架,通常包括以下组件:

  • 数据库
  • 文件系统
  • 网络设备
  • 请求-响应管道
    单元测试使用称为 fake 或 mock 对象的制造组件,而不是基础结构组件。

与单元测试相比,集成测试:

  • 使用应用在生产环境中使用的实际组件。
  • 需要进行更多代码和数据处理。
  • 需要更长时间来运行。

基础结构组件(如测试 Web 主机和内存中测试服务器 (TestServer))由 TestServer 包提供或管理。 使用此包可简化测试创建和执行。

请勿为通过数据库和文件系统进行的数据和文件访问的每个可能排列编写集成测试。 无论应用中有多少位置与数据库和文件系统交互,一组集中式读取、写入、更新和删除集成测试通常能够充分测试数据库和文件系统组件。 将单元测试用于与这些组件交互的方法逻辑的例程测试。 在单元测试中,使用基础结构 fake/mock 会导致更快地执行测试。

Microsoft.AspNetCore.Mvc.Testing 包处理以下任务:

  • 将依赖项文件 (.deps) 从 SUT 复制到测试项目的 bin 目录中。

  • 将内容根目录设置为 SUT 的项目根目录,以便可在执行测试时找到静态文件和页面/视图。

  • 提供 WebApplicationFactory 类,以简化 SUT 在 中的启动过程。

快速创建一个 Web 服务器:

    public TestServer CreateServer()
    {
        var path = Assembly.GetAssembly(typeof(BasketScenarioBase))
            .Location;

        var hostBuilder = new WebHostBuilder()
            .UseContentRoot(Path.GetDirectoryName(path))
            .ConfigureAppConfiguration(cb =>
            {
                cb.AddJsonFile("appsettings.json", optional: false)
                .AddEnvironmentVariables();
            }).UseStartup<BasketTestsStartup>();

        return new TestServer(hostBuilder);
    }

接着是实际使用的 Startup,使用一个子类继承 Startup,再把它注入到 ASP.NET Core 中。

    class BasketTestsStartup : Startup
    {
        public BasketTestsStartup(IConfiguration env) : base(env)
        {
        }

        public override IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.Configure<RouteOptions>(Configuration);
            return base.ConfigureServices(services);
        }

        protected override void ConfigureAuth(IApplicationBuilder app)
        {
            if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())
            {
                app.UseMiddleware<AutoAuthorizeMiddleware>();
            }
            else
            {
                base.ConfigureAuth(app);
            }
        }
    }

然后通过创建 TestServer 服务器对象,再创建一个对应的客户端即可。

    [Fact]
    public async Task Post_basket_and_response_ok_status_code()
    {
        using (var server = CreateServer())
        {
            var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
            var response = await server.CreateClient()
                .PostAsync("url", content);     // url只需要填写 /api/xxx 不需要填写主机地址

            response.EnsureSuccessStatusCode();
        }
    }

这样便可以请求任一个 api 了。

另外还可以从 Web Host 中取出一个实例:

            using (var server = CreateServer())
            {
                var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();

                var redisBasketRepository = BuildBasketRepository(redis);

                var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
                {
                    BuyerId = "buyerId",
                    Items = BuildBasketItems()
                });

                Assert.NotNull(basket);
                Assert.Single(basket.Items);
            }

可以通过在主机生成器上调用 ConfigureTestServices,在测试中替代服务。 若要注入模拟服务,SUT 必须具有包含 Startup.ConfigureServices 方法的 类。

    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

设置 Web Host 为开发环境或测试环境:

protected override IHostBuilder CreateHostBuilder() =>
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

// 如果 SUT 使用 Web 主机 (IWebHostBuilder),则替代 CreateWebHostBuilder:

protected override IWebHostBuilder CreateWebHostBuilder() =>
    base.CreateWebHostBuilder().UseEnvironment("Testing");

而在集成测试中,也可以很方便 配置加载的 appsettings.json、appsettings.development.json、appsettngs.production.json 文件。

        config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

痴者工良

高级程序员劝退师

文章评论