【问题标题】:Msbuild: transforming one itemgroup into another itemgroup with a different structureMsbuild:将一个项目组转换为具有不同结构的另一个项目组
【发布时间】: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 文件,其中包含:&lt;Execution parallelTestCount="0"&gt;(0 表示自动检测,默认为 1)并且需要使用 /m 参数和/或 &lt;Msbuild BulidInParallel="true"&gt; 调用 msbuild

【问题讨论】:

    标签: msbuild mstest


    【解决方案1】:

    试试这个:

    <ItemGroup>
       <TestAssemblies Include="**\bin\**\*.Tests.dll" />
       <TestAssembliesToBuild Include="Project.mstest.proj">
         <Properties>TestAssembly=%(TestAssemblies.FileName)</Properties>
       </TestAssembliesToBuild>
    </ItemGroup>
    <Message Text="'%(TestAssembliesToBuild.Properties)'" />
    

    【讨论】:

    • 这让我走上了正轨。我已将 TestAssemblies 定义为全局项组,并且 TestAssembliestoBuild 在同一个项组中定义不起作用,但如果它位于任务内的项组内,它确实有效。之后效果很好。
    猜你喜欢
    • 1970-01-01
    • 2012-06-26
    • 2017-01-01
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    相关资源
    最近更新 更多