다음을 통해 공유


첫 번째 .NET.NET Aspire 테스트 작성

이 문서에서는 테스트 프로젝트를 만들고, 테스트를 작성하고, .NET.NET Aspire 솔루션에 대해 실행하는 방법을 알아봅니다. 이 문서의 테스트는 단위 테스트가 아니라 기능 또는 통합 테스트입니다. .NET .NET Aspire .NET .NET Aspire 리소스 종속성 및 해당 통신을 테스트하는 데 사용할 수 있는 테스트 프로젝트 템플릿 여러 변형이 포함되어 있습니다. 테스트 프로젝트 템플릿은 MSTest, NUnit 및 xUnit 테스트 프레임워크에 사용할 수 있으며 테스트의 시작점으로 사용할 수 있는 샘플 테스트를 포함합니다.

.NET .NET Aspire 테스트 프로젝트 템플릿은 📦Aspire.Hosting.Testing NuGet 패키지에 의존합니다. 이 패키지는 분산 애플리케이션에 대한 테스트 호스트를 만드는 데 사용되는 DistributedApplicationTestingBuilder 클래스를 노출합니다. 분산 애플리케이션 테스트 빌더는 DistributedApplication 클래스를 사용하여 앱 호스트만들고 시작합니다.

테스트 프로젝트 만들기

.NET .NET Aspire 테스트 프로젝트를 만드는 가장 쉬운 방법은 테스트 프로젝트 템플릿을 사용하는 것입니다. 새 .NET.NET Aspire 프로젝트를 시작하고 테스트 프로젝트를 포함하려는 경우 Visual Studio 도구에서 해당 옵션지원합니다. 기존 .NET.NET Aspire 프로젝트에 테스트 프로젝트를 추가하는 경우 dotnet new 명령을 사용하여 테스트 프로젝트를 만들 수 있습니다.

dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit

자세한 내용은 .NET CLI dotnet new 명령 설명서를 참조하세요.

테스트 프로젝트 탐색

다음 예제 테스트 프로젝트는 .NET.NET Aspire Starter 애플리케이션 템플릿의 일부로 만들어졌습니다. 익숙하지 않다면 빠른 시작: 첫 번째 .NET.NET Aspire 프로젝트빌드를 참고하세요. .NET .NET Aspire 테스트 프로젝트는 대상 앱 호스트에 대한 프로젝트 참조 종속성을 사용합니다. 템플릿 프로젝트를 고려합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Aspire.Hosting.Testing" Version="9.0.0" />
    <PackageReference Include="coverlet.collector" Version="6.0.2" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
    <PackageReference Include="xunit" Version="2.9.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="3.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="System.Net" />
    <Using Include="Microsoft.Extensions.DependencyInjection" />
    <Using Include="Aspire.Hosting.ApplicationModel" />
    <Using Include="Aspire.Hosting.Testing" />
    <Using Include="Xunit" />
  </ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>

  <PropertyGroup>
    <EnableMSTestRunner>true</EnableMSTestRunner>
    <OutputType>Exe</OutputType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Aspire.Hosting.Testing" Version="9.0.0" />
    <PackageReference Include="MSTest" Version="3.7.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="System.Net" />
    <Using Include="Microsoft.Extensions.DependencyInjection" />
    <Using Include="Aspire.Hosting.ApplicationModel" />
    <Using Include="Aspire.Hosting.Testing" />
    <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
  </ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Aspire.Hosting.Testing" Version="9.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
    <PackageReference Include="NUnit" Version="4.3.1" />
    <PackageReference Include="NUnit.Analyzers" Version="4.5.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="System.Net" />
    <Using Include="Microsoft.Extensions.DependencyInjection" />
    <Using Include="Aspire.Hosting.ApplicationModel" />
    <Using Include="Aspire.Hosting.Testing" />
    <Using Include="NUnit.Framework" />
  </ItemGroup>

</Project>

위의 프로젝트 파일은 상당히 표준입니다. PackageReference 에는 📦Aspire대한 Hosting.Testing NuGet 패키지가 있으며, 이 패키지는 .NET.NET Aspire 프로젝트에 대한 테스트를 작성하는 데 필요한 형식을 포함하고 있습니다.

템플릿 테스트 프로젝트에는 단일 테스트가 있는 IntegrationTest1 클래스가 포함됩니다. 테스트는 다음 시나리오를 확인합니다.

  • 앱 호스트가 성공적으로 만들어지고 시작됩니다.
  • webfrontend 리소스를 사용할 수 있고 실행 중입니다.
  • webfrontend 리소스에 대한 HTTP 요청을 수행하고 성공적인 응답(HTTP 200 OK)을 반환할 수 있습니다.

다음 테스트 클래스를 고려합니다.

namespace AspireApp.Tests;

public class IntegrationTest1
{
    [Fact]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });

        // To output logs to the xUnit.net ITestOutputHelper, 
        // consider adding a package from https://www.nuget.org/packages?q=xunit+logging

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

        var resourceNotificationService = app.Services
            .GetRequiredService<ResourceNotificationService>();

        await app.StartAsync();

        // Act
        var httpClient = app.CreateHttpClient("webfrontend");

        await resourceNotificationService.WaitForResourceAsync(
            "webfrontend",
            KnownResourceStates.Running
            )
            .WaitAsync(TimeSpan.FromSeconds(30));

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

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}
namespace AspireApp.Tests;

[TestClass]
public class IntegrationTest1
{
    [TestMethod]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });

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

        var resourceNotificationService = app.Services
            .GetRequiredService<ResourceNotificationService>();

        await app.StartAsync();

        // Act
        var httpClient = app.CreateHttpClient("webfrontend");

        await resourceNotificationService.WaitForResourceAsync(
                "webfrontend",
                KnownResourceStates.Running
            )
            .WaitAsync(TimeSpan.FromSeconds(30));
        
        var response = await httpClient.GetAsync("/");

        // Assert
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
    }
}
namespace AspireApp.Tests;

public class IntegrationTest1
{
    [Test]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });

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

        var resourceNotificationService = app.Services
            .GetRequiredService<ResourceNotificationService>();

        await app.StartAsync();

        // Act
        var httpClient = app.CreateHttpClient("webfrontend");

        await resourceNotificationService.WaitForResourceAsync(
                "webfrontend",
                KnownResourceStates.Running
            )
            .WaitAsync(TimeSpan.FromSeconds(30));
        
        var response = await httpClient.GetAsync("/");

        // Assert
        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    }
}

앞의 코드는 다음과 같습니다.

  • DistributedApplicationTestingBuilder.CreateAsync API를 사용하여 앱 호스트를 비동기적으로 만듭니다.
    • appHost 앱 호스트를 나타내는 IDistributedApplicationTestingBuilder 인스턴스입니다.
    • appHost 인스턴스에는 표준 HTTP 복원력 처리기로 구성된 서비스 컬렉션이 있습니다. 자세한 내용은 복원력 있는 HTTP 앱 빌드 :주요 개발 패턴을 참조하세요.
  • appHostIDistributedApplicationTestingBuilder.BuildAsync(CancellationToken) 메서드가 호출되며, DistributedApplication로서 app 인스턴스를 반환합니다.
  • HttpClient를 호출하여 webfrontend 리소스를 위한 app.CreateHttpClient가 생성됩니다.
  • resourceNotificationServicewebfrontend 리소스를 사용할 수 있고 실행될 준비가 될 때까지 기다리는 데 사용됩니다.
  • webfrontend 리소스의 루트에 대한 간단한 HTTP GET 요청이 이루어집니다.
  • 테스트는 응답 상태 코드가 OK인지 확인합니다.

리소스 환경 변수 테스트

.NET .NET Aspire 솔루션에서 리소스 및 해당 표현된 종속성을 추가로 테스트하려면 환경 변수가 올바르게 삽입되도록 어설션할 수 있습니다. 다음 예제에서는 webfrontend 리소스에 apiservice 리소스로 확인되는 HTTPS 환경 변수가 있는지 테스트하는 방법을 보여 줍니다.

using Aspire.Hosting;

namespace AspireApp.Tests;

public class EnvVarTests
{
    [Fact]
    public async Task WebResourceEnvVarsResolveToApiService()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        var frontend = (IResourceWithEnvironment)appHost.Resources
            .Single(static r => r.Name == "webfrontend");

        // Act
        var envVars = await frontend.GetEnvironmentVariableValuesAsync(
            DistributedApplicationOperation.Publish);

        // Assert
        Assert.Contains(envVars, static (kvp) =>
        {
            var (key, value) = kvp;

            return key is "services__apiservice__https__0"
                && value is "{apiservice.bindings.https.url}";
        });
    }
}
using Aspire.Hosting;

namespace AspireApp.Tests;

[TestClass]
public class EnvVarTests
{
    [TestMethod]
    public async Task WebResourceEnvVarsResolveToApiService()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        var frontend = (IResourceWithEnvironment)appHost.Resources
            .Single(static r => r.Name == "webfrontend");

        // Act
        var envVars = await frontend.GetEnvironmentVariableValuesAsync(
            DistributedApplicationOperation.Publish);

        // Assert
        CollectionAssert.Contains(envVars,
            new KeyValuePair<string, string>(
                key: "services__apiservice__https__0",
                value: "{apiservice.bindings.https.url}"));
    }
}
using Aspire.Hosting;

namespace AspireApp.Tests;

public class EnvVarTests
{
    [Test]
    public async Task WebResourceEnvVarsResolveToApiService()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        var frontend = (IResourceWithEnvironment)appHost.Resources
            .Single(static r => r.Name == "webfrontend");

        // Act
        var envVars = await frontend.GetEnvironmentVariableValuesAsync(
            DistributedApplicationOperation.Publish);

        // Assert
        Assert.That(envVars, Does.Contain(
            new KeyValuePair<string, string>(
                key: "services__apiservice__https__0",
                value: "{apiservice.bindings.https.url}")));
    }
}

앞의 코드는 다음과 같습니다.

요약

.NET Aspire 테스트 프로젝트 템플릿을 사용하면 .NET Aspire 솔루션에 대한 테스트 프로젝트를 더 쉽게 만들 수 있습니다. 템플릿 프로젝트에는 테스트의 시작점으로 사용할 수 있는 샘플 테스트가 포함되어 있습니다. DistributedApplicationTestingBuilderWebApplicationFactory<TEntryPoint>에서 ASP.NET Core의 익숙한 패턴을 따릅니다. 이를 통해 분산 애플리케이션에 대한 테스트 호스트를 만들고 이에 대한 테스트를 실행할 수 있습니다.

마지막으로 DistributedApplicationTestingBuilder 사용하는 경우 모든 리소스 로그는 기본적으로 DistributedApplication 리디렉션됩니다. 리소스 로그의 리디렉션을 사용하면 리소스가 올바르게 로깅되고 있음을 어설션하려는 시나리오를 사용할 수 있습니다.

참고 항목

  • xUnit 및 dotnet test를 사용하여 .NET에서 C#으로 단위 테스트
  • MSTest 개요
  • NUnit과 Core를 사용하여 C#의 단위 테스트