使用 编写功能或集成测试 Aspire时,有效管理 AppHost 实例至关重要。 AppHost 表示完整的应用程序环境,创建和拆解成本可能很高。 本文介绍如何在你的Aspire测试中管理 AppHost 实例。
若要使用 Aspire 编写测试,请使用 📦 Aspire.Hosting.Testing 包含一些帮助程序类的 NuGet 包来管理测试中的 AppHost 实例。
使用 DistributedApplicationTestingBuilder 类
在 编写第一次测试的教程中,你被介绍到 DistributedApplicationTestingBuilder 可用于创建 AppHost 实例的类:
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
该方法 DistributedApplicationTestingBuilder.CreateAsync<T> 采用 AppHost 项目类型作为泛型参数来创建 AppHost 实例。 虽然此方法在每个测试开始时执行,但创建 AppHost 实例一次,并在测试套件增长时在测试之间共享它更为高效。
借助 xUnit,可以在测试类上实现 IAsyncLifetime 接口,以支持 AppHost 实例的异步初始化和处置。 该方法 InitializeAsync 用于在运行测试之前创建 AppHost 实例,在测试完成后, DisposeAsync 该方法将释放 AppHost。
public class WebTests : IAsyncLifetime
{
private DistributedApplication _app;
public async Task InitializeAsync()
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
public async Task DisposeAsync() => await _app.DisposeAsync();
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// test code here
}
}
使用 MSTest 时,可以使用ClassInitializeAttributeClassCleanupAttribute测试类的静态方法来提供 AppHost 实例的初始化和清理。 该方法 ClassInitialize 用于在测试运行之前创建 AppHost 实例,并在 ClassCleanup 测试完成后释放 AppHost 实例。
[TestClass]
public class WebTests
{
private static DistributedApplication _app;
[ClassInitialize]
public static async Task ClassInitialize(TestContext context)
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
[ClassCleanup]
public static async Task ClassCleanup() => await _app.DisposeAsync();
[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// test code here
}
}
使用 NUnit,可以在测试类的方法上使用 OneTimeSetUp 和 OneTimeTearDown 属性来提供 AppHost 实例的设置和拆解。 该方法 OneTimeSetUp 用于在测试运行之前创建 AppHost 实例,并在 OneTimeTearDown 测试完成后释放 AppHost 实例。
public class WebTests
{
private DistributedApplication _app;
[OneTimeSetUp]
public async Task OneTimeSetup()
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
[OneTimeTearDown]
public async Task OneTimeTearDown() => await _app.DisposeAsync();
[Test]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// test code here
}
}
通过在启动测试运行时捕获字段中的 AppHost,可以在每个测试中访问它,而无需重新创建它,从而减少运行测试所需的时间。 然后,测试运行完成后,将释放 AppHost,这将清理在测试运行期间创建的任何资源,例如容器。
将参数传递给 AppHost
可以使用参数从 AppHost args 访问参数。 参数也传递给 .NET的配置系统,因此你可以这样替代许多配置设置。 在以下示例中,通过将其指定为命令行选项来替代 环境:
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyAppHost>(
[
"--environment=Testing"
]);
其他参数可以传递给 AppHost Program 并在 AppHost 中提供。 在下一个示例中,将参数传递给 AppHost,并使用它来控制是否向实例添加数据卷 Postgres 。
在 AppHost Program中,使用配置来支持启用或禁用卷:
var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
postgres.WithDataVolume();
}
在测试代码中"UseVolumes=false",将传入 args AppHost:
public async Task DisableVolumesFromTest()
{
// Disable volumes in the test builder via arguments:
using var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.TestingAppHost1_AppHost>(
[
"UseVolumes=false"
]);
// The container will have no volume annotation since we disabled volumes by passing UseVolumes=false
var postgres = builder.Resources.Single(r => r.Name == "postgres1");
Assert.Empty(postgres.Annotations.OfType<ContainerMountAnnotation>());
}
使用 DistributedApplicationFactory 类
虽然该 DistributedApplicationTestingBuilder 类适用于许多方案,但在某些情况下,你可能希望更好地控制启动 AppHost,例如在创建生成器之前执行代码或在生成 AppHost 之后执行代码。 在这些情况下,你将实现自己的 DistributedApplicationFactory 类版本。 这是 DistributedApplicationTestingBuilder 在内部使用的内容。
public class TestingAspireAppHost()
: DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
// override methods here
}
构造函数需要 AppHost 项目引用的类型作为参数。 (可选)可以向基础主机应用程序生成器提供参数。 这些参数控制 AppHost 的启动方式,并向 AppHost.cs 文件用来启动 AppHost 实例的 args 变量提供值。
生命周期方法
该 DistributionApplicationFactory 类提供了多个生命周期方法,可以重写这些方法,以便在整个 AppHost 的准备和创建过程中提供自定义行为。 可用的方法是 OnBuilderCreating、OnBuilderCreated、OnBuilding和 OnBuilt。
例如,我们可以在创建 AppHost 之前使用 OnBuilderCreating 该方法设置配置(例如订阅和资源组信息 Azure),并预配任何依赖 Azure 资源,从而使用正确的 Azure 环境进行测试。
public class TestingAspireAppHost() : DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
protected override void OnBuilderCreating(DistributedApplicationOptions applicationOptions, HostApplicationBuilderSettings hostOptions)
{
hostOptions.Configuration ??= new();
hostOptions.Configuration["environment"] = "Development";
hostOptions.Configuration["AZURE_SUBSCRIPTION_ID"] = "00000000-0000-0000-0000-000000000000";
hostOptions.Configuration["AZURE_RESOURCE_GROUP"] = "my-resource-group";
}
}
由于 .NET 配置系统中的优先级顺序,环境变量的优先级高于 appsettings.json 或 secrets.json 文件中的任何内容。
你可能要在生命周期中使用的另一种方案是配置 AppHost 使用的服务。 在以下示例中,请考虑通过覆写 OnBuilderCreated API 来提高 HttpClient 的容错性:
protected override void OnBuilderCreated(
DistributedApplicationBuilder applicationBuilder)
{
applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
}