次の方法で共有


.NET .NET Aspire テストでアプリ ホストを管理する

.NET .NET Aspireを使用して機能テストまたは統合テストを記述する場合、アプリ ホスト インスタンスを効率的に管理することが重要です。 アプリ ホストは完全なアプリケーション環境を表し、作成と破棄にコストがかかる場合があります。 この記事では、.NET.NET Aspire テストでアプリ ホスト インスタンスを管理する方法について説明します。

.NET .NET Aspireを使用してテストを記述するには、いくつかのヘルパー クラスを含む 📦 Aspire.Hosting.Testing NuGet パッケージを使用して、テストのアプリ ホスト インスタンスを管理します。

DistributedApplicationTestingBuilder クラスを使用する

最初のテストの作成に関する チュートリアルでは、アプリ ホスト インスタンスの作成に使用できる クラスについて説明しました。

var appHost = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.AspireApp_AppHost>();

DistributedApplicationTestingBuilder.CreateAsync<T> メソッドは、アプリ ホスト インスタンスを作成するための汎用パラメーターとして、アプリ ホスト プロジェクトの種類を受け取ります。 このメソッドは各テストの開始時に実行されますが、テスト スイートの拡大に合わせてアプリ ホスト インスタンスを 1 回作成し、テスト間で共有する方が効率的です。

xUnit では、IAsyncLifetime インターフェイスをテスト クラスに実装して、アプリ ホスト インスタンスの非同期初期化と破棄をサポートします。 InitializeAsync メソッドは、テストを実行する前にアプリ ホスト インスタンスを作成するために使用され、DisposeAsync メソッドはテストが完了するとアプリ ホストを破棄します。

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 を使用して、アプリ ホスト インスタンスの初期化とクリーンアップを提供します。 ClassInitialize メソッドは、テストを実行する前にアプリ ホスト インスタンスを作成するために使用され、ClassCleanup メソッドはテストが完了するとアプリ ホスト インスタンスを破棄します。

[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 属性を使用し、アプリホストインスタンスのセットアップと破棄を行います。 OneTimeSetUp メソッドは、テストを実行する前にアプリ ホスト インスタンスを作成するために使用され、OneTimeTearDown メソッドはテストが完了するとアプリ ホスト インスタンスを破棄します。

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
    }
}

テストの実行が開始されたときにフィールドにアプリ ホストをキャプチャすることで、各テストでアプリ ホストにアクセスできます。再作成する必要がなく、テストの実行にかかる時間が短縮されます。 その後、テストの実行が完了すると、アプリ ホストが破棄され、テストの実行中に作成されたリソース (コンテナーなど) がクリーンアップされます。

アプリ ホストに引数を渡す

args パラメーターを使用して、アプリ ホストから引数にアクセスできます。 引数は .NETの構成システムにも渡されるため、この方法で多くの構成設定をオーバーライドできます。 次の例では、環境 をコマンド ライン オプションとして指定してオーバーライドします。

var builder = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.MyAppHost>(
    [
        "--environment=Testing"
    ]);

その他の引数は、アプリ ホスト Program に渡して、アプリ ホストで使用できます。 次の例では、アプリ ホストに引数を渡し、それを使用して、Postgres インスタンスにデータ ボリュームを追加するかどうかを制御します。

アプリ ホスト Programでは、ボリュームの有効化または無効化をサポートするために構成を使用します。

var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
    postgres.WithDataVolume();
}

テスト コードでは、args 内の "UseVolumes=false" をアプリ ホストに渡します。

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 クラスは多くのシナリオで役立ちますが、ビルダーの作成前やアプリ ホストのビルド後にコードを実行するなど、アプリ ホストの起動をより詳細に制御する必要がある場合があります。 このような場合は、独自のバージョンの DistributedApplicationFactory クラスを実装します。 これは、DistributedApplicationTestingBuilder が内部的に使用する内容です。

public class TestingAspireAppHost()
    : DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
    // override methods here
}

コンストラクターには、パラメーターとしてアプリ ホスト プロジェクト参照の型が必要です。 必要に応じて、基になるホスト アプリケーション ビルダーに引数を指定できます。 これらの引数は、アプリ ホストの起動方法を制御し、アプリ ホスト インスタンスを開始するために Program.cs ファイルによって使用される args 変数に値を提供します。

ライフサイクル メソッド

DistributionApplicationFactory クラスには、アプリ ホストの準備と作成全体を通じてカスタム動作を提供するためにオーバーライドできるいくつかのライフサイクル メソッドが用意されています。 使用可能なメソッドは、OnBuilderCreatingOnBuilderCreatedOnBuilding、および OnBuiltです。

たとえば、アプリ ホストが作成され、依存 Azure リソースがプロビジョニングされる前に、OnBuilderCreating メソッドを使用して、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 ファイル内の何よりも使用されます。

ライフサイクルで使用する必要があるもう 1 つのシナリオは、アプリ ホストによって使用されるサービスを構成することです。 次の例では、OnBuilderCreated API をオーバーライドして、HttpClientに回復性を追加するシナリオを考えてみましょう。

protected override void OnBuilderCreated(
    DistributedApplicationBuilder applicationBuilder)
{
    applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
    {
        clientBuilder.AddStandardResilienceHandler();
    });
}

関連項目