本文說明如何為 Microsoft.Testing.Platform (MTP) 建立自訂的測試框架。 測試框架是唯一強制性的擴充功能。 它會發現並執行測試,並將結果回報給平台。
關於完整的擴充點摘要及進程/出程概念,請參見「建立自訂擴充」。
如果你正在遷移現有的 VSTest 測試框架,建議以原生方式實現介面 ITestFramework 。
VSTest 橋接擴充功能作為過渡步驟可用,但原生實作能提供最佳體驗。
測試框架擴展
測試框架是為測試平台提供發現和執行測試能力的主要擴充功能。 測試框架負責將測試結果傳達回測試平台。 測試框架是執行測試工作階段所需的唯一強制擴充功能。
註冊測試框架
本節介紹如何將測試框架註冊到測試平台。 您只能用 API TestApplication.RegisterTestFramework 為每個測試應用程式構建器註冊一個測試框架,如 MTP 架構 文件中所示。
註冊 API 定義如下:
ITestApplicationBuilder RegisterTestFramework(
Func<IServiceProvider, ITestFrameworkCapabilities> capabilitiesFactory,
Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework> adapterFactory);
RegisterTestFramework API 需要兩個處理站:
Func<IServiceProvider, ITestFrameworkCapabilities>:這是一個委派,它接受實作IServiceProvider介面的物件,並傳回實作ITestFrameworkCapabilities介面的物件。IServiceProvider可讓您存取平台服務,例如組態、記錄器和命令列參數。ITestFrameworkCapabilities介面用於向平台和擴充功能宣告測試框架支援的功能。 它允許平台和擴充功能透過實作和支援特定行為來正確互動。 為了更理解功能的概念,請參閱相應的章節。Func<ITestFrameworkCapabilities, IServiceProvider, ITestFramework>:這是一個委派,它接受 ITestFrameworkCapability 物件 (該物件是由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();
備註
傳回空的 ITestFrameworkCapability 不應阻止測試工作階段的執行。 所有測試框架都應該能夠發現和執行測試。 如果測試框架缺乏某些功能,影響應僅限於可能選擇不參加的擴充功能。
對於需要框架特定元資料的報告,請註冊 ITrxReportCapability 並將對應的 TRX 屬性加入你的測試結果。
建立測試框架
Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework 是透過提供測試框架的擴充功能來實作的:
public interface ITestFramework : IExtension
{
Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context);
Task ExecuteRequestAsync(ExecuteRequestContext context);
Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context);
}
IExtension 介面
介面 ITestFramework 繼承自介面 IExtension,所有擴展點的介面都從中繼承。
IExtension 用於檢索擴充功能的名稱和描述。
IExtension 還提供了一種透過 Task<bool> IsEnabledAsync() 在設定中動態啟用或停用擴充功能的方法。 如果沒有特別要求停用它,請務必從此方法中返回true。
CreateTestSessionAsync 方法
CreateTestSessionAsync 方法會在測試工作階段開始時呼叫,用於初始化測試框架。 API 會接受 CreateTestSessionContext 物件並傳回 CreateTestSessionResult。
public sealed class CreateTestSessionContext : TestSessionContext
{
public CancellationToken CancellationToken { get; }
}
此 SessionUid 屬性繼承自 TestSessionContext (詳見 TestSessionContext 章節)。
CancellationToken 用於停止執行 CreateTestSessionAsync。
傳回物件為 CreateTestSessionResult:
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:該令牌用於中斷處理特定請求。
若要停止排程新測驗而不取消結果發布或清理,請實作 IGracefulStopTestExecutionCapability。 此獨立功能支援例如 --maximum-failed-tests 等功能。
Complete():如前面的序列所示,Complete 方法會通知平台請求已成功處理,且所有相關資訊已傳輸至 IMessageBus。
警告
忽略對請求叫用 Complete() 將導致測試應用程式變得無回應。
若要根據您或使用者的要求自訂測試框架,您可以使用設定檔中的個人化區段,或使用自訂命令列選項。
處理請求
後續各節詳細描述了測試框架可能接收和處理的各種請求。
在繼續下一節之前,徹底理解 IMessageBus 的概念至關重要,它是將測試執行資訊傳送到測試平台的基本服務。
TestSessionContext
TestSessionContext 是所有請求的共用屬性,提供有關正在進行的測試工作階段的資訊:
public class TestSessionContext
{
public SessionUid SessionUid { get; }
}
public readonly struct SessionUid(string value)
{
public string Value { get; }
}
TestSessionContext 由 SessionUid 組成,它是正在進行的測試工作階段的唯一識別碼,有助於記錄和關聯測試工作階段資料。
DiscoverTestExecutionRequest (發現測試執行請求)
public class DiscoverTestExecutionRequest
{
public TestSessionContext Session { get; }
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
{
public TestSessionContext Session { get; }
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));
遵循請求篩選條件
若要只執行使用者或工具選擇的測試,請同時套用 TestExecutionRequest.Filter 於發現與執行階段。 MTP 會將過濾器傳遞給你的框架;平台不會過濾框架發佈或執行的測試。
Microsoft.Testing.Platform.Requests 中的穩定濾波器條件包括:
| 篩選 | 框架的行為 |
|---|---|
NopFilter |
不要套用任何其他限制。 |
TestNodeUidListFilter |
只選擇其穩定 TestNodeUid 出現在 TestNodeUids 的測試。 空陣列則不會選擇任何測試。 |
比較 UID 作為不透明、區分大小寫的識別碼。 不要用顯示名稱或新產生的識別碼來取代原有識別碼。 平台使用 UID 過濾器來處理 --filter-uid、定向工具請求以及 重試擴充功能。
以下輔助工具處理這兩種過濾器類型,並拒絕不支援的過濾器,而非靜默執行完整套件:
private static bool MatchesFilter(ITestExecutionFilter filter, TestNodeUid uid)
=> filter switch
{
NopFilter => true,
TestNodeUidListFilter uidFilter => uidFilter.TestNodeUids.Any(
candidate => string.Equals(candidate.Value, uid.Value, StringComparison.Ordinal)),
_ => throw new NotSupportedException(
$"Filter '{filter.GetType().FullName}' isn't supported.")
};
Private Shared Function MatchesFilter(filter As ITestExecutionFilter, uid As TestNodeUid) As Boolean
If TypeOf filter Is NopFilter Then
Return True
End If
Dim uidFilter = TryCast(filter, TestNodeUidListFilter)
If uidFilter IsNot Nothing Then
Return uidFilter.TestNodeUids.Any(
Function(candidate) String.Equals(candidate.Value, uid.Value, StringComparison.Ordinal))
End If
Throw New NotSupportedException($"Filter '{filter.GetType().FullName}' isn't supported.")
End Function
在發佈已探索到的節點或執行已選取的測試前,請先使用該輔助工具。 以下範例框架對兩個請求使用相同的選擇邏輯:
public async Task ExecuteRequestAsync(ExecuteRequestContext context)
{
if (context.Request is not DiscoverTestExecutionRequest
and not RunTestExecutionRequest)
{
throw new NotSupportedException("The request type isn't supported.");
}
var request = (TestExecutionRequest)context.Request;
foreach (var test in s_tests)
{
context.CancellationToken.ThrowIfCancellationRequested();
var uid = new TestNodeUid(test.Uid);
if (!MatchesFilter(request.Filter, uid))
{
continue;
}
if (request is DiscoverTestExecutionRequest)
{
await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(
request.Session.SessionUid,
new TestNode
{
Uid = uid,
DisplayName = test.Name,
Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance)
}));
}
else
{
await PublishResultAsync(context, request, uid, test.Name);
}
}
context.Complete();
}
Public Async Function ExecuteRequestAsync(context As ExecuteRequestContext) As Task Implements ITestFramework.ExecuteRequestAsync
If Not TypeOf context.Request Is DiscoverTestExecutionRequest AndAlso
Not TypeOf context.Request Is RunTestExecutionRequest Then
Throw New NotSupportedException("The request type isn't supported.")
End If
Dim request = DirectCast(context.Request, TestExecutionRequest)
For Each test In s_tests
context.CancellationToken.ThrowIfCancellationRequested()
Dim uid As New TestNodeUid(test.Uid)
If Not MatchesFilter(request.Filter, uid) Then
Continue For
End If
If TypeOf request Is DiscoverTestExecutionRequest Then
Await context.MessageBus.PublishAsync(Me, New TestNodeUpdateMessage(
request.Session.SessionUid,
New TestNode With {
.Uid = uid,
.DisplayName = test.Name,
.Properties = New PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance)
}))
Else
Await PublishResultAsync(context, request, uid, test.Name)
End If
Next
context.Complete()
End Function
當沒有匹配的測試時,完成請求時,不執行或發布未被選取的測試。 MTP 採用其零測試或最低預期測試政策。
這很重要
TreeNodeFilter 且 CompositeTestExecutionFilter 是實驗性質,並使用診斷識別碼 TPEXP 。 支援樹狀過濾器的框架必須根據其測試樹和屬性來評估該過濾器。 使用 過濾器提供者 的框架也必須評估複合過濾器的每個子節點。 此範例中的輔助者僅支援 NopFilter 和 TestNodeUidListFilter。
標準測試執行
從 MTP 2.5 預覽版開始,使用進行中的 TestNodeUpdateMessage 呼叫 ExecuteRequestContext.StartTestExecutionAsync,即可啟動實驗性的標準 TestExecution。 Start 方法會發布進行中的更新,並回傳一個物件,將執行生命週期、訊息排序與遙測相關性保持在一起。
在典型執行情境中使用 Run 或 RunAsync 來調用框架與測試程式碼。 當 OpenTelemetry 啟用時,測試所建立的子活動仍屬於相同的測試執行活動。
呼叫 CompleteAsync 時會附上排序結果訊息及生產者觀察到的執行結束時間。 每個結果都必須使用起始訊息中的會話、測試 UID 和父 UID。 最後一個訊息就是最終結果。 空集合會發佈執行完成的更新,卻不發明結果。
捨棄未完成的 TestExecution,即可在不發佈結果的情況下放棄它。 完成或丟棄後,物件會拒絕額外的執行或完成呼叫。
這很重要
StartTestExecutionAsync 和 TestExecution 使用 TPEXP 診斷 ID,且可能會在未來版本中變更或移除。
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:TestNodeUpdateMessage由兩個屬性組成:TestNode和ParentTestNodeUid。ParentTestNodeUid表示測試可能有父測試,引入了測試樹狀結構的概念,其中TestNode可以相互關聯排列。 此結構可根據節點之間的樹狀結構關係,進行增強和功能擴充。 如果您的測試框架不需要測試樹狀結構,您可以選擇不使用它,只需將其設為 Null,就能獲得簡單的TestNode平面清單。TestNode:TestNode由三個屬性組成,其中之一是Uid類型的TestNodeUid。Uid做為節點的唯一穩定識別碼。 術語「唯一穩定識別碼」代表相同的TestNode應該在不同的執行和作業系統之間保持相同的Uid。TestNodeUid是測試平台原樣接受的任意不透明字串。
這很重要
識別碼的穩定性和唯一性在測試領域至關重要。 它們能夠精確鎖定單一測試以進行執行,並允許測試 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)
];
// ...
}
如果你的測試介面卡在執行時產生 檔案 ,平台會辨識兩種不同的機制,而正確的機制取決於該檔案屬於特定測試還是整個執行。 詳情請參見 訊息匯流排檔案雜訊。
- 執行層級或工作階段層級檔案是獨立的匯流排訊息:發佈
FileArtifact(無範圍)或SessionFileArtifact(透過其SessionUid限定於該執行範圍)。 - 測試專用附件不是獨立訊息。 將它們以
FileArtifactProperty項目的形式加入到TestNode.Properties內的TestNodeUpdateMessage上,讓終端機、dotnet test和 IDE 將每個檔案與其測試建立關聯。
如果你打算發佈一個 SessionFileArtifact,必須事先透過 DataTypesProduced向平台宣告該執行時類型,如下所示。 請注意,FileArtifactProperty 並未單獨列出,因為它是在 TestNodeUpdateMessage 內傳送,而不是作為獨立的訊息類型:
internal sealed class TestingFramework
: ITestFramework, IDataProducer
{
// ...
public Type[] DataTypesProduced =>
[
typeof(TestNodeUpdateMessage),
typeof(SessionFileArtifact)
];
// ...
}
已知屬性
如 請求部分 所述,測試平臺會識別新增至 TestNodeUpdateMessage 的特定屬性,以判斷 TestNode 的狀態(例如,成功、失敗、略過等)。 這允許執行時在控制台中準確顯示失敗測試的清單及其相應資訊,並為測試處理序設定適當的結束程式碼。
如 requests 區段所述,測試平台會識別新增至 TestNodeUpdateMessage 的特定屬性,用來判斷 TestNode 的狀態(例如成功、失敗和略過)。 這允許執行時在控制台中準確顯示失敗測試的清單及其相應資訊,並為測試處理序設定適當的結束程式碼。
本節將介紹各種知名 IProperty 選項及其各自的意涵。
欲了解所有知名屬性的完整清單,請參見 TestNodeStateProperties.cs(以及同一目錄中的其他 Property 類型)。 如果您發現屬性描述遺失,請提出問題。
這些性質可分為以下幾類:
- 通用資訊:可以包含在任何類型的請求中的屬性。
-
發現資訊:在
DiscoverTestExecutionRequest發現請求期間提供的屬性。 -
執行資訊:在測試執行請求
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 是測試方法的唯一識別碼。
public sealed record TestMetadataProperty(
string Key,
string Value)
TestMetadataProperty 用於傳達的特徵或TestNode。
從 MTP 2.4 開始,可將 AssertionFailureProperty 加入失敗的測試節點,以提供各自預先格式化的期望值和實際值。 報告使用者可以利用這些數值來呈現結構化差異,而非解析失敗訊息。 至少必須有一個值不是 null。 取用端偏好此屬性,但也可以回退至 Data 中舊版的 assert.expected 和 assert.actual 項目。
從 MTP 2.4 開始,請在進行中的重試序列中的每次更新加入 RetryAttemptProperty。
AttemptNumber 是從 1 開始編號,而 IsSuperseded 則標示某次嘗試已被後續嘗試取代。 TRX、JUnit 及程序退出程式碼會忽略被取代的嘗試,而終端機、HTML 與 CTRF 使用者則可保留完整歷史。 這個屬性不表示處理序外的 --retry-failed-tests 嘗試。
擷取測試輸出
要將擷取的文字與測試建立關聯,請將來自 Microsoft.Testing.Platform.Extensions.Messages 的 StandardOutputProperty 和 StandardErrorProperty 加入該測試的 TestNode.Properties。 透過 TestNodeUpdateMessage 發佈該節點,並使用與測試結果相同的穩定 UID 和會話。
你的框架負責輸出擷取與歸因,包括平行測試間的隔離。 MTP 會使用這些屬性。它不會自動擷取對 WriteLine 的任意呼叫。 以下範例使用範例文字取代由真實架構擷取的輸出:
var testNode = new TestNode
{
Uid = uid,
DisplayName = name,
Properties = new PropertyBag(
PassedTestNodeStateProperty.CachedInstance,
new StandardOutputProperty($"Output from {name}."),
new StandardErrorProperty($"Diagnostic from {name}."))
};
await context.MessageBus.PublishAsync(
this, new TestNodeUpdateMessage(request.Session.SessionUid, testNode));
Dim testNode As New TestNode With {
.Uid = uid,
.DisplayName = name,
.Properties = New PropertyBag(
PassedTestNodeStateProperty.CachedInstance,
New StandardOutputProperty($"Output from {name}."),
New StandardErrorProperty($"Diagnostic from {name}."))
}
Await context.MessageBus.PublishAsync(
Me, New TestNodeUpdateMessage(request.Session.SessionUid, testNode))
終端報告器使用這些性質搭配 --show-stdout 和 --show-stderr。 標準誤屬性不會讓測試失敗;測試的狀態屬性決定了其結果。
透過 IOutputDevice 將每次測試的輸出與診斷日誌及擴充功能層級輸出分開。 若要在 TRX 報告中包含文字,也請提供 TRX 專屬的訊息屬性。 不要假設每份報告的使用者都對應相同的屬性。
探索資訊
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 來計時各個執行步驟。 當您的測試概念分為多個階段 (例如初始化、執行和清理) 時,這特別有用。
以下屬性中必須指定其中一個,並將結果傳達給測試平台。
public sealed record PassedTestNodeStateProperty(
string? Explanation = null)
: TestNodeStateProperty(Explanation)
{
public static PassedTestNodeStateProperty CachedInstance
{ get; } = new PassedTestNodeStateProperty();
}
PassedTestNodeStateProperty 會通知測試平台此 TestNode 已通過。
請注意 CachedInstance 屬性所提供的方便快取值。
public sealed record SkippedTestNodeStateProperty(
string? Explanation = null)
: TestNodeStateProperty(Explanation)
{
public static SkippedTestNodeStateProperty CachedInstance
{ get; } = new SkippedTestNodeStateProperty();
}
SkippedTestNodeStateProperty 會通知測試平台此 TestNode 已略過。
請注意 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; }
}
ErrorTestNodeStateProperty 會通知測試平台此 TestNode 失敗。 這種類型的失敗與用於斷言失敗的 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 因取消而失敗。