Я, возможно, задаю здесь не тот вопрос, и я открыт для этого, поэтому я немного расскажу о том, что я пытаюсь сделать.Я вызываю mstest через проект msbuild после динамического поиска всех тестовых сборок.Я вызываю mstest отдельно для каждой тестовой сборки, чтобы результаты можно было импортировать в teamcity (мой сервер CI), как только они стали доступны, вместо того, чтобы ждать завершения всех из них, прежде чем показывать какой-либо прогресс в TC.
Проблема заключается в том, что при этом запускается один тест за раз, и в сочетании с медленными издержками (даже на четырехъядерном процессоре i7 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 single-Потоковая, вся моя сборка (которая включает компиляцию, создание снимков приложений и баз данных, развертывание схемы для пары баз данных, которые используются в некоторых модульных тестах, а затем, наконец, запуск mstest) заняла около 9m30s.После этого изменения прошло ~ 7м.
Однако, прежде чем получить ответ на этот вопрос, я просто попытался запустить один экземпляр mstest, чтобы посмотреть, насколько он улучшится, и это занимает около 4m50s (из которых 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">