【发布时间】:2011-04-05 16:39:13
【问题描述】:
我可能在这里问错了问题,我对此持开放态度,所以我将提供一些我正在尝试做的事情的背景。在动态找到所有测试程序集之后,我通过 msbuild 项目调用 mstest。我为每个测试程序集单独调用 mstest,以便结果可以在它们可用时立即导入到 teamcity(我的 CI 服务器)中,而不是在显示 TC 中的任何进展之前等待它们全部完成。
问题在于,这一次只运行一个测试,再加上缓慢的开销(即使在 i7 quad 上,mstest 需要 3-5 秒的开销来打开每个项目)和许多测试,测试需要运行几分钟。
使用 msbuild task 和 BuildInParallel=true(并使用 /m 参数调用),可以一次构建多个项目。
所以我想做的是
- 获取所有 *.Tests.dll 的列表
-
为每个 .dll 并行调用同一项目中的 ExecMsTest 目标
<PropertyGroup> <MsTestExePath Condition="'$(MsTestExePath)'==''">C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</MsTestExePath> <MsTestSettingsPath Condition="'$(MsTestSettingsPath)'==''">Project.testsettings</MsTestSettingsPath> </PropertyGroup> <ItemGroup> <TestAssemblies Include="**\bin\**\*.Tests.dll" /> </ItemGroup> <Target Name="RunTests"> <Message Text="Found test assemblies: @(TestAssemblies)" /> <MakeDir Directories="TestResults" /> <MsBuild Projects="@(ProjectsToBuild)" Targets="ExecMsTest" BuildInParallel="True" /> </Target> <Target Name="ExecMsTest"> <Message Text="Running tests in $(TestAssembly)" /> <!-- show TC progress --> <Message Text="##teamcity[progressMessage 'Running tests in $(TestAssembly).dll']" Importance="High" /> <PropertyGroup> <MsTestCommand>"$(MsTestExePath)" /testcontainer:"$(TestAssembly)" /resultsfile:"TestResults\$(TestAssembly).trx" /testsettings:"$(MsTestSettingsPath)"</MsTestCommand> </PropertyGroup> <!-- Message Text="Exec: $(MsTestCommand)" / --> <Exec Command="$(MsTestCommand)" ContinueOnError="true" /> <!-- import data to teamcity test results --> <Message Text="##teamcity[importData type='mstest' path='TestResults\$(TestAssembly).trx']" /> <Message Text="Tests complete from $(TestAssembly)" />
但是,这并不完全正确。您可以看到我的项目组名为 TestAssemblies,但我将 @(ProjectsToBuild) 传递给 mstest。这是因为 msbuild 任务需要不同格式的项目组,例如:
<ItemGroup>
<ProjectsToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=Project.UI.Tests</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=Project.Model.Tests</Properties>
</ProjectsToBuild>
</ItemGroup>
所以这是我的问题的症结所在,假设我什至问的是正确的事情:我如何将 TestAssemblies 项目组转换为类似于 ProjectsToBuild 项目组的东西?
如果不是很明显,TestAssemblies 中的项目名称是 *.tests.Dll 文件名,而我需要该名称是项目内部的,并且 ProjectsToBuild 项目的名称都是项目。 mstest.proj 文件(因为它们都在调用同一个文件)。
感谢@Spider M9,这可行:
<ItemGroup>
<TestAssemblies Include="**\bin\**\*.Tests.dll" />
</ItemGroup>
<Target Name="RunTests">
<Message Text="Found test assemblies: @(TestAssemblies)" />
<ItemGroup>
<TestAssembliesToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=%(TestAssemblies.FileName);FullPath=%(TestAssemblies.FullPath)</Properties>
</TestAssembliesToBuild>
</ItemGroup>
<MakeDir Directories="TestResults" />
<MsBuild Projects="@(TestAssembliesToBuild)" Targets="ExecMsTest" BuildInParallel="True" />
</Target>
运行 msbuild 单线程,我的整个构建(包括编译、构建应用程序和数据库快照、为在某些单元测试中使用的几个数据库部署架构,然后最终运行 mstest)花了大约 9 分 30 秒。进行此更改后,大约需要 7m。
但是,在得到这个问题的答案之前,我只是尝试运行一个 mstest 实例,看看它会改进多少,这大约需要 4 分 50 秒(其中 mstest 需要 1 分钟多一点的时间来运行)。缺点是我必须等到所有测试都完成才能获得结果,但考虑到从 6m 到 1m 的惊人改进,这是一个完全可以接受的权衡。
需要明确的是,唯一的区别是 mstest 启动一次,而不是启动十几次,而且多任务处理可能也有一些好处。我在 Core i7-860(4 个物理内核,8 个逻辑内核)上运行它,我怀疑内核数量会极大地影响此更改所带来的改进水平。
这是我的新运行测试:
<Target Name="RunTests">
<Message Text="Found test assemblies: @(TestAssemblies)" />
<MakeDir Directories="TestResults" />
<!-- this executes mstest once, and runs all assemblies at the same time. Faster, but no output to TC until they're all completed -->
<PropertyGroup>
<MsTestCommand>"$(MsTestExePath)" @(TestAssemblies->'/testcontainer:"%(FullPath)"', ' ') /resultsfile:"TestResults\Results.trx" /testsettings:"$(MsTestSettingsPath)"</MsTestCommand>
</PropertyGroup>
<Message Text="##teamcity[progressMessage 'Running tests']" Importance="High" />
<Message Text="Exec: $(MsTestCommand)" />
<Exec Command="$(MsTestCommand)" ContinueOnError="true" />
<Message Text="##teamcity[importData type='mstest' path='TestResults\Results.trx']" />
</Target>
另外,您需要一个 testsettings 文件,其中包含:<Execution parallelTestCount="0">(0 表示自动检测,默认为 1)并且需要使用 /m 参数和/或 <Msbuild BulidInParallel="true"> 调用 msbuild
【问题讨论】: