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)'" />