发布外部派生的 TFS 测试用例结果
本文介绍如何从外部源(例如,您自己开发的自动化测试框架)将自动测试结果发布到 Microsoft 的 Test Manager Infrastructure。
引言
无论是出于在生产环境中进行测试的需要,还是运行与无法在 Microsoft Build 和 Development 环境中运行的工具集成的测试,仍然需要将测试用例结果报告回 Team Foundation Server (TFS)。对于自动化测试而言,这尤其如此,需要自动发布测试用例结果。以下是从与 Microsoft.TestManagment
.NET 程序集集成的外部运行的实用程序中处理此问题的一种方法。
注意事项
- 请自行承担使用风险和(不)便之处.
- 依赖于 Microsoft Team Explorer。Microsoft Team Explorer (TE) 是免费提供的,有多个版本可匹配您的 Visual Studio 和 TFS 版本。鉴于这些环境的多样性,以及出于对重新分发许可(或缺乏许可)的尊重,依赖的 TE 程序集未包含在内,必须单独获取并配置才能运行。
- 代码示例密集、不完整。提前为代码风格的疏忽致歉。请根据需要进行美化。为简洁起见,本文代码片段中已省略错误检查和变量获取。
发布测试用例结果
第一步:查找并添加 TestManager 引用
首先,您必须下载并安装 Microsoft Team Explorer。接下来,从您的项目映射对 Microsoft.TeamFoundation.TestManagement.Client
和 Microsoft.TeamFoundation.TestManagement.Common
的引用。
您还需要引用 Microsoft.TeamFoundation.Client
、Microsoft.TeamFoundation.Common
、Microsoft.TeamFoundation.WorkItemTracking.Client
和 Microsoft.TeamFoundation.WorkItemTracking.Common
程序集。
虽然这会将大约 30 个程序集放入您的输出目录,但 Team Explorer 程序集中有两个重要文件被遗漏,无法直接引用。对此的变通方法是找到 DLL 文件,并将它们作为“Build Action = None”(构建操作 = 无)和“Copy if newer”(如果较新则复制)文件放入您的项目中。
如果您通过 Add|Existing Item,... 项目菜单添加 Microsoft.WITDataStore32.dll 和 Microsoft.WITDataStore64.dll,那么 DLL 的静态副本将被复制到您的项目源文件文件夹中。我的强迫症让我关闭项目,删除复制的 Microsoft DLL,然后编辑 .csproj 文件将其指向原始目录,例如:
Before:
<ItemGroup>
<None Include="Microsoft.WITDataStore32.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Microsoft.WITDataStore64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
After:
<ItemGroup>
<None Include="..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\TestPro\
Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.WITDataStore32.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\TestPro\
Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.WITDataStore64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
当您部署您的 'TCResults
' 可执行文件时,您必须包含与可执行文件一起被塞入项目构建输出目录的三十几个 DLL 的随行文件。
第二步:获取 TfsTeamProjectCollection 对象
与 TFS .NET 对象集成时的初始步骤是从 TFS 服务器 URI 和项目名称获取 ProjectCollection
。如果能找到,此静态例程将返回 ProjectCollection
。
static TfsTeamProjectCollection GetTeamProjectCollection(Uri TFSServer, string ProjectName)
{
TfsConfigurationServer configServer = TfsConfigurationServerFactory.GetConfigurationServer(TFSServer);
ReadOnlyCollection<CatalogNode> projects = configServer.CatalogNode.QueryChildren(new[]
{ CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None);
foreach (CatalogNode pc in projects)
{
Guid collection_id = new Guid(pc.Resource.Properties["InstanceId"]);
ReadOnlyCollection<CatalogNode> teamProjects = pc.QueryChildren(new[]
{ CatalogResourceTypes.TeamProject }, false, CatalogQueryOptions.None);
foreach (CatalogNode tp in teamProjects)
{
if (string.Compare(ProjectName, tp.Resource.DisplayName,
CLU.Option.Collection.CaseSensitivity) == 0)
return configServer.GetTeamProjectCollection(collection_id);
}
}
return null;
}
第三步:获取要发布到的 TestPlan。
(自行填入加粗的变量值)
TfsTeamProjectCollection teamCollection =
GetTeamProjectCollection(_tfs_server_uri, _project_name);
ITestManagementService tms = teamCollection.GetService<ITestManagementService>();
ITestManagementTeamProject testProject = tms.GetTeamProject(_project_name);
string wis_command =
(string.IsNullOrWhiteSpace(_test_plan_name))
? " SELECT * FROM TestPlan WHERE [PlanState] = 'Active'"
: " SELECT * FROM TestPlan WHERE [PlanState] = 'Active'
AND [Title] = '" + _test_plan_name + "' ";
ITestPlanCollection testplanCollection = testProject.TestPlans.Query(wis_command);
</code>
解释性插曲
现在情况变得混乱而复杂,其中夹杂着我基于与 TFS 2013 本地服务器的实际工作经验得出的推测。
fluentbytes.com 网站上有一篇由 Marcel de Vries 发布的出色实现指南,介绍了如何通过 TestManagement interface
s 使用 TFS TestCase
s 和 TestCaseResult
s。从中,我大致了解了 TestManagement
程序集的 .NET 对象是如何组织的,如下面粗略所示。
但是将新对象写入存储比这个只读图表所暗示的要受到更多限制。下面是相关 TestManagement
对象的接口使用图。
Marcel de Vries 的文章中并未说明 TestCaseResult
s、TestRun
s、TestPoint
s、TestCase
s、TestSuite
s 和 TestPlan
s 的关系和排序要求。
基本约束
TestCaseResult
s 只能分配给TestRun
的TestPoint
s。- (根据我的经验)
TestPoint
s 只能在TestRun
创建后、保存前添加到TestRun
中。 TestPoint
s 是TestCase
s 与TestSuite
配置的矩阵交叉。TestRun
必须在有效的、最好是“Active”(活动)的TestPlan
下创建。TestCaseResult
对象在TestRun
首次保存时实例化。- 与
TestCaseResult
关联的TestCase
也必须与目标TestPlan
下的目标TestSuite
相关联。这暗示了新的TestRun
与TestSuite
之间存在同级关系,两者具有相同的父TestPlan
。而TestCase
与TestPoint
之间存在表亲关系。
第四步:间接创建 TestCaseResult 占位符
要使 TeamProject
的 TestCase
(s)(存在结果的)的占位符 TestCaseResult
对象得到实例化,必须验证 TestCase
(s) 是否与指定的 TestSuite
相关联。然后,必须创建一个新的 TestRun
,添加与 TestCase
(s) 相关联的适当 TestPoint
(s)。当 TestRun
保存时,占位符 TestCaseResult
对象会被实例化。下面通过 Id
或 AutomatedTestName
字段查询 TestCase
s。
(自行填入加粗的变量值)
<code>
foreach (ITestPlan testPlan in testplanCollection)
{
int suite_index = 0;
ITestSuiteBase testSuite =
(testPlan.RootSuite.SubSuites.Count > suite_index)
? testPlan.RootSuite.SubSuites[suite_index] : testPlan.RootSuite;
while (testSuite != null)
{
if (((ITestSuiteBase2)testSuite).Status.Equals("In Progress")
&&
(string.IsNullOrWhiteSpace(_test_suite_name)
|| _test_suite_name.Equals(testSuite.Title))
&&
(!string.IsNullOrWhiteSpace(_test_case_id)
|| !string.IsNullOrWhiteSpace(_automated_test_name))
)
{
string testcase_query = null;
if (!string.IsNullOrWhiteSpace(_test_case_id))
testcase_query = "SELECT * FROM WorkItems WHERE [Work Item Type] = 'Test Case'
AND [Id] = '" + _test_case_id + "'";
else if (!string.IsNullOrWhiteSpace(_automated_test_name))
testcase_query = "SELECT * FROM WorkItems WHERE [Work Item Type] = 'Test Case'
AND [Microsoft.VSTS.TCM.AutomatedTestName] = '" + _automated_test_name + "'";
foreach (ITestCase test_case in testProject.TestCases.Query(testcase_query))
{
ITestRun testRun = testPlan.CreateTestRun(true);
ITestPointCollection testpointCollection = testPlan.QueryTestPoints
("SELECT * FROM [TestPoint] WHERE [TestCaseId] = '" + test_case.Id.ToString() + "'");
if (testpointCollection.Count == 0)
{
testSuite.TestCases.Add(test_case);
testPlan.Save();
testPlan.Refresh();
testSuite.Refresh(true);
testpointCollection = testPlan.QueryTestPoints
("SELECT * FROM [TestPoint] WHERE [TestCaseId] = '" + test_case.Id.ToString() + "'");
}
foreach (ITestPoint test_point in testpointCollection)
{
if (test_point.ConfigurationName.Equals(_test_configuration_name))
testRun.AddTestPoint(test_point, null);
}
testRun.Title = test_case.Title;
testRun.Save();</code>
第五步:发布并保存 TestCaseResult
现在占位符 TestCaseResut
对象已实例化,可以设置它们的内部值。
(自行填入加粗的变量值)
foreach (ITestCaseResult tcr in testRun.QueryResults(false))
{
switch (_tcr_outcome)
{
case "NONE":
tcr.Outcome = TestOutcome.None;
tcr.State = TestResultState.Completed;
break;
case "PASS":
case "PASSED":
tcr.Outcome = TestOutcome.Passed;
tcr.State = TestResultState.Completed;
break;
case "FAIL":
case "FAILED":
tcr.Outcome = TestOutcome.Failed;
tcr.State = TestResultState.Completed;
break;
case "INCONCLUSIVE":
tcr.Outcome = TestOutcome.Inconclusive;
tcr.State = TestResultState.Completed;
break;
case "TIMEOUT":
tcr.Outcome = TestOutcome.Timeout;
tcr.State = TestResultState.Completed;
break;
case "ABORTED":
tcr.Outcome = TestOutcome.Aborted;
tcr.State = TestResultState.Completed;
break;
case "BLOCK":
case "BLOCKED":
tcr.Outcome = TestOutcome.Blocked;
tcr.State = TestResultState.Pending;
break;
case "NE":
case "NOTEXECUTED":
tcr.Outcome = TestOutcome.NotExecuted;
tcr.State = TestResultState.Pending;
break;
case "WARNING":
tcr.Outcome = TestOutcome.Warning;
tcr.State = TestResultState.Unspecified;
break;
case "ERROR":
tcr.Outcome = TestOutcome.Error;
tcr.State = TestResultState.Unspecified;
break;
case "NA":
case "NOT APPLICABLE":
tcr.Outcome = TestOutcome.NotApplicable;
tcr.State = TestResultState.Completed;
break;
case "PAUSED":
tcr.Outcome = TestOutcome.Paused;
tcr.State = TestResultState.Paused;
break;
case "INPROGRESS":
tcr.Outcome = TestOutcome.InProgress;
tcr.State = TestResultState.InProgress;
break;
case "UNSPECIFIED":
default:
tcr.Outcome = TestOutcome.Unspecified;
tcr.State = TestResultState.Unspecified;
break;
}
tcr.Comment = _tcr_comment;
tcr.Save();
}
testRun.QueryResults(false).Save(false);
}
}
testSuite = (testPlan.RootSuite.SubSuites.Count > ++suite_index) ?
testPlan.RootSuite.SubSuites[suite_index] : null;
}
}
结论
通过能够从外部源发布更详尽的测试用例结果,您的项目和团队将能更好地了解您产品的质量指标。
历史
- 2017.07.27 - 初始发布