Zarządzanie hostem aplikacji w testach .NET.NET Aspire
Podczas pisania testów funkcjonalnych lub integracyjnych za pomocą .NET.NET Aspirezarządzanie hostem aplikacji jest niezwykle istotne. Host aplikacji reprezentuje pełne środowisko aplikacji i może być kosztowny do utworzenia i usunięcia. W tym artykule wyjaśniono, jak zarządzać instancją hosta aplikacji w testach .NET.NET Aspire.
Do pisania testów za pomocą .NET.NET Aspirenależy użyć pakietu 📦 Aspire.Hosting.Testing
NuGet, który zawiera klasy pomocnicze do zarządzania instancją hosta aplikacji w testach.
Używanie klasy DistributedApplicationTestingBuilder
W samouczku o pisaniu twojego pierwszego testuzostała przedstawiona klasa DistributedApplicationTestingBuilder, której można użyć do utworzenia instancji hosta aplikacji.
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
Metoda DistributedApplicationTestingBuilder.CreateAsync<T>
przyjmuje typ projektu hosta aplikacji jako parametr ogólny, aby stworzyć jego instancję. Mimo że ta metoda jest wykonywana na początku każdego z testów, wydajniejsze jest utworzenie wystąpienia hosta aplikacji raz i udostępnienie go w miarę rozrostu zestawu testów.
Za pomocą frameworku xUnit stosujesz interfejs IAsyncLifetime w klasie testowej, aby obsługiwać asynchroniczne inicjowanie i usuwanie instancji hosta aplikacji. Metoda InitializeAsync
służy do tworzenia wystąpienia hosta aplikacji przed uruchomieniem testów, a metoda DisposeAsync
usuwa hosta aplikacji po zakończeniu testów.
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
}
}
Za pomocą MSTest używasz metod statycznych ClassInitializeAttribute i ClassCleanupAttribute klasy testowej, aby inicjować i usuwać wystąpienia hosta aplikacji. Metoda ClassInitialize
służy do tworzenia wystąpienia hosta aplikacji przed uruchomieniem testów, a metoda ClassCleanup
usuwa wystąpienie hosta aplikacji po zakończeniu testów.
[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
}
}
W przypadku narzędzia NUnit używasz atrybutów OneTimeSetUp i OneTimeTearDown w metodach klasy testowej do przeprowadzenia konfiguracji i usuwania instancji hosta aplikacji. Metoda OneTimeSetUp
służy do tworzenia wystąpienia hosta aplikacji przed uruchomieniem testów, a metoda OneTimeTearDown
usuwa wystąpienie hosta aplikacji po zakończeniu testów.
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
}
}
Przechwytując hosta aplikacji w polu po uruchomieniu testu, można uzyskać do niego dostęp w każdym teście bez konieczności ponownego tworzenia go, skracając czas potrzebny do uruchomienia testów. Następnie po zakończeniu przebiegu testu host aplikacji zostanie usunięty, co spowoduje wyczyszczenie wszystkich zasobów utworzonych podczas przebiegu testu, takich jak kontenery.
Przekazywanie argumentów do hosta aplikacji
Możesz uzyskać dostęp do argumentów z hosta aplikacji przy użyciu parametru args
. Argumenty są również przekazywane do systemu konfiguracji .NET, dzięki czemu można tym sposobem przesłonić liczne ustawienia konfiguracyjne. W poniższym przykładzie zastąpisz środowisko , określając je jako opcję wiersza polecenia:
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyAppHost>(
[
"--environment=Testing"
]);
Inne argumenty można przekazać do Twojego hosta aplikacji Program
i udostępnić w Twoim hoście aplikacji. W następnym przykładzie przekażesz argument do hosta aplikacji i użyjesz go do kontrolowania, czy dodasz woluminy danych do wystąpienia Postgres.
W hoście aplikacji Program
używasz konfiguracji do włączania lub wyłączania woluminów.
var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
postgres.WithDataVolume();
}
W kodzie testowym przekazujesz "UseVolumes=false"
w args
do hosta aplikacji:
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>());
}
Używanie klasy DistributedApplicationFactory
Chociaż klasa DistributedApplicationTestingBuilder
jest przydatna w wielu scenariuszach, mogą wystąpić sytuacje, w których potrzebujesz większej kontroli nad uruchomieniem hosta aplikacji, takich jak wykonywanie kodu przed utworzeniem konstruktora lub po skompilowaniu hosta aplikacji. W takich przypadkach należy zaimplementować własną wersję klasy DistributedApplicationFactory. Jest to to, czego DistributedApplicationTestingBuilder
używa wewnętrznie.
public class TestingAspireAppHost()
: DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
// override methods here
}
Konstruktor wymaga typu odwołania do projektu hosta aplikacji jako parametru. Opcjonalnie możesz podać argumenty dla bazowego konstruktora aplikacji hosta. Te argumenty kontrolują sposób uruchamiania hosta aplikacji i udostępniają wartości zmiennej args używanej przez plik Program.cs w celu uruchomienia wystąpienia hosta aplikacji.
Metody cyklu życia
Klasa DistributionApplicationFactory
udostępnia kilka metod cyklu życia, które można nadpisać, aby wprowadzić niestandardowe zachowanie podczas przygotowywania i tworzenia hosta aplikacji. Dostępne metody to OnBuilderCreating
, OnBuilderCreated
, OnBuilding
i OnBuilt
.
Na przykład możemy użyć metody OnBuilderCreating
, aby ustawić konfigurację, w tym informacje o subskrypcji i grupie zasobów dla Azure, zanim zostanie utworzony host aplikacji i zaaprovisionowane zostaną wszystkie zasoby zależne Azure, co pozwoli na przeprowadzenie testów w poprawnym środowisku 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";
}
}
Ze względu na kolejność pierwszeństwa w systemie konfiguracji .NET zmienne środowiskowe będą używane zamiast elementów w pliku appsettings.json i secrets.json.
Innym scenariuszem, którego możesz użyć w cyklu życia, jest skonfigurowanie usług używanych przez hosta aplikacji. W poniższym przykładzie rozważ scenariusz, w którym nadpiszesz interfejs API OnBuilderCreated
, aby zwiększyć odporność systemu HttpClient
.
protected override void OnBuilderCreated(
DistributedApplicationBuilder applicationBuilder)
{
applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
}