다음을 통해 공유


Microsoft.Testing.Platform 확장성

Microsoft.Testing.Platform은 테스트 프레임워크와 프로세스 내 또는 Out-of-process에서 작동할 수 있는 확장의 수 구성됩니다.

아키텍처 섹션에 설명된 대로 Microsoft.Testing.Platform은 다양한 시나리오 및 확장성 지점을 수용하도록 설계되었습니다. 기본 및 필수 확장은 의심할 여지 없이 테스트가 활용할 테스트 프레임워크입니다. 이 등록에 실패하면 시작 오류가 발생합니다. 테스트 프레임워크 테스트 세션을 실행하는 데 필요한 유일한 필수 확장입니다.

테스트 보고서 생성, 코드 검사, 실패한 테스트 재시도 및 기타 잠재적 기능과 같은 시나리오를 지원하려면 다른 확장이 테스트 프레임워크 함께 작동하여 테스트 프레임워크 자체에서 기본적으로 제공하지 않는 이러한 기능을 제공할 수 있는 메커니즘을 제공해야 합니다.

기본적으로 테스트 프레임워크 테스트 제품군을 구성하는 각 테스트에 대한 정보를 제공하는 기본 확장입니다. 특정 테스트가 성공했는지, 실패했는지, 건너뛰었는지 보고하고, 사람이 읽을 수 있는 이름(표시 이름이라고 함), 원본 파일 및 테스트가 시작되는 줄 등 각 테스트에 대한 추가 정보를 제공할 수 있습니다.

확장성 지점을 사용하면 테스트 프레임워크 제공하는 정보를 활용하여 새 아티팩트 생성 또는 추가 기능으로 기존 아티팩트 향상을 수행할 수 있습니다. 일반적으로 사용되는 확장은 TRX 보고서 생성기입니다. 이 생성기는 TestNodeUpdateMessage 구독하고 이 생성기에서 XML 보고서 파일을 생성합니다.

아키텍처설명한 것처럼 테스트 프레임워크동일한 프로세스 내에서 작동할 수 없는 특정 확장 지점이 있습니다. 그 이유는 일반적으로 다음과 같습니다.

  • 테스트 호스트환경 변수을 수정해야 합니다. 테스트 호스트 프로세스 자체 내에서 작동하는 것은 너무 늦습니다.
  • 테스트 및 사용자 코드가 실행되는 테스트 호스트프로세스 자체를 불안정한 렌더링하는 사용자 코드 버그가 있을 수 있으므로 모니터링해야 외부의 프로세스를 모니터링해야잠재적인 중단되거나 충돌발생할 수 있습니다. 이러한 경우, 확장은 테스트 호스트 프로세스와 함께 충돌하거나 중단됩니다.

이러한 이유로 확장 지점은 다음 두 가지 유형으로 분류됩니다.

  1. 프로세스 내 확장: 이 확장은 테스트 프레임워크의 동일한 프로세스 내에서 작동합니다.

    속성을 통해 ITestApplicationBuilder.TestHost을 등록할 수 있습니다.

    // ...
    var builder = await TestApplication.CreateBuilderAsync(args);
    builder.TestHost.AddXXX(...);
    // ...
    
  2. 프로세스 외부 확장: 이러한 확장은 별도의 프로세스에서 작동하여 테스트 호스트 자체의 영향을 받지 않고 테스트 호스트를 모니터링할 수 있습니다.

    out-of-process 확장ITestApplicationBuilder.TestHostControllers를 통해 등록할 수 있습니다.

    var builder = await TestApplication.CreateBuilderAsync(args);
    builder.TestHostControllers.AddXXX(...);
    

    마지막으로, 일부 확장은 두 시나리오에서 모두 작동하도록 설계되었습니다. 이러한 일반 확장 기능은 호스트에서 동일하게 작동합니다. TestHostTestHostController 인터페이스를 통해 또는 ITestApplicationBuilder 수준에서 직접 이러한 확장을 등록할 수 있습니다. 이러한 확장의 예는 ICommandLineOptionsProvider.

IExtension 인터페이스

IExtension 인터페이스는 테스트 플랫폼 내의 모든 확장성 지점에 대한 기본 인터페이스 역할을 합니다. 확장에 대한 설명 정보를 가져오는 데 주로 사용되며, 가장 중요한 것은 확장 자체를 사용하거나 사용하지 않도록 설정하는 데 사용됩니다.

다음 IExtension 인터페이스를 고려합니다.

public interface IExtension
{
    string Uid { get; }
    string Version { get; }
    string DisplayName { get; }
    string Description { get; }
    Task<bool> IsEnabledAsync();
}
  • Uid: 확장의 고유 식별자를 나타냅니다. 다른 확장과 충돌하지 않도록 이 문자열의 고유한 값을 선택하는 것이 중요합니다.

  • Version: 인터페이스의 버전을 나타냅니다. 의미 체계 버전 관리는 이 필요합니다.

  • DisplayName: 로그에 표시되고 --info 명령줄 옵션을 사용하여 정보를 요청할 때 사용자에게 친숙한 이름 표현입니다.

  • Description: --info 명령줄 옵션을 사용하여 정보를 요청할 때 나타나는 확장에 대한 설명입니다.

  • IsEnabledAsync(): 확장이 인스턴스화될 때 테스트 플랫폼에서 이 메서드를 호출합니다. 메서드가 false반환하면 확장이 제외됩니다. 이 메서드는 일반적으로 구성 파일 또는 일부 사용자 지정 명령줄 옵션따라 결정을 내립니다. 사용자는 종종 명령줄에서 --customExtensionOption 지정하여 확장 자체를 옵트인합니다.

테스트 프레임워크 확장

테스트 프레임워크는 테스트를 검색하고 실행할 수 있는 기능을 테스트 플랫폼에 제공하는 기본 확장입니다. 테스트 프레임워크는 테스트 결과를 테스트 플랫폼에 다시 전달하는 역할을 담당합니다. 테스트 프레임워크는 테스트 세션을 실행하는 데 필요한 유일한 필수 확장입니다.

테스트 프레임워크 등록

이 섹션에서는 테스트 플랫폼에 테스트 프레임워크를 등록하는 방법을 설명합니다. TestApplication.RegisterTestFramework 설명서와 같이 API를 사용하여 테스트 애플리케이션 작성기당 하나의 테스트 프레임워크만 등록합니다.

등록 API는 다음과 같이 정의됩니다.

ITestApplicationBuilder RegisterTestFramework(
    Func<IServiceProvider, ITestFrameworkCapabilities> capabilitiesFactory,
    Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework> adapterFactory);

RegisterTestFramework API는 두 개의 공장을 기대합니다.

  1. Func<IServiceProvider, ITestFrameworkCapabilities>: IServiceProvider 인터페이스를 구현하는 개체를 수락하고 ITestFrameworkCapabilities 인터페이스를 구현하는 개체를 반환하는 대리자입니다. 이 IServiceProvider 구성, 로거 및 명령줄 인수와 같은 플랫폼 서비스에 대한 액세스를 제공합니다.

    ITestFrameworkCapabilities 인터페이스는 테스트 프레임워크에서 지원하는 기능을 플랫폼 및 확장에 알리는 데 사용됩니다. 이를 통해 플랫폼 및 확장은 특정 동작을 구현하고 지원하여 올바르게 상호 작용할 수 있습니다. 기능의개념을 더 잘 이해하려면 해당 섹션을 참조하세요.

  2. Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework>: 에 의해 반환된 Func<IServiceProvider, ITestFrameworkCapabilities> 개체와 IServiceProvider를 수용하여 플랫폼 서비스에 대한 액세스를 제공하는 대리자입니다. 예상되는 반환 개체는 ITestFramework 인터페이스를 구현하는 개체입니다. ITestFramework 테스트를 검색하고 실행한 다음, 결과를 테스트 플랫폼에 다시 전달하는 실행 엔진 역할을 합니다.

플랫폼이 ITestFrameworkCapabilities 만들기 및 ITestFramework 만들기를 구분해야 하는 것은 지원되는 기능이 현재 테스트 세션을 실행하기에 충분하지 않은 경우 테스트 프레임워크를 만들지 않도록 하기 위한 최적화입니다.

빈 기능 집합을 반환하는 테스트 프레임워크 등록을 보여 주는 다음 사용자 코드 예제를 살펴보겠습니다.

internal class TestingFrameworkCapabilities : ITestFrameworkCapabilities
{
    public IReadOnlyCollection<ITestFrameworkCapability> Capabilities => [];
}

internal class TestingFramework : ITestFramework
{
   public TestingFramework(ITestFrameworkCapabilities capabilities, IServiceProvider serviceProvider)
   {
       // ...
   }
   // Omitted for brevity...
}

public static class TestingFrameworkExtensions
{
    public static void AddTestingFramework(this ITestApplicationBuilder builder)
    {
        builder.RegisterTestFramework(
            _ => new TestingFrameworkCapabilities(),
            (capabilities, serviceProvider) => new TestingFramework(capabilities, serviceProvider));
    }
}

// ...

이제 등록 코드를 사용하여 이 예제의 해당 진입점을 고려합니다.

var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args);
// Register the testing framework
testApplicationBuilder.AddTestingFramework();
using var testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();

메모

ITestFrameworkCapabilities 반환해도 테스트 세션의 실행을 방지할 수 없습니다. 모든 테스트 프레임워크는 테스트를 검색하고 실행할 수 있어야 합니다. 테스트 프레임워크에 특정 기능이 없는 경우 옵트아웃할 수 있는 확장으로 영향을 제한해야 합니다.

테스트 프레임워크 만들기

Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework 테스트 프레임워크를 제공하는 확장에 의해 구현됩니다.

public interface ITestFramework : IExtension
{
    Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context);
    Task ExecuteRequestAsync(ExecuteRequestContext context);
    Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context);
}

ITestFramework 인터페이스는 모든 확장 지점이 상속하는 인터페이스인 IExtension 인터페이스에서 상속됩니다. IExtension 확장의 이름과 설명을 검색하는 데 사용됩니다. 또한 IExtensionTask<bool> IsEnabledAsync()통해 설정에서 확장을 동적으로 사용하거나 사용하지 않도록 설정하는 방법을 제공합니다. 특별한 요구 사항이 없는 경우 이 메서드에서 true 반환해야 합니다.

CreateTestSessionAsync 메서드

CreateTestSessionAsync 메서드는 테스트 세션 시작 시 호출되며 테스트 프레임워크를 초기화하는 데 사용됩니다. API는 CloseTestSessionContext 개체를 허용하고 CloseTestSessionResult반환합니다.

public sealed class CreateTestSessionContext : TestSessionContext
{
    public SessionUid SessionUid { get; }
    public ClientInfo Client { get; }
    public CancellationToken CancellationToken { get; }
}

public readonly struct SessionUid
{
    public string Value { get; }
}

public sealed class ClientInfo
{
    public string Id { get; }
    public string Version { get; }
}

SessionUid 현재 테스트 세션의 고유 식별자로 사용되며 세션 결과에 대한 논리적 연결을 제공합니다. ClientInfo 테스트 프레임워크를 호출하는 엔터티에 대한 세부 정보를 제공합니다. 이 정보는 테스트 프레임워크에서 해당 동작을 수정하는 데 사용할 수 있습니다. 예를 들어 이 문서가 작성된 시점을 기준으로 콘솔 실행은 클라이언트 이름(예: "testingplatform-console")을 보고합니다. CancellationToken CreateTestSessionAsync실행을 중지하는 데 사용됩니다.

반환 개체는 CloseTestSessionResult:

public sealed class CreateTestSessionResult
{
    public string? WarningMessage { get; set; }
    public string? ErrorMessage { get; set; }
    public bool IsSuccess { get; set; }
}

IsSuccess 속성은 세션 만들기에 성공했는지 여부를 나타내는 데 사용됩니다. false반환하면 테스트 실행이 중지됩니다.

CloseTestSessionAsync 메서드

CloseTestSessionAsync 메서드는 기능 면에서 CreateTestSessionAsync과 비교되며, 유일한 차이점은 개체 이름입니다. 자세한 내용은 CreateTestSessionAsync 섹션을 참조하세요.

ExecuteRequestAsync 메서드

ExecuteRequestAsync 메서드는 ExecuteRequestContext형식의 개체를 허용합니다. 이 개체는 이름에서 제안한 대로 테스트 프레임워크가 수행해야 하는 작업에 대한 세부 정보를 보유합니다. ExecuteRequestContext 정의는 다음과 같습니다.

public sealed class ExecuteRequestContext
{
    public IRequest Request { get; }
    public IMessageBus MessageBus { get; }
    public CancellationToken CancellationToken { get; }
    public void Complete();
}

IRequest: 모든 유형의 요청에 대한 기본 인터페이스입니다. 테스트 프레임워크를 수명 주기가 있는 프로세스 내 상태 저장 서버로 생각해야 합니다.

테스트 프레임워크의 수명 주기를 나타내는 시퀀스 다이어그램입니다.

위의 다이어그램은 테스트 프레임워크 인스턴스를 만든 후 테스트 플랫폼이 세 가지 요청을 발급하는 것을 보여 줍니다. 테스트 프레임워크는 이러한 요청을 처리하고 요청 자체에 포함된 IMessageBus 서비스를 활용하여 각 특정 요청에 대한 결과를 제공합니다. 특정 요청이 처리되면 테스트 프레임워크는 요청이 처리되었음을 테스트 플랫폼에 나타내는 Complete() 메서드를 호출합니다. 테스트 플랫폼은 디스패치된 모든 요청을 모니터링합니다. 모든 요청이 처리되면 CloseTestSessionAsync 호출하고 인스턴스를 삭제합니다(IDisposable/IAsyncDisposable 구현된 경우). 요청과 해당 완료가 겹쳐서 요청의 동시 및 비동기 실행을 가능하게 할 수 있습니다.

메모

현재 테스트 플랫폼은 겹치는 요청을 보내지 않으며 다음 요청을 보내기 전에 요청 >> 완료될 때까지 기다립니다. 그러나 이 동작은 나중에 변경될 수 있습니다. 동시 요청에 대한 지원은 기능 시스템을 통해 결정됩니다.

IRequest 구현은 수행해야 하는 정확한 요청을 지정합니다. 테스트 프레임워크는 요청 유형을 식별하고 그에 따라 처리합니다. 요청 유형을 인식할 수 없는 경우 예외가 발생해야 합니다.

사용 가능한 요청에 대한 자세한 내용은 IRequest 섹션에서 확인할 수 있습니다.

IMessageBus: 요청과 연결된 이 서비스를 사용하면 테스트 프레임워크가 비동기적으로 테스트 플랫폼에 진행 중인 요청에 대한 정보를 게시할 수 있습니다. 메시지 버스는 플랫폼의 중앙 허브 역할을 하여 모든 플랫폼 구성 요소 및 확장 간의 비동기 통신을 용이하게 합니다. 테스트 플랫폼에 게시할 수 있는 포괄적인 정보 목록은 IMessageBus 섹션을 참조하세요.

CancellationToken: 이 토큰은 특정 요청의 처리를 중단하는 데 활용됩니다.

Complete(): 이전 시퀀스에 설명된 것처럼 Complete 메서드는 요청이 성공적으로 처리되었으며 모든 관련 정보가 IMessageBus전송되었음을 플랫폼에 알립니다.

경고

요청에서 Complete() 호출하지 않으면 테스트 애플리케이션이 응답하지 않습니다.

요구 사항 또는 사용자의 요구 사항에 따라 테스트 프레임워크를 사용자 지정하려면 구성 파일 내에서 또는 사용자 지정 명령줄 옵션개인 설정된 섹션을 사용할 수 있습니다.

요청 처리

후속 섹션에서는 테스트 프레임워크가 수신하고 처리할 수 있는 다양한 요청에 대한 자세한 설명을 제공합니다.

다음 섹션으로 진행하기 전에 테스트 실행 정보를 테스트 플랫폼에 전달하는 데 필수적인 서비스인 IMessageBus개념을 철저히 이해하는 것이 중요합니다.

TestSessionContext

TestSessionContext 진행 중인 테스트 세션에 대한 정보를 제공하는 모든 요청에서 공유 속성입니다.

public class TestSessionContext
{
    public SessionUid SessionUid { get; }
    public ClientInfo Client { get; }
}

public readonly struct SessionUid(string value)
{
    public string Value { get; }
}

public sealed class ClientInfo
{
    public string Id { get; }
    public string Version { get; }
}

TestSessionContext는 테스트 세션 데이터를 로깅하고 상관 관계를 지정하는 데 도움이 되는, 진행 중인 테스트 세션의 고유 식별자인 SessionUid로 구성됩니다. 또한 테스트 세션의 ClientInfo 대한 세부 정보를 제공하는 형식도 포함됩니다. 테스트 프레임워크는 다른 경로를 선택하거나 테스트 세션의 개시자ID에 따라 다양한 정보를 게시할 수 있습니다.

테스트 실행 요청 검색

public class DiscoverTestExecutionRequest
{
    // Detailed in the custom section below
    public TestSessionContext Session { get; }

    // This is experimental and intended for future use, please disregard for now.
    public ITestExecutionFilter Filter { get; }
}

DiscoverTestExecutionRequest 테스트 프레임워크 검색하고 이 정보를 IMessageBus전달하도록 지시합니다.

이전 섹션에서 설명한 대로 검색된 테스트의 속성은 DiscoveredTestNodeStateProperty. 참조를 위한 제네릭 코드 조각은 다음과 같습니다.

var testNode = new TestNode
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        DiscoveredTestNodeStateProperty.CachedInstance),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        discoverTestExecutionRequest.Session.SessionUid,
        testNode));

// ...

테스트 실행 요청

public class RunTestExecutionRequest
{
    // Detailed in the custom section below
    public TestSessionContext Session { get; }

    // This is experimental and intended for future use, please disregard for now.
    public ITestExecutionFilter Filter { get; }
}

RunTestExecutionRequest 테스트 프레임워크 테스트 실행하고 이 정보를 IMessageBus전달하도록 지시합니다.

참조를 위한 제네릭 코드 조각은 다음과 같습니다.

var skippedTestNode = new TestNode()
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        SkippedTestNodeStateProperty.CachedInstance),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        runTestExecutionRequest.Session.SessionUid,
        skippedTestNode));

// ...

var successfulTestNode = new TestNode()
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        PassedTestNodeStateProperty.CachedInstance),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        runTestExecutionRequest.Session.SessionUid,
        successfulTestNode));

// ...

var assertionFailedTestNode = new TestNode()
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        new FailedTestNodeStateProperty(assertionException)),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        runTestExecutionRequest.Session.SessionUid,
        assertionFailedTestNode));

// ...

var failedTestNode = new TestNode()
{
    Uid = GenerateUniqueStableId(),
    DisplayName = GetDisplayName(),
    Properties = new PropertyBag(
        new ErrorTestNodeStateProperty(ex.InnerException!)),
};

await context.MessageBus.PublishAsync(
    this,
    new TestNodeUpdateMessage(
        runTestExecutionRequest.Session.SessionUid,
        failedTestNode));

TestNodeUpdateMessage 데이터

IMessageBus 섹션에서 설명한 대로 메시지 버스를 활용하기 전에 제공하려는 데이터 형식을 지정해야 합니다. 테스트 플랫폼은 TestNodeUpdateMessage개념을 나타내기 위해 잘 알려진 형식인 정의했습니다.

문서의 이 부분에서는 이 페이로드 데이터를 활용하는 방법을 설명합니다. 표면을 살펴보겠습니다.

public sealed class TestNodeUpdateMessage(
    SessionUid sessionUid,
    TestNode testNode,
    TestNodeUid? parentTestNodeUid = null)
{
    public TestNode TestNode { get; }
    public TestNodeUid? ParentTestNodeUid { get; }
}

public class TestNode
{
    public required TestNodeUid Uid { get; init; }
    public required string DisplayName { get; init; }
    public PropertyBag Properties { get; init; } = new();
}

public sealed class TestNodeUid(string value)

public sealed partial class PropertyBag
{
    public PropertyBag();
    public PropertyBag(params IProperty[] properties);
    public PropertyBag(IEnumerable<IProperty> properties);
    public int Count { get; }
    public void Add(IProperty property);
    public bool Any<TProperty>();
    public TProperty? SingleOrDefault<TProperty>();
    public TProperty Single<TProperty>();
    public TProperty[] OfType<TProperty>();
    public IEnumerable<IProperty> AsEnumerable();
    public IEnumerator<IProperty> GetEnumerator();
    ...
}

public interface IProperty
{
}
  • TestNodeUpdateMessage: TestNodeUpdateMessageTestNodeParentTestNodeUid두 가지 속성으로 구성됩니다. ParentTestNodeUid은 테스트에 부모 테스트가 있을 수 있음을 나타내며, 서로 간에 로 배열할 수 있는, TestNode의 개념을 도입합니다. 이 구조를 사용하면 노드 간의 트리 관계를 기반으로 향후 향상된 기능과 기능을 사용할 수 있습니다. 테스트 프레임워크에 테스트 트리 구조가 필요하지 않은 경우 테스트 트리 구조를 사용하지 않도록 선택하고 null로 설정하면 TestNode간단한 플랫 목록이 생성됩니다.

  • TestNode: TestNode 세 가지 속성으로 구성되며, 그 중 하나는 Uid형식의 TestNodeUid. 이 Uid 노드에 대한 UNIQUE STABLE ID 역할을 합니다. UNIQUE STABLE ID 용어는 다른 실행 및 운영 체제에서도 동일한 TestNodeIDENTICALUid를 유지해야 함을 의미합니다. TestNodeUid 테스트 플랫폼에서 그대로 허용하는 임의 불투명 문자열.

중요하다

ID의 안정성과 고유성은 테스트 도메인에서 매우 중요합니다. 단일 테스트의 정확한 실행을 대상으로 지정하고 ID를 테스트의 영구 식별자로 사용할 수 있도록 하여 강력한 확장 및 기능을 지원합니다.

두 번째 속성은 DisplayName( 테스트의 인간 친화적인 이름)입니다. 예를 들어 이 이름은 --list-tests 명령줄을 실행할 때 표시됩니다.

세 번째 특성은 Properties 형식인 PropertyBag. 코드에서 설명한 것처럼 이 속성 모음은 TestNodeUpdateMessage대한 제네릭 속성을 보유하는 특수한 속성 모음입니다. 이는 자리 표시자 인터페이스 IProperty구현하는 모든 속성을 노드에 추가할 수 있음을 의미합니다.

테스트 플랫폼은 TestNode.Properties에 추가된 특정 속성을 식별하여 테스트가 통과했는지, 실패했는지, 건너뛰었는지를 결정합니다.

TestNodeUpdateMessage.TestNode 섹션에서 상대 설명과 함께 사용 가능한 속성의 현재 목록을 찾을 수 있습니다.

PropertyBag 형식은 일반적으로 모든 IData 액세스할 수 있으며 플랫폼 및 확장에서 쿼리할 수 있는 기타 속성을 저장하는 데 활용됩니다. 이 메커니즘을 사용하면 호환성이 손상되는 변경을 도입하지 않고도 새 정보로 플랫폼을 향상시킬 수 있습니다. 구성 요소가 속성을 인식하는 경우 쿼리할 수 있습니다. 그렇지 않으면 무시됩니다.

마지막으로, 이 섹션은 아래 예시와 같이 IDataProducer을 생성하는 TestNodeUpdateMessage을 구현해야 하는 프레임워크 구현을 테스트하는 것을 명확히 합니다.

internal sealed class TestingFramework
    : ITestFramework, IDataProducer
{
   // ...

   public Type[] DataTypesProduced =>
   [
       typeof(TestNodeUpdateMessage)
   ];

   // ...
}

테스트 어댑터가 실행 중에 파일을 게시해야 하는 경우, https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs소스 파일에서 확인된 속성을 찾을 수 있습니다. 보시다시피, 파일 자산을 일반적으로 제공하거나 특정 TestNode에 연결할 수 있습니다. SessionFileArtifact푸시하려는 경우 아래와 같이 플랫폼에 미리 선언해야 합니다.

internal sealed class TestingFramework
    : ITestFramework, IDataProducer
{
   // ...

   public Type[] DataTypesProduced =>
   [
       typeof(TestNodeUpdateMessage),
       typeof(SessionFileArtifact)
   ];

   // ...
}

잘 알려진 특성

요청 섹션에 자세히 설명된 대로 테스트 플랫폼은 추가된 특정 속성을 식별하여 TestNodeUpdateMessage 상태 TestNode (예: 성공, 실패, 건너뛰기 등)를 확인합니다. 이렇게 하면 런타임이 콘솔에서 해당 정보를 사용하여 실패한 테스트 목록을 정확하게 표시하고 테스트 프로세스에 적절한 종료 코드를 설정할 수 있습니다.

이 세그먼트에서는 잘 알려진 다양한 IProperty 옵션과 해당 의미를 설명합니다.

잘 알려진 속성의 포괄적인 목록은 TestNodeProperties.cs 참조하세요. 속성 설명이 누락된 경우 문제를 제출하세요.

이러한 속성은 다음 범주로 나눌 수 있습니다.

  1. 제네릭 정보: 모든 종류의 요청에 포함할 수 있는 속성입니다.
  2. 검색 정보: DiscoverTestExecutionRequest 검색 요청 중에 제공되는 속성입니다.
  3. 실행 정보: 테스트 실행 요청 RunTestExecutionRequest동안 제공되는 속성입니다.

특정 속성은 필요하지만다른 속성은 선택 사항입니다. 필수 속성은 실패한 테스트를 보고하고 전체 테스트 세션이 성공했는지 여부를 나타내는 것과 같은 기본 테스트 기능을 제공하는 데 필요합니다.

반면 선택적 속성은 추가 정보를 제공하여 테스트 환경을 향상시킵니다. 특히 IDE 시나리오(예: VS, VSCode 등), 콘솔 실행 또는 보다 자세한 정보가 필요한 특정 확장을 지원하는 경우에 유용합니다. 그러나 이러한 선택적 속성은 테스트 실행에 영향을 미치지 않습니다.

메모

확장은 특정 정보가 올바르게 작동해야 하는 경우 예외를 경고하고 관리하는 작업을 수행합니다. 확장에 필요한 정보가 없는 경우, 테스트 실행이 실패하지 않도록 하고, 대신 참여를 포기해야 합니다.

일반 정보
public record KeyValuePairStringProperty(
    string Key,
    string Value)
        : IProperty;

KeyValuePairStringProperty 일반 키/값 쌍 데이터를 나타냅니다.

public record struct LinePosition(
    int Line,
    int Column);

public record struct LinePositionSpan(
    LinePosition Start,
    LinePosition End);

public abstract record FileLocationProperty(
    string FilePath,
    LinePositionSpan LineSpan)
        : IProperty;

public sealed record TestFileLocationProperty(
    string FilePath,
    LinePositionSpan LineSpan)
        : FileLocationProperty(FilePath, LineSpan);

TestFileLocationProperty 소스 파일 내에서 테스트의 위치를 정확히 파악하는 데 사용됩니다. 이는 초기자가 Visual Studio 또는 Visual Studio Code와 같은 IDE인 경우에 특히 유용합니다.

public sealed record TestMethodIdentifierProperty(
    string AssemblyFullName,
    string Namespace,
    string TypeName,
    string MethodName,
    string[] ParameterTypeFullNames,
    string ReturnTypeFullName)

TestMethodIdentifierProperty ECMA-335 표준을 준수하는 테스트 메서드의 고유 식별자입니다.

메모

이 속성을 만드는 데 필요한 데이터는 System.Reflection 네임스페이스의 형식을 사용하여 .NET 리플렉션 기능을 사용하여 편리하게 가져올 수 있습니다.

public sealed record TestMetadataProperty(
    string Key,
    string Value)

TestMetadataProperty은(는) TestNode 특성을 전달하는 데 활용됩니다.

검색 정보
public sealed record DiscoveredTestNodeStateProperty(
    string? Explanation = null)
{
    public static DiscoveredTestNodeStateProperty CachedInstance { get; }
}

DiscoveredTestNodeStateProperty 이 TestNode가 검색되었음을 나타냅니다. DiscoverTestExecutionRequest 테스트 프레임워크로 전송될 때 활용됩니다. CachedInstance 속성에서 제공하는 편리한 캐시된 값을 기록해 둡다. 이 속성은 반드시 필요합니다.

실행 정보
public sealed record InProgressTestNodeStateProperty(
    string? Explanation = null)
{
    public static InProgressTestNodeStateProperty CachedInstance { get; }
}

InProgressTestNodeStateProperty은 테스트 플랫폼에 TestNode이 실행되도록 예약되었으며 현재 진행 중임을 알립니다. CachedInstance 속성에서 제공하는 편리한 캐시된 값을 기록해 둡다.

public readonly record struct TimingInfo(
    DateTimeOffset StartTime,
    DateTimeOffset EndTime,
    TimeSpan Duration);

public sealed record StepTimingInfo(
    string Id,
    string Description,
    TimingInfo Timing);

public sealed record TimingProperty : IProperty
{
    public TimingProperty(TimingInfo globalTiming)
        : this(globalTiming, [])
    {
    }

    public TimingProperty(
        TimingInfo globalTiming,
        StepTimingInfo[] stepTimings)
    {
        GlobalTiming = globalTiming;
        StepTimings = stepTimings;
    }

    public TimingInfo GlobalTiming { get; }

    public StepTimingInfo[] StepTimings { get; }
}

TimingProperty TestNode 실행에 대한 타이밍 세부 정보를 릴레이하는 데 사용됩니다. 또한 StepTimingInfo통해 개별 실행 단계의 타이밍을 허용합니다. 이는 테스트 개념을 초기화, 실행 및 정리와 같은 여러 단계로 나눌 때 특히 유용합니다.

다음 속성 중 하나만 필요하며 TestNode 결과를 테스트 플랫폼에 전달합니다.

public sealed record PassedTestNodeStateProperty(
    string? Explanation = null)
        : TestNodeStateProperty(Explanation)
{
    public static PassedTestNodeStateProperty CachedInstance
        { get; } = new PassedTestNodeStateProperty();
}

PassedTestNodeStatePropertyTestNode이 통과되었음을 테스트 플랫폼에 알립니다. CachedInstance 속성에서 제공하는 편리한 캐시된 값을 기록해 둡다.

public sealed record SkippedTestNodeStateProperty(
    string? Explanation = null)
        : TestNodeStateProperty(Explanation)
{
    public static SkippedTestNodeStateProperty CachedInstance
        { get; } =  new SkippedTestNodeStateProperty();
}

SkippedTestNodeStatePropertyTestNode을 건너뛰었다고 테스트 플랫폼에 알립니다. CachedInstance 속성에서 제공하는 편리한 캐시된 값을 기록해 둡다.

public sealed record FailedTestNodeStateProperty : TestNodeStateProperty
{
    public FailedTestNodeStateProperty()
        : base(default(string))
    {
    }

    public FailedTestNodeStateProperty(string explanation)
        : base(explanation)
    {
    }

    public FailedTestNodeStateProperty(
        Exception exception,
        string? explanation = null)
        : base(explanation ?? exception.Message)
    {
        Exception = exception;
    }

    public Exception? Exception { get; }
}

FailedTestNodeStateProperty은 어설션 후 이 TestNode이 실패했음을 테스트 플랫폼에 알립니다.

public sealed record ErrorTestNodeStateProperty : TestNodeStateProperty
{
    public ErrorTestNodeStateProperty()
        : base(default(string))
    {
    }

    public ErrorTestNodeStateProperty(string explanation)
        : base(explanation)
    {
    }

    public ErrorTestNodeStateProperty(
        Exception exception,
        string? explanation = null)
            : base(explanation ?? exception.Message)
    {
        Exception = exception;
    }

    public Exception? Exception { get; }
}

ErrorTestNodeStatePropertyTestNode 실패했음을 테스트 플랫폼에 알릴 수 있습니다. 이러한 유형의 오류는 어설션 실패에 사용되는 FailedTestNodeStateProperty과 다릅니다. 예를 들어 ErrorTestNodeStateProperty테스트 초기화 오류와 같은 문제를 보고할 수 있습니다.

public sealed record TimeoutTestNodeStateProperty : TestNodeStateProperty
{
    public TimeoutTestNodeStateProperty()
        : base(default(string))
    {
    }

    public TimeoutTestNodeStateProperty(string explanation)
        : base(explanation)
    {
    }

    public TimeoutTestNodeStateProperty(
        Exception exception,
        string? explanation = null)
            : base(explanation ?? exception.Message)
    {
        Exception = exception;
    }

    public Exception? Exception { get; }

    public TimeSpan? Timeout { get; init; }
}

TimeoutTestNodeStateProperty 시간 제한 이유로 이 TestNode 실패했음을 테스트 플랫폼에 알릴 수 있습니다. Timeout 속성을 사용하여 시간 제한을 보고할 수 있습니다.

public sealed record CancelledTestNodeStateProperty : TestNodeStateProperty
{
    public CancelledTestNodeStateProperty()
        : base(default(string))
    {
    }

    public CancelledTestNodeStateProperty(string explanation)
        : base(explanation)
    {
    }

    public CancelledTestNodeStateProperty(
        Exception exception,
        string? explanation = null)
        : base(explanation ?? exception.Message)
    {
        Exception = exception;
    }

    public Exception? Exception { get; }
}

CancelledTestNodeStateProperty는 취소로 인해 이 TestNode이 실패했음을 테스트 플랫폼에 알립니다.

기타 확장성 지점

테스트 플랫폼은 플랫폼 및 테스트 프레임워크의 동작을 사용자 지정할 수 있는 추가 확장성 지점을 제공합니다. 이러한 확장성 지점은 선택 사항이며 테스트 환경을 향상시키는 데 사용할 수 있습니다.

ICommandLineOptionsProvider 확장 기능

메모

이 API를 확장할 때 사용자 지정 확장은 테스트 호스트 프로세스 안팎에 존재합니다.

아키텍처 섹션에서 설명한 대로 초기 단계에는 테스트 프레임워크 및 확장을 등록하는 ITestApplicationBuilder 만드는 작업이 포함됩니다.

var builder = await TestApplication.CreateBuilderAsync(args);

CreateBuilderAsync 메서드는 string[]문자열(args) 배열을 허용합니다. 이러한 인수를 사용하여 기본 제공 구성 요소, 테스트 프레임워크 및 확장을 포함하여 테스트 플랫폼의 모든 구성 요소에 명령줄 옵션을 전달하여 동작을 사용자 지정할 수 있습니다.

일반적으로 전달된 인수는 표준 Main(string[] args) 메서드에서 수신된 인수입니다. 그러나 호스팅 환경이 다른 경우 인수 목록을 제공할 수 있습니다.

인수는 두 개의 대시 --를 접두사로 가져야 합니다. 예를 들어 --filter.

테스트 프레임워크 또는 확장 지점과 같은 구성 요소가 사용자 지정 명령줄 옵션을 제공하려는 경우 ICommandLineOptionsProvider 인터페이스를 구현하여 수행할 수 있습니다. 이 구현은 ITestApplicationBuilder 속성의 등록 팩터리를 통해 CommandLine에 등록될 수 있으며, 그 방법은 다음과 같습니다.

builder.CommandLine.AddProvider(
    static () => new CustomCommandLineOptions());

제공된 예제에서 CustomCommandLineOptionsICommandLineOptionsProvider 인터페이스의 구현이며, 이 인터페이스는 다음 멤버 및 데이터 형식으로 구성됩니다.

public interface ICommandLineOptionsProvider : IExtension
{
    IReadOnlyCollection<CommandLineOption> GetCommandLineOptions();

    Task<ValidationResult> ValidateOptionArgumentsAsync(
        CommandLineOption commandOption,
        string[] arguments);

    Task<ValidationResult> ValidateCommandLineOptionsAsync(
        ICommandLineOptions commandLineOptions);
}

public sealed class CommandLineOption
{
    public string Name { get; }
    public string Description { get; }
    public ArgumentArity Arity { get; }
    public bool IsHidden { get; }

    // ...
}

public interface ICommandLineOptions
{
    bool IsOptionSet(string optionName);

    bool TryGetOptionArgumentList(
        string optionName,
        out string[]? arguments);
}

관찰된 대로 ICommandLineOptionsProviderIExtension 인터페이스를 확장합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

ICommandLineOptionsProvider 실행 순서는 다음과 같습니다.

'ICommandLineOptionsProvider' 인터페이스의 실행 순서를 나타내는 다이어그램입니다.

API 및 해당 평균을 살펴보겠습니다.

ICommandLineOptionsProvider.GetCommandLineOptions(): 이 메서드는 구성 요소에서 제공하는 모든 옵션을 검색하는 데 활용됩니다. 각 CommandLineOption 다음 속성을 지정해야 합니다.

string name: 대시 없이 표시되는 옵션의 이름입니다. 예를 들어, 사용자들이 필터--filter로 사용합니다.

string description: 옵션에 대한 설명입니다. 사용자가 애플리케이션 작성기에서 인수로 --help 전달할 때 표시됩니다.

ArgumentArity arity: 옵션의 심각도는 해당 옵션 또는 명령이 지정된 경우 전달할 수 있는 값의 수입니다. 현재 사용 가능한 항목은 다음과 같습니다.

  • Zero: 인수의 진수(0)를 나타냅니다.
  • ZeroOrOne: 0 또는 1의 인수 진도를 나타냅니다.
  • ZeroOrMore: 인수의 개수가 0 이상임을 나타냅니다.
  • OneOrMore: 하나 이상의 인수 개수를 나타냅니다.
  • ExactlyOne: 정확히 1의 인수 진도를 나타냅니다.

예를 들어 System.CommandLine arity 테이블을 참조하세요.

bool isHidden: 이 속성은 옵션을 사용할 수 있지만 --help 호출될 때 설명에 표시되지 않음을 나타냅니다.

ICommandLineOptionsProvider.ValidateOptionArgumentsAsync: 이 메서드는 사용자가 제공한 인수의 유효성을 검사하기 사용됩니다.

예를 들어 사용자 지정 테스트 프레임워크의 병렬 처리 수준을 나타내는 --dop 매개 변수가 있는 경우 사용자는 --dop 0입력할 수 있습니다. 이 시나리오에서는 0 이상의 병렬 처리가 필요하므로 1 값이 유효하지 않습니다. ValidateOptionArgumentsAsync사용하여 선행 유효성 검사를 수행하고 필요한 경우 오류 메시지를 반환할 수 있습니다.

위의 샘플에 대한 가능한 구현은 다음과 같습니다.

public Task<ValidationResult> ValidateOptionArgumentsAsync(
    CommandLineOption commandOption,
    string[] arguments)
{
    if (commandOption.Name == "dop")
    {
        if (!int.TryParse(arguments[0], out int dopValue) || dopValue <= 0)
        {
            return ValidationResult.InvalidTask("--dop must be a positive integer");
        }
    }

    return ValidationResult.ValidTask;
}

ICommandLineOptionsProvider.ValidateCommandLineOptionsAsync: 이 메서드는 마지막 메서드로 호출되며 전역 일관성 검사를 수행할 수 있습니다.

예를 들어 테스트 결과 보고서를 생성하고 파일에 저장할 수 있는 기능이 테스트 프레임워크에 있다고 가정해 보겠습니다. 이 기능은 --generatereport 옵션을 사용하여 액세스되고 파일 이름은 --reportfilename myfile.rep지정됩니다. 이 시나리오에서는 사용자가 파일 이름을 지정하지 않고 --generatereport 옵션만 제공하는 경우 파일 이름 없이 보고서를 생성할 수 없으므로 유효성 검사가 실패해야 합니다. 위의 샘플에 대한 가능한 구현은 다음과 같습니다.

public Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions)
{
    bool generateReportEnabled = commandLineOptions.IsOptionSet(GenerateReportOption);
    bool reportFileName = commandLineOptions.TryGetOptionArgumentList(ReportFilenameOption, out string[]? _);

    return (generateReportEnabled || reportFileName) && !(generateReportEnabled && reportFileName)
        ? ValidationResult.InvalidTask("Both `--generatereport` and `--reportfilename` need to be provided simultaneously.")
        : ValidationResult.ValidTask;
}

ValidateCommandLineOptionsAsync 메서드는 플랫폼 자체에서 구문 분석된 인수 정보를 가져오는 데 사용되는 ICommandLineOptions 서비스를 제공합니다.

ITestSessionLifetimeHandler 확장 기능

ITestSessionLifeTimeHandler는 테스트 세션 이전() 코드를 실행하고 이후() 코드를 실행할 수 있게 해주는 인-프로세스 확장입니다.

사용자 지정 ITestSessionLifeTimeHandler등록하려면 다음 API를 활용합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestSessionLifetimeHandle(
    static serviceProvider => new CustomTestSessionLifeTimeHandler());

팩터리는 IServiceProvider 활용하여 테스트 플랫폼에서 제공하는 서비스 제품군에 액세스합니다.

중요하다

등록 순서대로 API가 호출되므로 등록 순서가 중요합니다.

ITestSessionLifeTimeHandler 인터페이스에는 다음 메서드가 포함됩니다.

public interface ITestSessionLifetimeHandler : ITestHostExtension
{
    Task OnTestSessionStartingAsync(
        SessionUid sessionUid,
        CancellationToken cancellationToken);

    Task OnTestSessionFinishingAsync(
        SessionUid sessionUid,
        CancellationToken cancellationToken);
}

public readonly struct SessionUid(string value)
{
    public string Value { get; } = value;
}

public interface ITestHostExtension : IExtension
{
}

ITestSessionLifetimeHandler 모든 ITestHostExtension 확장의 기본 역할을 하는 유형입니다. 다른 모든 확장 지점과 마찬가지로, 그것도 IExtension을 상속합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

이 API에 대한 다음 세부 정보를 고려합니다.

OnTestSessionStartingAsync: 이 메서드는 테스트 세션이 시작되기 전에 호출되고 현재 테스트 세션에 대한 불투명 식별자를 제공하는 SessionUid 개체를 받습니다.

OnTestSessionFinishingAsync: 이 메서드는 테스트 세션이 완료된 후 호출되어 테스트 프레임워크 모든 테스트 실행을 완료하고 모든 관련 데이터를 플랫폼에 보고했는지 확인합니다. 일반적으로 이 메서드에서 확장은 IMessageBus 사용하여 사용자 지정 자산 또는 데이터를 공유 플랫폼 버스로 전송합니다. 또한 이 메서드는 테스트 세션이 종료되었음을 사용자 지정 out-of-process 확장에 신호를 보낼 수도 있습니다.

마지막으로, 두 API 모두 확장이 CancellationToken을/를 준수할 것으로 예상됩니다.

확장에 집중적인 초기화가 필요하고 비동기/대기 패턴을 사용해야 하는 경우 Async extension initialization and cleanup참조할 수 있습니다. 확장 지점 간에 상태를 공유해야 하는 경우 , CompositeExtensionFactory<T> 섹션을 참조할 수 있습니다.

ITestApplicationLifecycleCallbacks 확장 기능

ITestApplicationLifecycleCallbacks는 모든 항목 전에 코드를 실행할 수 있게 해주는 in-process 확장이며, 마치 가상의 테스트 호스트메인의 첫 번째 줄에 액세스하는 것과 같습니다.

사용자 지정 ITestApplicationLifecycleCallbacks등록하려면 다음 API를 활용합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddTestApplicationLifecycleCallbacks(
    static serviceProvider
    => new CustomTestApplicationLifecycleCallbacks());

팩터리는 IServiceProvider 활용하여 테스트 플랫폼에서 제공하는 서비스 제품군에 액세스합니다.

중요하다

등록 순서대로 API가 호출되므로 등록 순서가 중요합니다.

ITestApplicationLifecycleCallbacks 인터페이스에는 다음 메서드가 포함됩니다.

public interface ITestApplicationLifecycleCallbacks : ITestHostExtension
{
    Task BeforeRunAsync(CancellationToken cancellationToken);

    Task AfterRunAsync(
        int exitCode,
        CancellationToken cancellation);
}

public interface ITestHostExtension : IExtension
{
}

ITestApplicationLifecycleCallbacks 모든 ITestHostExtension 확장의 기본 역할을 하는 유형입니다. 다른 모든 확장 지점과 마찬가지로, 그것도 IExtension을 상속합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

BeforeRunAsync: 이 메서드는 테스트 호스트 대한 초기 연락 지점 역할을 하며 in-process 확장에서 기능을 실행할 수 있는 첫 번째 기회입니다. 일반적으로 기능이 두 환경에서 작동하도록 설계된 경우 해당 out-of-process 확장과 연결을 설정하는 데 사용됩니다.

예를 들어 기본 제공 중단 덤프 기능은 프로세스 내프로세스 외부 확장으로 구성되며, 이 메서드는 확장의 프로세스 외부 구성 요소와 정보를 교환하는 데 사용됩니다.

AfterRunAsync: 이 메서드는 int ITestApplication.RunAsync() 종료하기 전에 마지막 호출이며 exit code제공합니다. 정리 작업에만 사용해야 하며 해당 out-of-process 확장에 테스트 호스트 종료될 예정임을 알려야 합니다.

마지막으로, 두 API 모두 확장이 CancellationToken을/를 준수할 것으로 예상됩니다.

IDataConsumer 확장 기능

IDataConsumer테스트 프레임워크 및 그 확장에 의해 IData로 푸시되는 정보를 구독하고 받을 수 있는 in-process 확장입니다.

이 확장 지점은 개발자가 테스트 세션 중에 생성된 모든 정보를 수집하고 처리할 수 있도록 하기 때문에 중요합니다.

사용자 지정 IDataConsumer등록하려면 다음 API를 활용합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHost.AddDataConsumer(
    static serviceProvider => new CustomDataConsumer());

팩터리는 IServiceProvider 활용하여 테스트 플랫폼에서 제공하는 서비스 제품군에 액세스합니다.

중요하다

등록 순서대로 API가 호출되므로 등록 순서가 중요합니다.

IDataConsumer 인터페이스에는 다음 메서드가 포함됩니다.

public interface IDataConsumer : ITestHostExtension
{
    Type[] DataTypesConsumed { get; }

    Task ConsumeAsync(
        IDataProducer dataProducer,
        IData value,
        CancellationToken cancellationToken);
}

public interface IData
{
    string DisplayName { get; }
    string? Description { get; }
}

IDataConsumer 모든 ITestHostExtension 확장의 기본 역할을 하는 유형입니다. 다른 모든 확장 지점과 마찬가지로, 그것도 IExtension을 상속합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

DataTypesConsumed: 이 속성은 이 확장에서 사용할 Type 목록을 반환합니다. IDataProducer.DataTypesProduced해당합니다. 특히 IDataConsumer는 서로 다른 IDataProducer 인스턴스로부터 발생하는 여러 형식을 문제 없이 구독할 수 있습니다.

ConsumeAsync: 현재 소비자가 구독하고 있는 유형의 데이터가 IMessageBus에 푸시될 때마다 이 메서드가 호출됩니다. 그것은 데이터 페이로드의 생산자에 대한 세부 정보와 IDataProducer 페이로드 자체를 제공하는 IData을 받아 처리합니다. 보듯이 IData 일반적인 정보 제공 데이터를 포함하는 제네릭 자리 표시자 인터페이스입니다. 다양한 유형의 IData 푸시하는 기능은 소비자가 형식 자체에서 전환하여 올바른 형식으로 캐스팅하고 특정 정보에 액세스해야 한다는 것을 의미합니다.

테스트 프레임워크에서 생성한 TestNodeUpdateMessage 정교하게 소비자의 샘플 구현은 다음과 같습니다.

internal class CustomDataConsumer : IDataConsumer, IOutputDeviceDataProducer
{
    public Type[] DataTypesConsumed => new[] { typeof(TestNodeUpdateMessage) };
    ...
    public Task ConsumeAsync(
        IDataProducer dataProducer,
        IData value,
        CancellationToken cancellationToken)
    {
        var testNodeUpdateMessage = (TestNodeUpdateMessage)value;

        switch (testNodeUpdateMessage.TestNode.Properties.Single<TestNodeStateProperty>())
        {
            case InProgressTestNodeStateProperty _:
                {
                    ...
                    break;
                }
            case PassedTestNodeStateProperty _:
                {
                    ...
                    break;
                }
            case FailedTestNodeStateProperty failedTestNodeStateProperty:
                {
                    ...
                    break;
                }
            case SkippedTestNodeStateProperty _:
                {
                    ...
                    break;
                }
            ...
        }

        return Task.CompletedTask;
    }
...
}

마지막으로, API는 확장이 존중해야 하는 CancellationToken를 수락합니다.

중요하다

ConsumeAsync 메서드 내에서 직접 페이로드를 처리하는 것이 중요합니다. IMessageBus 동기 및 비동기 처리를 모두 관리할 수 있으며 테스트 프레임워크실행을 조정합니다. 사용 프로세스는 완전히 비동기적이며 이 글을 쓰는 시점에서는 IMessageBus.Push을 차단하지 않지만, 이는 향후 요구 사항에 따라 변경될 수 있는 구현 세부 사항입니다. 그러나 플랫폼은 이 메서드를 항상 한 번 호출하여 복잡한 동기화가 필요하지 않고 소비자의 확장성을 관리할 수 있도록 합니다.

경고

IDataConsumer내에서 ITestHostProcessLifetimeHandler 함께 사용하는 경우 ITestSessionLifetimeHandler.OnTestSessionFinishingAsync실행 후 받은 데이터를 무시해야 합니다. OnTestSessionFinishingAsync 누적된 데이터를 처리하고 IMessageBus새 정보를 전송할 수 있는 마지막 기회이므로 이 시점을 초과하여 사용되는 데이터는 확장에서 사용할 수 없습니다.

확장에 집중적인 초기화가 필요하고 비동기/대기 패턴을 사용해야 하는 경우 Async extension initialization and cleanup참조할 수 있습니다. 확장 지점 간에 상태를 공유해야 하는 경우 , CompositeExtensionFactory<T> 섹션을 참조할 수 있습니다.

ITestHostEnvironmentVariableProvider 확장 기능

ITestHostEnvironmentVariableProvider 테스트 호스트에 대한 사용자 지정 환경 변수를 설정할 수 있는 out-of-process 확장입니다. 이 확장 지점을 활용하면 테스트 플랫폼이 아키텍처 섹션에 설명된 대로 적절한 환경 변수를 사용하여 새 호스트를 시작할 수 있습니다.

사용자 지정 ITestHostEnvironmentVariableProvider등록하려면 다음 API를 활용합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHostControllers.AddEnvironmentVariableProvider(
    static serviceProvider => new CustomEnvironmentVariableForTestHost());

팩터리는 IServiceProvider 활용하여 테스트 플랫폼에서 제공하는 서비스 제품군에 액세스합니다.

중요하다

등록 순서대로 API가 호출되므로 등록 순서가 중요합니다.

ITestHostEnvironmentVariableProvider 인터페이스에는 다음과 같은 메서드 및 형식이 포함됩니다.

public interface ITestHostEnvironmentVariableProvider : ITestHostControllersExtension, IExtension
{
    Task UpdateAsync(IEnvironmentVariables environmentVariables);

    Task<ValidationResult> ValidateTestHostEnvironmentVariablesAsync(
        IReadOnlyEnvironmentVariables environmentVariables);
}

public interface IEnvironmentVariables : IReadOnlyEnvironmentVariables
{
    void SetVariable(EnvironmentVariable environmentVariable);
    void RemoveVariable(string variable);
}

public interface IReadOnlyEnvironmentVariables
{
    bool TryGetVariable(
        string variable,
        [NotNullWhen(true)] out OwnedEnvironmentVariable? environmentVariable);
}

public sealed class OwnedEnvironmentVariable : EnvironmentVariable
{
    public IExtension Owner { get; }

    public OwnedEnvironmentVariable(
        IExtension owner,
        string variable,
        string? value,
        bool isSecret,
        bool isLocked);
}

public class EnvironmentVariable
{
    public string Variable { get; }
    public string? Value { get; }
    public bool IsSecret { get; }
    public bool IsLocked { get; }
}

ITestHostEnvironmentVariableProvider 모든 ITestHostControllersExtension 확장의 기본 역할을 하는 유형입니다. 다른 모든 확장 지점과 마찬가지로, 그것도 IExtension을 상속합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

이 API에 대한 세부 정보를 고려합니다.

UpdateAsync: 이 업데이트 API는 IEnvironmentVariables 또는 SetVariable 메서드를 호출할 수 있는 RemoveVariable 개체의 인스턴스를 제공합니다. SetVariable사용하는 경우 다음 사양이 필요한 EnvironmentVariable형식의 개체를 전달해야 합니다.

  • Variable: 환경 변수의 이름입니다.
  • Value: 환경 변수의 값입니다.
  • IsSecret: 환경 변수에 TryGetVariable통해 기록하거나 액세스할 수 없는 중요한 정보가 포함되어 있는지 여부를 나타냅니다.
  • IsLocked: 다른 ITestHostEnvironmentVariableProvider 확장에서 이 값을 수정할 수 있는지 여부를 결정합니다.

ValidateTestHostEnvironmentVariablesAsync: 등록된 UpdateAsync 인스턴스의 모든 ITestHostEnvironmentVariableProvider 메서드가 호출된 후 이 메서드가 호출됩니다. 이를 통해 환경 변수의 올바른 설정을 확인할 수 있습니다. IReadOnlyEnvironmentVariables을 구현하는 개체는 TryGetVariable 개체 형식으로 특정 환경 변수 정보를 가져오는 OwnedEnvironmentVariable 메서드를 제공합니다. 유효성 검사 후 오류 이유가 포함된 ValidationResult 반환합니다.

메모

테스트 플랫폼은 기본적으로 SystemEnvironmentVariableProvider구현하고 등록합니다. 이 공급자는 모든 현재 환경 변수를 로드합니다. 첫 번째 등록된 공급자는 먼저 실행되어 다른 모든 ITestHostEnvironmentVariableProvider 사용자 확장에 대한 기본 환경 변수에 대한 액세스 권한을 부여합니다.

확장에 집중적인 초기화가 필요하고 비동기/대기 패턴을 사용해야 하는 경우 Async extension initialization and cleanup참조할 수 있습니다. 확장 지점 간에 상태를 공유해야 하는 경우 , CompositeExtensionFactory<T> 섹션을 참조할 수 있습니다.

ITestHostProcessLifetimeHandler 확장 기능

ITestHostProcessLifetimeHandler는 외부 관점에서 테스트 호스트 프로세스를 관찰할 수 있도록 하는 프로세스 외부 확장입니다. 이렇게 하면 테스트 중인 코드에서 유도할 수 있는 잠재적인 충돌 또는 중단으로 인해 확장이 영향을 받지 않습니다. 이 확장 지점을 활용하면 아키텍처 섹션에 설명된 대로 테스트 플랫폼에서 새 호스트를 시작하라는 메시지가 표시됩니다.

사용자 지정 ITestHostProcessLifetimeHandler등록하려면 다음 API를 활용합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

builder.TestHostControllers.AddProcessLifetimeHandler(
    static serviceProvider => new CustomMonitorTestHost());

팩터리는 IServiceProvider 활용하여 테스트 플랫폼에서 제공하는 서비스 제품군에 액세스합니다.

중요하다

등록 순서대로 API가 호출되므로 등록 순서가 중요합니다.

ITestHostProcessLifetimeHandler 인터페이스에는 다음 메서드가 포함됩니다.

public interface ITestHostProcessLifetimeHandler : ITestHostControllersExtension
{
    Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken);

    Task OnTestHostProcessStartedAsync(
        ITestHostProcessInformation testHostProcessInformation,
        CancellationToken cancellation);

    Task OnTestHostProcessExitedAsync(
        ITestHostProcessInformation testHostProcessInformation,
        CancellationToken cancellation);
}

public interface ITestHostProcessInformation
{
    int PID { get; }
    int ExitCode { get; }
    bool HasExitedGracefully { get; }
}

ITestHostProcessLifetimeHandler 모든 ITestHostControllersExtension 확장의 기본 역할을 하는 유형입니다. 다른 모든 확장 지점과 마찬가지로, 그것도 IExtension을 상속합니다. 따라서 다른 확장과 마찬가지로 IExtension.IsEnabledAsync API를 사용하여 사용하도록 설정하거나 사용하지 않도록 선택할 수 있습니다.

이 API에 대한 다음 세부 정보를 고려합니다.

BeforeTestHostProcessStartAsync: 테스트 플랫폼이 테스트 호스트를 시작하기 전에 이 메서드가 호출됩니다.

OnTestHostProcessStartedAsync: 이 메서드는 테스트 호스트가 시작된 직후에 호출됩니다. 이 메서드는 테스트 호스트 프로세스 결과에 대한 주요 세부 정보를 제공하는 ITestHostProcessInformation 인터페이스를 구현하는 개체를 제공합니다.

중요하다

이 메서드를 호출해도 테스트 호스트의 실행이 중단되지는 않습니다. 일시 중지해야 하는 경우 같은 확장을 등록하고 ITestApplicationLifecycleCallbacks 확장과 동기화해야 합니다.

OnTestHostProcessExitedAsync: 이 메서드는 테스트 도구 모음 실행이 완료되면 호출됩니다. 이 메서드는 테스트 호스트 프로세스의 결과에 대한 중요한 세부 정보를 전달하는 ITestHostProcessInformation 인터페이스를 준수하는 개체를 제공합니다.

ITestHostProcessInformation 인터페이스는 다음과 같은 세부 정보를 제공합니다.

  • PID: 테스트 호스트의 프로세스 ID입니다.
  • ExitCode: 프로세스의 종료 코드입니다. 이 값은 OnTestHostProcessExitedAsync 메서드 내에서만 사용할 수 있습니다. OnTestHostProcessStartedAsync 메서드 내에서 액세스하려고 하면 예외가 발생합니다.
  • HasExitedGracefully: 테스트 호스트가 충돌했는지 여부를 나타내는 부울 값입니다. true이면 테스트 호스트가 정상적으로 종료되지 않았음을 나타냅니다.

확장 실행 순서

테스트 플랫폼은 테스트 프레임워크및 프로세스 내 또는 프로세스 외 에서 작동할 수 있는 확장 수로 구성됩니다. 이 문서에서는 기능이 호출될 것으로 예상되는 시기에 대한 명확성을 제공하기 위해 모든 잠재적 확장성 지점에 호출 시퀀스를 간략하게 설명합니다.

  1. ITestHostEnvironmentVariableProvider.UpdateAsync: 프로세스 외부
  2. ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync: 프로세스 외부에서
  3. ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync: 프로세스 외부에서 실행됨
  4. 호스트 프로세스 시작 테스트
  5. ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync : Out-of-process 상태에서 이 이벤트는 경합 상태에 따라 의 in-process 확장의 작업을 얽힐 수 있습니다.
  6. ITestApplicationLifecycleCallbacks.BeforeRunAsync: 진행 중
  7. ITestSessionLifetimeHandler.OnTestSessionStartingAsync: 진행 중
  8. ITestFramework.CreateTestSessionAsync: 처리 중
  9. ITestFramework.ExecuteRequestAsync : In-process에서 이 메서드를 하나 이상 호출할 수 있습니다. 이 시점에서 테스트 프레임워크는 IDataConsumer활용할 수 있는 IMessageBus 정보를 전송합니다.
  10. ITestFramework.CloseTestSessionAsync: 처리 중
  11. ITestSessionLifetimeHandler.OnTestSessionFinishingAsync: 진행 중
  12. ITestApplicationLifecycleCallbacks.AfterRunAsync: 진행 중
  13. 프로세스 내 정리에는 모든 확장 지점에서 dispose 및 IAsyncCleanableExtension을 호출하는 작업이 포함됩니다.
  14. ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync : 프로세스 외부
  15. 프로세스 외부 정리에는 모든 확장 지점에서 dispose와 IAsyncCleanableExtension을 호출하는 작업이 포함됩니다.

확장 도우미

테스트 플랫폼은 확장 구현을 간소화하기 위한 도우미 클래스 및 인터페이스 집합을 제공합니다. 이러한 도우미는 개발 프로세스를 간소화하고 확장이 플랫폼의 표준을 준수하도록 설계되었습니다.

확장의 비동기 초기화 및 정리

팩터리를 통한 테스트 프레임워크 및 확장의 생성은 동기 생성자를 사용하는 표준 .NET 개체 생성 메커니즘을 준수합니다. 확장에 집약적인 초기화(예: 파일 시스템 또는 네트워크 액세스)가 필요한 경우 생성자가 않고 void를 반환하기 때문에 생성자에서 Task 패턴을 사용할 수 없습니다.

따라서 테스트 플랫폼은 간단한 인터페이스를 통해 비동기/대기 패턴을 사용하여 확장을 초기화하는 메서드를 제공합니다. 대칭성을 위해 확장이 원활하게 구현할 수 있는 정리 작업을 위한 비동기 인터페이스도 제공합니다.

public interface IAsyncInitializableExtension
{
    Task InitializeAsync();
}

public interface IAsyncCleanableExtension
{
    Task CleanupAsync();
}

IAsyncInitializableExtension.InitializeAsync: 이 메서드는 생성 팩터리 다음에 호출되도록 보장됩니다.

IAsyncCleanableExtension.CleanupAsync: 이 메서드는 기본 또는 DisposeAsync전에 테스트 세션이 종료되는 동안 한 번 이상 Dispose 호출됩니다.

중요하다

표준 Dispose 메서드와 마찬가지로 CleanupAsync 여러 번 호출될 수 있습니다. 개체의 CleanupAsync 메서드가 두 번 이상 호출되면 개체는 첫 번째 호출 이후의 모든 호출을 무시해야 합니다. 개체가 CleanupAsync 메서드를 여러 번 호출하는 경우 예외를 throw해서는 안됩니다.

메모

기본적으로 테스트 플랫폼은 사용 가능한 경우 DisposeAsync을 호출하고, 구현된 경우 Dispose을 호출합니다. 테스트 플랫폼은 삭제 메서드를 모두 호출하지 않지만 구현된 경우 비동기 메서드의 우선 순위를 지정합니다.

CompositeExtensionFactory<T>

확장 섹션에 설명된 대로 테스트 플랫폼을 사용하면 인터페이스를 구현하여 프로세스 내/외부 모두에 사용자 지정 확장을 통합할 수 있습니다.

각 인터페이스는 특정 기능을 다루며 .NET 디자인에 따라 이 인터페이스를 특정 개체에 구현합니다. 해당 섹션에 설명된 대로 AddXXX 또는 TestHostTestHostController 개체의 특정 등록 API ITestApplicationBuilder 사용하여 확장 자체를 등록할 수 있습니다.

그러나 두 확장 간에 상태 공유해야 하는 경우 서로 다른 인터페이스를 구현하는 다른 개체를 구현하고 등록할 수 있으므로 공유가 어려운 작업입니다. 도움이 없으면 하나의 확장에서 다른 확장으로 정보를 전달하여 공유하는 방식이 되어야 하므로, 디자인이 복잡해집니다.

따라서 테스트 플랫폼은 동일한 형식을 사용하여 여러 확장 지점을 구현하는 정교한 방법을 제공하여 데이터를 공유하는 작업을 간단하게 만듭니다. 단일 인터페이스 구현과 동일한 API를 사용하여 등록할 수 있는 CompositeExtensionFactory<T>활용하기만 하면 됩니다.

예를 들어 ITestSessionLifetimeHandlerIDataConsumer모두 구현하는 형식을 고려합니다. 테스트 프레임워크 정보를 수집한 다음 테스트 세션이 종료되면 IMessageBus내의 ITestSessionLifetimeHandler.OnTestSessionFinishingAsync 사용하여 아티팩트를 디스패치하기 때문에 일반적인 시나리오입니다.

해야 할 일은 일반적으로 인터페이스를 구현하는 것입니다.

internal class CustomExtension : ITestSessionLifetimeHandler, IDataConsumer, ...
{
   ...
}

CompositeExtensionFactory<CustomExtension>을(를) 생성한 후, IDataConsumerITestSessionLifetimeHandler API에 모두 등록할 수 있으며, 이러한 API는 CompositeExtensionFactory<T>에 대한 오버로드를 제공합니다.

var builder = await TestApplication.CreateBuilderAsync(args);

// ...

var factory = new CompositeExtensionFactory<CustomExtension>(serviceProvider => new CustomExtension());

builder.TestHost.AddTestSessionLifetimeHandle(factory);
builder.TestHost.AddDataConsumer(factory);

팩터리 생성자는 IServiceProvider 사용하여 테스트 플랫폼에서 제공하는 서비스에 액세스합니다.

테스트 플랫폼은 복합 확장의 수명 주기를 관리해야 합니다.

테스트 플랫폼이 in-processout-of-process 확장 모두에 대한 지원으로 인해 확장 지점을 임의로 결합할 수 없다는 점에 유의해야 합니다. 확장의 생성 및 사용률은 호스트 유형에 따라 달라집니다. 즉, 프로세스 내(TestHost)와 out-of-process(TestHostController) 확장만 그룹화할 수 있습니다.

다음과 같은 조합이 가능합니다.

  • ITestApplicationBuilder.TestHost의 경우, IDataConsumerITestSessionLifetimeHandler를 결합할 수 있습니다.
  • ITestApplicationBuilder.TestHostControllers의 경우, ITestHostEnvironmentVariableProviderITestHostProcessLifetimeHandler를 결합할 수 있습니다.