Msbuild: преобразование одной группы элементов в другую группу элементов с другой структурой

Возможно, я задаю здесь не тот вопрос, и я открыт для этого, поэтому я немного расскажу о том, что я пытаюсь сделать. Я вызываю mstest через проект msbuild после динамического поиска всех тестовых сборок. Я вызываю mstest отдельно для каждой тестовой сборки, чтобы результаты можно было импортировать в teamcity (мой сервер CI), как только они стали доступны, вместо того, чтобы ждать завершения всех из них, прежде чем показывать какой-либо прогресс в TC.

Проблема состоит в том, что он запускает один тест за раз, и в сочетании с медленными издержками (даже для i7 quad, mstest требует 3-5 секунд на открытие для каждого проекта) и многими тестами, тесты занимают несколько минут, чтобы запустить.

Используя задачу msbuild с BuildInParallel=true (и вызывая с параметром / m), можно создать несколько проектов одновременно.

Так что я пытаюсь сделать это

  • получить список всех *.Tests.dll
  • Вызвать цель ExecMsTest в одном и том же проекте параллельно для каждого.dll

    <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 должно быть Project.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 минут. После этого изменения это заняло ~7м.

Однако, прежде чем получить ответ на этот вопрос, я просто попытался запустить один экземпляр mstest, чтобы посмотреть, насколько он улучшится, и это займет около 4 мсек (из которых 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) и нужно вызвать msbuild, используя /m параметр и / или <Msbuild BulidInParallel="true">

1 ответ

Решение

Попробуй это:

<ItemGroup>
   <TestAssemblies Include="**\bin\**\*.Tests.dll" />
   <TestAssembliesToBuild Include="Project.mstest.proj">
     <Properties>TestAssembly=%(TestAssemblies.FileName)</Properties>
   </TestAssembliesToBuild>
</ItemGroup>
<Message Text="'%(TestAssembliesToBuild.Properties)'" />
Другие вопросы по тегам