TFS

使用 Team Foundation Service 构建和验证 Windows 应用商店应用程序

Thomas LeBrun

 

自 2012 年 10 月推出最终版本以来,Visual Studio Team Foundation Server (TFS) 的云版本 Team Foundation Service 提供了大量功能,可帮助你提供高质量的软件。 开发出面向 Windows 8 的软件这一愿望将不再遥不可及。

你可能想知道两个产品是否可以强强联合,即使用 Team Foundation Service 来构建 Windows 应用商店应用程序。 很遗憾,由于存在一些限制,目前这一点还无法直接实现,本文后面将进行介绍。 不过,我也会介绍避开这一问题所需要的步骤,帮助你在构建过程中验证 Windows 应用商店应用程序。

首先,让我们快速了解一下 Team Foundation Service。

Team Foundation Service 概述

作为一项基于云的服务,Team Foundation Service 允许开发者访问 TFS 提供的功能,而不必费力地安装和管理 TFS。 只需在 bit.ly/ZusqUY 注册(免费!),你就可以开始了。

在该产品提供的服务中,你可能将重点使用三个主要功能来开发和提供高质量的软件:

源代码管理 你可以在当前工具和首选语言保持不变的情况下使用源代码管理功能,源代码管理器可处理几乎任何种类的文件(C#、C++、HTML、PHP、Java 等)。 如果使用 IDE(如 Visual Studio 或 Eclipse),可以继续使用它来开发应用程序以及将文件签入源代码控制。

源代码管理器体系结构提供本地工作区来存储源代码的本地副本。 在连接断开模式下,可以在此工作区中执行所有修改。 重新连接后,只需签入代码,将其推送到服务器,即可保留所有版本的完整历史记录,因此你可以跟踪和回滚更改。

协作 借助 Team Foundation Service,开发者可以更好地协同工作,主要通过一个称为任务板的工具实现。 可以从任何允许创建自定义仪表板的当前浏览器访问任务板。 你可以使用任务板管理有关工作项的信息、构建状态、测试结果等,如图 1 所示。

Sample Dashboard for Team Foundation Service
图 1 Team Foundation Service 的示例仪表板

生成服务 在撰写本文时,生成服务仍处于预览阶段,它基于属于 TFS 2010 的 Team Build,通过云提供自动生成功能。

Team Build 的所有标准功能都可供生成服务使用,其中包括连续集成、每夜构建和网关签入构建。 此外,它使用的生成模板可完全自定义。 甚至可利用一个“现成的”模板在 Windows Azure 上进行连续部署(你可以将代码签入源代码管理,然后在 Windows Azure 网站上查看其更新)。

生成服务承载于使用 Windows Server 2008 R2、Team Build、Visual Studio 2010 或更高版本部署的生成服务器上(请参见本页底部的必需软件和选项的完整列表: bit.ly/12Sf99Z)。 默认配置适合大部分应用程序,但 Windows 应用商店应用程序除外。 如图 2 所示,Windows 应用商店应用程序需要在 Windows 8(或 Windows Server 2012)上构建,它们并未安装在生成服务器上。

Building Windows Store Apps on Team Foundation Service Is Not Possible out of the Box
图 2 无法直接使用 Team Foundation Service 上构建 Windows 应用商店应用程序

那么,如前文所述,我们无法直接使用 Team Foundation Service 构建 Windows 应用商店应用程序。 但是,正如你将看到的那样,这个问题可以通过一个方法得到解决。 实际上,该解决方法包括安装 Windows 8 计算机,这台计算机将成为新的生成代理,专用于通过 Team Foundation Service 构建 Windows 应用商店应用程序。 我将说明如何实现该方法。

使用 Team Foundation Service 构建 Windows 应用商店应用程序

下面,我们来看一下使用 Team Foundation Service 构建 Windows 应用商店应用程序所需的步骤。

安装生成服务 首先,你需要一台运行 Windows 8 的计算机。 它可以是物理机或虚拟机 (VM);只要可从 Internet 访问该计算机就行。 然后,在该计算机上安装 TFS 2012。 请注意,安装 TFS 并不意味着它已配置好且可供使用。 需要安装的唯一原因是要能够配置生成服务。 你无需获取 Team Foundation Application Server,这是因为 Team Foundation Service 已提供。

在生成服务安装完成后,可以使用专用团队项目集合对其进行配置。 这种情况下,因为不设置其他 TFS 组件并且要使用 Team Foundation Service,所以需要指定可用于 Team Foundation Service 帐户的团队项目集合,然后完成配置,如图 3 所示。

Installing the Team Foundation Build Service in a Dedicated Team Projects Collection
图 3 在专用团队项目集合中安装 Team Foundation 生成服务

配置生成服务 在下一部分中,你需要了解有关 TFS 及其生成体系结构的一些基本知识。

每个 TFS 都有一组专用生成控制器。 生成控制器是将使用专用生成代理接收生成请求并执行请求的端点。 生成代理执行生成中最重要的工作: 它从源代码控制获取文件、编译代码、执行单元测试等等。

Team Foundation Service 附带专用生成控制器 — 托管生成控制器,因此,你可以考虑只创建新代理来使用此控制器运行。 很遗憾,你不能将内部生成代理附加到托管生成控制器。 你需要选择其他生成控制器或创建新的生成控制器。

为简单起见,我们创建一个新的生成控制器,如图 4 所示。

Creating a New Build Controller
图 4 创建新的生成控制器

在控制器启动并运行后,下一步是创建专门用于构建 Windows 应用商店应用程序的新代理。 创建新生成代理十分简单,只需单击“新建代理”链接并填写相应字段。 在实际生产环境中,可能有多个代理用于生成控制器;为了确保 Windows 应用商店应用程序仅由 Windows 8 上运行的代理构建,请添加一个专用标记,如图 5 所示。 这不是必需的,但在之后创建生成定义时,将需要指定此标记以确保只使用此代理执行生成过程。

Creating a New Build Agent
图 5 创建新的生成代理

在进行下一环节之前,需要转至生成服务属性并将其设置为以交互方式运行。 如果只想构建 Windows 应用商店应用程序,则此步骤并不是必需的。 但是,如果要验证应用程序,需要将这些应用程序安装在生成计算机上并启动才能使用 Team Foundation Service 和生成服务;如果生成服务未配置为以交互方式运行,则无法实现这一点。

在 Visual Studio 团队资源管理器的“生成”页上,依次单击“操作”和“管理生成控制器”以打开一个窗口,其中列出了已安装的所有生成控制器(及其专用代理)。 如果配置成功,可以看到新的控制器和代理。

准备生成代理以运行单元测试 如果承载生成代理的计算机将用于执行单元测试,则还需要执行另外两个步骤。 首先,必须在该计算机上安装 Windows 8 开发者许可证。 开发者许可证免费获取,但需要每 30 天(如果有 Windows 应用商店帐户,则为 90 天)续订一次;如果已有 Microsoft 帐户,可根据需要获取多个这样的许可证。 有两种方法可获取开发者许可证。 在生成计算机上,只需创建 Windows 应用商店应用程序即可打开一个对话框,可以从中获取有效的许可证。 如果不想在生成计算机上创建一个虚假应用程序,可以运行以下 Windows PowerShell 命令以显示该对话框:

C:\PS> Show-WindowsDeveloperLicenseRegistration

在获得开发者许可证后,需要在生成代理上生成并安装一个单元测试证书(从包含要运行的单元测试的代码项目中)。 在这一步骤中,需要在开发者计算机上生成一个应用程序包。 在 Visual Studio 中,单击“应用商店”|“创建应用程序包”。 这会创建一个文件夹,其中包含 Windows 应用商店应用程序(位于扩展名为 .appx 的文件中)及其证书。

要在生成计算机上安装该证书,请以管理员身份打开命令提示符,输入以下命令:

certutil -addstore root certificate_file

请注意,certificate_file 是证书文件的路径。

构建 Windows 应用商店应用程序 在运行控制器和代理后,构建 Windows 应用商店应用程序,这与构建其他种类的应用程序一样。 你只需创建一个新生成定义并指定要使用刚设置好的新生成控制器。 为确保生成过程使用在 Windows 8 上运行的生成代理,请在生成定义的“过程”选项卡中,选择在创建生成代理时所指定的标记(参见图 6)。

Creating the Build Process Using the Specified Tag
图 6 使用指定标记创建生成过程

完成此操作后,使用刚创建的生成定义对新生成进行排队,然后启动它;因为有指定的标记,可以确保新生成是使用正确代理执行的,因而不会失败。

可以看到,使用 Team Foundation Service 构建 Windows 应用商店应用程序的过程极其简单,但其功能十分强大,你可以完全自定义生成过程。 不过,仍存在一个问题。 即使生成成功,也并不意味着应用程序将正常运行,甚至也不意味着它将通过所有基本验证步骤。 接下来,我将说明如何验证应用程序以及如何向用户指示(通过生成报告)验证是通过还是失败。

团队项目生成期间验证 Windows 应用商店应用程序

你可能知道,若要发布到 Windows 应用商店,必须验证应用程序。 也就是说,应用程序必须通过必需的验证步骤。 你可以在生成过程中验证应用程序。 只需添加生成后事件即可轻松地验证,该事件将启动 Windows 应用程序认证包 (ACK) 来验证应用程序。 但是验证完成时,并不会向用户通知结果。 我将说明如何扩展生成过程以包含此步骤。

要在生成过程中包含 ACK 执行,只需修改项目文件添加以下 PostPackageEvent:

<Target Name="PostPackageEvent" AfterTargets="_GenerateAppxPackage">   <Exec Command="'$(TargetPlatformSdkPath)App Certification Kit\appcert.exe' reset"/>   <Exec Command="'$(TargetPlatformSdkPath)\App Certification Kit\appcert.exe' test -apptype windowsstoreapp -AppxPackagePath '$(FinalAppxPackage)' –reportoutputpath '$(outdir)\ValidationResult.xml'" />   <Exec Command="copy '$(userprofile)\appdata\Local\Microsoft\appcertkit\ValidationResult.htm' '$(outdir)\ValidationResult.htm'"/> </Target>

执行时,代码将创建文件 ValidationResult.html,该文件包含 ACK 所执行验证的结果。 如果在生成执行时连接到生成服务器,可以看到将启动应用程序以供 ACK 验证。 这是正常现象;将安装、验证应用程序,然后在测试完成后自动将其删除。 请注意,生成服务已配置为以交互方式运行,因此可以安装和执行应用程序。 如果尚未这样配置,则在生成期间会出现错误。

生成过程本身不会受验证结果的影响,因此用户需要检查测试结果才能了解应用程序是否出现验证错误。 幸运的是,你可以改进生成报告,让用户了解验证是否出现任何错误。 让我们看一下如何自定义生成报告,以包含 ACK 工具的验证结果。

自定义生成报告 ACK 创建一个 HTML 文件和一个 XML 文件,将它们保存在所选的文件夹中。 你可以使用此 XML 文件创建一个将修改生成报告的自定义工作流活动,以向用户通知验证结果。

创建此活动的代码十分简单。 该代码会查找 XML 文件(包含验证结果)、读取该文件以查找“OVERALL_RESULT”特性的值,然后返回该值。 图 7 显示了创建活动 CheckWackResultsActivity 的代码。

图 7 CheckWackResultsActivity

[BuildActivity(HostEnvironmentOption.All)] public sealed class CheckWackResultsActivity : CodeActivity<bool> {   [RequiredArgument]   public InArgument<string> DropLocation { get; set; }   [RequiredArgument]   public InArgument<string> WackResultsFilename { get; set; }   [RequiredArgument]   public InArgument<string> WackReportFilename { get; set; }   public OutArgument<string> WackReportFilePath { get; set; }   // If your activity returns a value, derive from CodeActivity<TResult>   // and return the value from the Execute method.
protected override bool Execute(CodeActivityContext context)   {     string dropLocation = context.GetValue(this.DropLocation);     string wackResultsFilename =       context.GetValue(this.WackResultsFilename);     string wackReportFilename = context.GetValue(this.WackReportFilename);     var dropLocationFiles = Directory.GetFiles(dropLocation, "*.*",       SearchOption.AllDirectories);     if (dropLocationFiles.Any())     {       var resultFile = dropLocationFiles.FirstOrDefault(         f => Path.GetFileName(f).ToLowerInvariant() ==           wackResultsFilename.ToLowerInvariant());       if (!string.IsNullOrWhiteSpace(resultFile))       {         var xDocument = XDocument.Load(resultFile);         var reportElement = xDocument.Element("REPORT");         if (reportElement != null)         {           var resultAttribute = reportElement.Attribute("OVERALL_RESULT");           if (resultAttribute != null)           {             context.SetValue(this.WackReportFilePath,               Path.GetDirectoryName(resultFile));             var validationResult = resultAttribute.Value;             // Fail or Pass             if (validationResult.ToLowerInvariant() == "fail")             {               return false;             }             return true;           }         }       }       throw new InvalidOperationException(         "Unable to find the Windows App Certification Kit results file!");     }     else     {       throw new InvalidOperationException(         "There are no files in the drop location!");     }     throw new InvalidOperationException(       "Unknow error while checking the content of the Windows App       Certification Kit results file!");   } }

默认情况下,生成活动在生成代理上运行。 但是在某些情况下,你可能希望该活动作为第一步尽早执行,甚至在生成开始之前执行,或者希望该活动作为生成结束之前的最后一步执行。 为了实现这种灵活性,你需要让活动在控制器而不是代理上运行。 因此,应使用特性 BuildActivityAttribute,它将枚举值 HostEnvironmentOption.All 作为参数(如图 7 所示)。 请注意,如果未使用正确的 Host­EnvironmentOption 选项,在生成过程中会收到错误。

类 CheckWackResultsActivity 从 Code­Activity<bool> 继承,以便其结果值可用于在生成报告中显示正确的消息。 要显示此消息,可以使用 TFS 2012 中提供的一个新活动: WriteCustomSummaryInfo。 要向生成报告添加消息,则此活动十分有用,因为通过它可以在生成报告中添加专用类别,而不是添加简单的文本。

你必须指定以下属性:

  • Message,它是要显示在报告中的文本
  • SectionDisplayName,它与节标题相对应
  • SectionKey,它是节的唯一值
  • SectionPriority,它定义新节在报告中的位置(0 表示最高优先级,标准节从 100 开始)

因此,通过使用新活动 WriteCustomSummaryInfo,可以修改生成过程以检查验证结果,并在生成报告中添加一个新节。 图 8 显示了修改后的生成过程的 XAML 代码。

图 8 修改后的生成过程

<Sequence DisplayName="Windows 8" sap2010:WorkflowViewState.IdRef="Sequence_4">   <Sequence.Variables>     <Variable x:TypeArguments="x:Boolean" Name="WackToolRanSuccessfully" />     <Variable x:TypeArguments="x:String" Name="WackReportFilePath" />   </Sequence.Variables>   <c:CheckWackResultsActivity DropLocation="[DropLocation]"     sap2010:WorkflowViewState.IdRef="CheckWackResultsActivity_3"     Result="[WackToolRanSuccessfully]"     WackReportFilePath="[WackReportFilePath]"     WackReportFilename="ValidationResult.html"     WackResultsFilename="ValidationResult.xml" />   <If Condition="[Not WackToolRanSuccessfully]"     sap2010:WorkflowViewState.IdRef="If_4">     <If.Then>       <Sequence sap2010:WorkflowViewState.IdRef="Sequence_6">         <mtbwa:WriteCustomSummaryInformation           sap2010:WorkflowViewState.IdRef=             "WriteCustomSummaryInformation_2"           Message="['Windows App Certification Kit ran with errors.
Click [here](' &amp; WackReportFilePath &amp; ') to           access the folder containing the report.']"           SectionDisplayName="Windows 8" SectionKey="Windows8"             SectionPriority="75"           mva:VisualBasic.Settings=             "Assembly references and imported namespaces             serialized as XML namespaces" />         <mtbwa:WriteBuildError           sap2010:WorkflowViewState.IdRef="WriteBuildError_1"           Message="Windows App Certification Kit ran with errors." />         <mtbwa:SetBuildProperties           CompilationStatus=             "[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Failed]"           DisplayName="Set Status and CompilationStatus to Failed"           sap2010:WorkflowViewState.IdRef="SetBuildProperties_1"           mtbwt:BuildTrackingParticipant.Importance=             "Low" PropertiesToSet="CompilationStatus" />       </Sequence>     </If.Then>     <If.Else>       <mtbwa:WriteCustomSummaryInformation         sap2010:WorkflowViewState.IdRef="WriteCustomSummaryInformation_1"         Message="['Windows App Certification Kit ran with success.
Click [here](' &amp; WackReportFilePath &amp; ') to         access the folder containing the report.']"         SectionDisplayName="Windows 8" SectionKey="Windows8"           SectionPriority="75"         mva:VisualBasic.Settings=           "Assembly references and imported namespaces           serialized as XML namespaces" />     </If.Else>   </If> </Sequence>

图 8 中可以看到,如果验证失败,则编译状态设置为“失败”以防止生成过程继续执行。 这不是必需的,如果希望无论验证结果如何都始终完成生成过程,可以将其删除。

现在,每次触发生成时,生成报告都会显示一个专用于 Windows 8 的新节,该节显示验证的结果(请参见图 9)。

The New Section in the Build Report Showing Validation Results
图 9 生成报告中显示验证结果的新节

使用 WriteCustomSummaryInfo 时,只能用文本和链接改进生成报告。 如果需要进行更复杂的修改(例如,添加图像),仍可以应用 TFS 2010 中使用的方法。

有许多方法可以为 Windows 应用商店应用程序自定义生成过程模板,值得高兴的是,这些自定义方法与用于 Team Foundation Service 和内部 TFS 的基本相同。

Thomas Lebrun 是 Microsoft 法国合作伙伴 Infinite Square 的技术主管,该公司致力于开发包括 Windows 8/Windows Phone、Team Foundation Server、SharePoint 等在内的技术。 Lebrun 编写了两本 Windows Presentation Foundation 和模型-视图-视图模型模式方面的法文书籍,他也经常在法国举办的活动中发表演讲。 你可以在 blog.thomaslebrun.net 上访问他的博客,也可在 Twitter twitter.com/thomas_lebrun 关注他。

衷心感谢以下技术专家对本文的审阅: Chris Patterson (Microsoft)