Udostępnij za pośrednictwem


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 Programuż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, OnBuildingi 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();
    });
}

Zobacz też