通过 REST API 将测试结果发布到 TFS 2018 测试用例

Posted

技术标签:

【中文标题】通过 REST API 将测试结果发布到 TFS 2018 测试用例【英文标题】:Publishing test results to TFS 2018 test cases via REST API 【发布时间】:2019-06-23 21:40:18 【问题描述】:

目前我们正在使用 mstest.exe 运行我们的自动化测试,然后创建一个 .trx 结果文件。然后,在此之后,我们使用 tcm.exe 将这些结果发布到我们 TFS 服务器上的一些测试套件/测试用例。

我们希望不再使用 tcm,而是使用 TFS REST API 从我们的 .trx 结果文件中发布结果。

我已经阅读了一些关于此的the documentation on the REST API,但没有什么是特定于使用 TFS 扩展客户端类(例如TestManagementHttpClient)的,它仅枚举要使用的实际 URL。它也没有给出很多关于它期望什么参数的例子。

Microsoft.TeamFoundation.TestManagement.WebApi 命名空间的官方.NET reference documentation 有帮助,但同样没有任何示例/示例来了解每个函数期望的参数。

only example/sample I was able to find 不够详细,我无法理解如何在我的情况下应用它,因为我对测试点/测试运行的概念不够熟悉,无法操作代表它们的类。

我猜测试运行有多个测试点(每个测试用例运行一个?)代表执行该测试用例的结果?在这种情况下,我应该假设我需要为每个测试结果创建一个测试点。如果是这样,我怎么知道要给它哪个 ID?上面的示例将“3”硬编码为其值。

如果有人可以解释上述示例,并提供与我的用例相关的更好/更完整的示例(从 .trx 文件开始,并将这些结果发布到与某个测试套件下的链接自动化项匹配的测试用例),帮助我了解一切如何相互关联,我将不胜感激。

谢谢。

【问题讨论】:

哪个版本的 tfs?您是否通过构建管道运行测试?在新版本的 tfs 上从管道运行具有测试发布步骤,或者如果您使用测试任务,我相信会为您完成。 一个测试运行可以包含多个测试用例。每个测试用例可以有 1 个或多个测试点。每个测试点代表执行测试用例的不同时间。每次执行都可以来自不同的测试配置或不同的测试环境或任何其他导致同一测试用例产生新结果的东西。 @Matt 我们目前正在使用 TFS 2018.3,但没有使用构建管道。所有自动化测试都是由“自制”框架手动启动的,该框架在一堆测试二进制文件/类别上调用 MSTest.exe。 您可以创建一个“假构建”,然后使用标准的 trx 发布选项。您唯一需要的是一个空的构建记录。 【参考方案1】:

因此,根据我在问题中链接的示例/示例回答我自己的问题:

    您需要使用TestManagementHttpClient.GetTestConfigurationsAsync()获取您想要的测试配置

    然后你会想要使用TestManagementHttpClient.GetPointsAsync()获取该测试用例/测试配置组合的所有测试点

    然后您需要创建一个测试运行。这是通过至少声明一个新的RunCreateModel 对象来完成的,指定您之前获取的测试点ID。您可能还需要填写大量参数(buildIdisAutomated 等)。然后您需要调用TestManagementHttpClient.CreateTestRunAsync() 来实际创建它。

    步骤 3 实际上在测试运行下创建了空的测试结果,对于您在创建时指定的每个测试点。您需要使用TestManagementHttpClient.GetTestResultsAsync() 获取它们并修改它们的Outcome 属性,使用TestCaseResult.TestCase.Id 属性来了解哪个结果适用于哪个测试用例。您可能还想填写其他属性,例如 State 等。同样,您需要使用 TestManagementHttpClient.UpdateTestResultsAsync() 将这些修改推送到 TFS

    最后一步是通过使用state = "Completed" 创建一个RunUpdateModel 对象,然后调用TestManagementHttpClient.UpdateTestRunAsync(),将您的测试运行设置为已完成

这是我最终编写的用 F# 编写的函数:

// A test point is a pairing of a test case with a test configuration
let createTestRun (httpClient:TestManagementHttpClient) (testRunName:string) (teamProjectName:string) (testPlanId:int) (testSuiteId:int) (testCaseIdsAndResults:seq<(int * string)>) (buildId:int) (cancellationToken:CancellationToken) = async 
    let testPlanIdString = testPlanId.ToString()
    let plan = new ShallowReference(testPlanIdString)

    let! testConfigurations = httpClient.GetTestConfigurationsAsync(teamProjectName, cancellationToken = cancellationToken) |> Async.AwaitTask
    let defaultTestConfiguration = testConfigurations |> Seq.find (fun c -> c.IsDefault) // TODO: We only use the default configuration for now. Do we always want this?

    let rec getTestPoints (testIdsAndResults:(int * string) list) (testPoints:TestPoint[]) = async 
        match testIdsAndResults with
        | (testId, _)::rest ->
            let! fetchedTestPoints = httpClient.GetPointsAsync(teamProjectName, testPlanId, testSuiteId, testCaseId = testId.ToString(), cancellationToken = cancellationToken) |> Async.AwaitTask
            let testPoint = fetchedTestPoints |> Seq.find (fun p -> p.Configuration.Id = defaultTestConfiguration.Id.ToString())
            let newTestPointsList = Array.append testPoints [|testPoint|]
            return! getTestPoints rest newTestPointsList
        | _ ->
            return testPoints
    
    let! testPoints = getTestPoints (List.ofSeq testCaseIdsAndResults) Array.empty
    let testPointIds = testPoints |> Array.map (fun p -> p.Id)

    let runCreateModel = new RunCreateModel(name = testRunName, plan = plan, buildId = buildId, isAutomated = new Nullable<bool>(true), pointIds = testPointIds)
    let! testRun = httpClient.CreateTestRunAsync(runCreateModel, teamProjectName, cancellationToken = cancellationToken) |> Async.AwaitTask
    let! emptyResults = httpClient.GetTestResultsAsync(project = teamProjectName, runId = testRun.Id, outcomes = new List<TestOutcome>(), cancellationToken = cancellationToken) |> Async.AwaitTask

    let rec createCaseResults (testIdsAndResults:(int * string) list) (results:TestCaseResult[]) = async 
        match testIdsAndResults with
        | (testId, testResult)::rest ->
            let caseResult = emptyResults |> Seq.find (fun r -> r.TestCase.Id = testId.ToString())
            caseResult.State <- "Completed"
            caseResult.Outcome <- testResult // "passed", "failed", "never run", "not applicable"
            let newResultsList = Array.append results [|caseResult|]
            return! createCaseResults rest newResultsList
        | _ ->
            return results
    
    let! results = createCaseResults (List.ofSeq testCaseIdsAndResults) Array.empty
    let! _ = httpClient.UpdateTestResultsAsync(results, teamProjectName, testRun.Id, cancellationToken = cancellationToken) |> Async.AwaitTask

    let runmodel = new RunUpdateModel(state = "Completed");
    let! _ = httpClient.UpdateTestRunAsync(runmodel, teamProjectName, testRun.Id, cancellationToken = cancellationToken) |> Async.AwaitTask

    ()

【讨论】:

以上是关于通过 REST API 将测试结果发布到 TFS 2018 测试用例的主要内容,如果未能解决你的问题,请参考以下文章

通过 TFS REST API 将工作项添加到板列

TFS Rest API - 通过测试用例名称或标题获取测试用例

通过 API 从 TFS 检索数据驱动测试的结果

尝试通过使用rest api从TFS票证中读取缺陷历史来获取TFS错误状态更改日期

使用 TFS 2015 REST API 进行队列构建的 409 冲突响应

通过 REST API 发布变量 - Visual Studio Team Services