我可能会在这里提出错误的问题,我对此持开放态度,所以我会给出一些我正在尝试做的事情的背景知识。在动态查找所有测试程序集后,我通过msbuild项目调用mstest。我为每个测试程序集单独调用mstest,以便结果可以在它们可用时立即导入teamcity(我的CI服务器),而不是在显示TC中的任何进度之前等待所有结果完成。
问题在于,它一次运行一个测试,并且结合缓慢的开销(即使在i7 quad上,mstest需要3-5秒的开销才能为每个项目打开)和许多测试,测试需要几分钟就可以跑了。
将msbuild task与BuildInParallel = true一起使用(并使用/ m参数调用),可以一次构建多个项目。
所以我要做的就是
为每个.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)" />
但是,这不太对。您可以看到我的itemgroup被称为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秒。在这个变化之后,它需要大约7米。
然而,在得到这个问题的答案之前,我只是尝试运行一个mstest实例,看看它会改进多少,并且需要大约4m50(其中mstest需要稍微超过1分钟才能运行)。缺点是我必须等到所有测试完成才能获得结果,但考虑到从6米到1米的惊人改进,这是一个完全可以接受的权衡。
要明确的是,唯一的区别是mstest是开始一次,而不是开始十几次,并且可能还有一些从多任务处理中获益。我在Core i7-860(4个物理内核,8个逻辑内核)上运行它,我怀疑内核数量将极大地影响这一变化所带来的改进水平。
这是我的新RunTests:
<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">
答案 0 :(得分:2)
试试这个:
<ItemGroup>
<TestAssemblies Include="**\bin\**\*.Tests.dll" />
<TestAssembliesToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=%(TestAssemblies.FileName)</Properties>
</TestAssembliesToBuild>
</ItemGroup>
<Message Text="'%(TestAssembliesToBuild.Properties)'" />