冠军

导航

[.NET Blog] .NET Aspire 测试入门

https://devblogs.microsoft.com/dotnet/getting-started-with-testing-and-dotnet-aspire/

自动化测试是软件开发的重要一环。它可以帮助我们尽早确认软件中的缺陷和防止回归问题。在本文中,我们将探讨如何在 .NET Aspire 中开始测试,支持我们进行跨分布式应用的测试场景。

测试分布式应用

分布式应用存在本质上的复杂性。你需要确保各种组件,例如数据库、缓存等等可用并且工作在正确的状态。然后,你的应用可能存在众多的服务需要一起进行测试。.NET Aspire 是非常棒的工具来帮助我们定义应用程序的环境,将众多的服务和资源连接在一起,进而比较容易地启动我们的环境。

对于端到端的测试也是这样,或者对于集成测试,我们需要确保数据库在期望的测试状态,避免其他的测试影响我们的测试,并且确保应用运行在正确的配置之下,这也是 .NET Aspire 可以提供帮助的地方。

谢天谢地,我们拥有了 .NET Aspire 的 Aspire.Hosting.Testing NuGet Package,它可以在这些方面帮助我们,让我们看一下我们可以如何使用它来编写测试用例。

入门

在开始的时候,我们将创建一个新的 .NET Aspire Starter App 项目,这将会创建一个新的使用了 AppHost 的 .NET Application,以及 Service Defaults, 和一个 API backend 和 一个 Blazor Web 前端项目。

首先确保你已经安装了 .NET Aspire workload

dotnet workload update
dotnet workload install aspire

然后,使用 aspire-starter 模板来创建新的项目。

dotnet new aspire-starter --name AspireWithTesting

然后,我们需要添加测试项目了,我们可以从 3 个测试框架中选择你希望的那个:

  • MSTest
  • xUnit
  • Nunit

例如,我们这里使用 MSTest。这可以通过使用 aspire-mstest 模板来完成。

dotnet new aspire-mstest --name AspireWithTesting.Tests
dotnet sln add AspireWithTesting.Tests

注意:在使用 aspire-starter 模板创建项目的时候,通过使用参数 --test-framework MSTest,你可以直接在新创建的项目中包含该测试项目。

在模板中已经引用了 Aspire-Hosting.Testing NuGet 包,以及选择的测试框架,在这里是 MSTest。所以,最后需要做的事情就是将测试项目添加到 AppHost 项目中。

dotnet add AspireWithTesting.Tests reference AspireWithTesting.AppHost

编写测试

你会发现这里已经提供了一个初始的测试文件,在上面描述中创建的测试项目中的 IntegrationTest1.cs。它提供了一个测试的示例,不过,我们还是从头编写一个,以便我们理解需要做的内容。创建一个新的名为 FrontEndTests.cs 的文件并试下下面的内容:

namespace AspireWithTesting.Tests;

[TestClass]
public class FrontEndTests
{
    [TestMethod]
    public async Task CanGetIndexPage()
    {
        var appHost =
            await DistributedApplicationTestingBuilder
                    .CreateAsync<Projects.AspireWithTesting_AppHost>();
        appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });

        await using var app = await appHost.BuildAsync();
        await app.StartAsync();

        var resourceNotificationService =
            app.Services.GetRequiredService<ResourceNotificationService>();
        await resourceNotificationService
            .WaitForResourceAsync("webfrontend", KnownResourceStates.Running)
            .WaitAsync(TimeSpan.FromSeconds(30));

        var httpClient = app.CreateHttpClient("webfrontend");
        var response = await httpClient.GetAsync("/");

        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
    }
}

好极了,测试已经写好了,让我们执行一下它。

dotnet test

如果所有的组件都在计划中执行,我们将应该看到如下的输出:

Test summary: total: 1, failed: 0, succeeded: 1, skipped: 0, duration: 0.9s

理解测试

让我分解上面的代码,理解在测试中发生了什么。

var appHost =
    await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireWithTesting_AppHost>();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
    clientBuilder.AddStandardResilienceHandler();
});

await using var app = await appHost.BuildAsync();
await app.StartAsync();

测试的第一部分是利用在 AppHost 项目中定义的所有资源、服务和它们的关系,然后启动它们,如同在 AppHost 项目中执行 dotnet run 一样。但是在测试项目中,我们可以控制一些额外的方面。例如,我们可以注入 StandardResilienceHandlerHttpClient 中,这样测试中可以可以用来与 AppHost 中的服务交互。在 AppHost 配置之后,我们构建整个应用,准备开始执行测试。

var resourceNotificationService =
    app.Services.GetRequiredService<ResourceNotificationService>();
await resourceNotificationService
    .WaitForResourceAsync("webfrontend", KnownResourceStates.Running)
    .WaitAsync(TimeSpan.FromSeconds(30));

因为 AppHost 将要启动多个不同的资源和服务,我们需要在基于它们执行我们的测试之前,确保它们可用。在这之后,如果 Web 应用没有正常启动,而我们试图访问它,我们就会得到一个错误。ResourceNotificationService 是用来帮助我们等待某个服务进入特定状态的服务。在我们的这个示例中,我们将等待 Webfrontend (这是配置在 AppHost 中的名称) 进入 Running 状态,我们使用 30s 的时间来等待。这个模式将需要对任何我们需要交互的服务使用,不管是直接还是间接。

var httpClient = app.CreateHttpClient("webfrontend");
var response = await httpClient.GetAsync("/");

Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

最后,我们可以从启动之后的应用中申请一个 HttpClient 的实例,提供希望访问的服务名称。这里将使用与以后的应用程序实际场景一样的服务发现机制,所以,我们不用担心服务的 URL 或者端口号。我们可以向服务发出请求,在我们的这个示例中,我们访问了 Web 前端的根目录 /,并检查我们是否得到一个 200 OK 的响应。来确认服务正确执行并如期望进行响应。

测试 API

测试 API 的方式非常类似于测试前端服务的方式,因为它返回的是数据,我们可以更进一步,可以根据返回的数据进行断言。

using System.Net.Http.Json;

namespace AspireWithTesting.Tests;

[TestClass]
public class ApiTests
{
    [TestMethod]
    public async Task CanGetWeatherForecast()
    {
        var appHost =
            await DistributedApplicationTestingBuilder.CreateAsync<Projects.AspireWithTesting_AppHost>();
        appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });

        await using var app = await appHost.BuildAsync();
        await app.StartAsync();

        var resourceNotificationService =
            app.Services.GetRequiredService<ResourceNotificationService>();
        await resourceNotificationService
                .WaitForResourceAsync("apiservice", KnownResourceStates.Running)
                .WaitAsync(TimeSpan.FromSeconds(30));

        var httpClient = app.CreateHttpClient("apiservice");
        var response = await httpClient.GetAsync("/weatherforecast");

        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

        var forecasts = await response.Content.ReadFromJsonAsync<IEnumerable<WeatherForecast>>();
        Assert.IsNotNull(forecasts);
        Assert.AreEqual(5, forecasts.Count());
    }

    record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary);
}

注意:由于这里涉及的 record 类型的 WeatherForecast 是在 API 项目中私有定义的,我们我们需要在测试项目中重新定义它,以便反序列化收到的 JSON 数据。

一旦我们断言 API 端点返回了期望的 200 OK 响应,我们可以进而反序列化收到的 JSON 响应为 WeatherForecast 对象的集合,并基于这些数据进一步断言。在我们的示例中,我们的 API 返回的是随机生成的数据,所以我们仅仅断言返回的 record 的数量,不过,如果我们的数据是来自于数据库中,那么,断言就可以根据期望的数据进行。

总结

在本文中,我们介绍了如何开始在 .NET Aspire 中进行测试。它使得我们可以处理分布式应用的测试场景。我们也看到了如何针对前端应用和 API 服务开发测试用例,确保它们运行并如期望进行响应。

Reference:

posted on 2024-09-30 10:18  冠军  阅读(35)  评论(0编辑  收藏  举报