#msbuild #mstest
#msbuild #mstest
Вопрос:
Возможно, я задаю здесь неправильный вопрос, и я открыт для этого, поэтому я немного расскажу о том, что я пытаюсь сделать. Я вызываю 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.0Common7IDEMSTest.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>
Итак, в этом суть моего вопроса, предполагая, что я даже задаю правильные вещи: как мне преобразовать itemgroup TestAssemblies itemgroup во что-то похожее на itemgroup ProjectsToBuild?
На случай, если это не очевидно, имена элементов в TestAssemblies — это *.tests .Имена файлов Dll, хотя мне нужно, чтобы это имя было a внутри элемента, а имя элемента 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) заняла около 930 минут. После этого изменения это заняло ~ 7m.
Однако, прежде чем получить ответ на этот вопрос, я просто попробовал запустить один экземпляр mstest, чтобы посмотреть, насколько это улучшит ситуацию, и это занимает около 4 миллионов (из которых запуск mstest занимает чуть более 1 минуты). Недостатком является то, что мне приходится ждать завершения всех тестов, прежде чем получать результаты, но, учитывая ошеломляющее улучшение с 6m до 1m, это вполне приемлемый компромисс.
Чтобы было понятно, единственное отличие заключается в том, что 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:"TestResultsResults.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='TestResultsResults.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)'" />
Комментарии:
1. Это навело меня на правильный путь. Я определил TestAssemblies как глобальную itemgroup, и TestAssembliestoBuild не работает, определенный в той же itemgroup, но он работает, если он находится внутри itemgroup, которая находится внутри задачи. После этого это отлично работает.