- PVSM.RU - https://www.pvsm.ru -

Windows Phone и непрерывная интеграция в TeamCity

Хочу поделиться опытом в настройке системы непрерывной интеграции для проекта Windows Phone 7 в Team City. Надеюсь, сэкономлю тем, кто пойдёт той же тропой, потраченные мной самим время и нервы.

Дано:

  1. Довольно-таки массивное приложение Windows Phone 7 c unit-тестами, реализованными средствами Silverlight Toolkit [1].
  2. Настроенная сборка приложения в TeamCity без запуска unit-тестов. Агент для сборки — «физическая» (в смысле, не виртуальная) машина.

Необходимо:

  1. Настроить ещё одного build-агента TeamCity на виртуальной машине под VMWare.
  2. Запускать unit-тесты при сборках и сбора результатов их выполнения в статистику TeamCity.

Настройка билд-агента

В качестве билд-агента используется виртуальная машина с Windows Server 2008 R2. Настройка поначалу казалось простой — ставим Visual Studio, Windows Phone SDK и самого агента (агент скачивается прямо с сайта развёрнутого TeamCity). Тестовый проект без Silverlight «взлетел» без проблем — сразу запутились unit-тесты, появился контроль покрытия кода с использованием встроенного в TeamCity dotCover:
Windows Phone и непрерывная интеграция в TeamCity
Приятно было получить такой результат за 5 минут с нуля!

Запуск эмулятора

Загоревшись, я взялся за запуск сборки Windows Phone. Здесь и началось самое интересное.
Нужно сказать, что в TeamCity у нас уже была настроена сборка, в процессе которой нужно было выполнять определённый код подготовки данных. Этот код использовался в «живом» приложении, и его поленились отрезать от Silverlight Runtime. Поэтому при сборке код выполнялся в эмуляторе, вызываемом агентом TeamCity. Такая схема работала на имеющемся билд-агенте.
Однако настроить аналогичную сборку на новом билд-агенте удалось только через пару дней, наполненных в основном руганью. Вот основные косяки, которые я поймал:

  • Windows Phone SDK не устанавливался — жаловался, что ему нужна Windows 7, не признавая серверную ОС. Выручили гугл и MSDN [2]. Поскольку у меня был полностью скачанный SDK, после редактирования baseline.dat по инструкциям я запускал setup.exe без ключей, при этом всё установилось как надо.
  • Эмулятор не запускался, поскольку не был установлен Windows Media Player [3]. Установка его под Windows Server 2008 тоже оказалась не вполне интуитивной [4].
  • Говорят [5], что эмулятор может не запускаться без определённых флажков и ручной (!) правки конфигов виртуальной машины, но меня сие миновало. Возможно потому, что мне нужен был эмулятор Windows Phone 7, а танцы с бубном описаны для эмулятора Windows Phone 8. Кроме того, админ заверял, что в настройках виртуалки «всё включено».
  • Эмулятор не запускался, пока объём ОЗУ виртуальной машины не увеличили с 1 ГБ до 2 ГБ. При этом не запускался молча — ни намёка на причину!

Кстати, через ярлык, создаваемый при установке SDK, эмулятор у меня так и не запускается. Не стал разбираться почему, поскольку при вызове его из утилиты управления он стартует нормально.

Самая большая проблема всплыла в конце, когда эмулятор наконец-то запустился. Эмулятор при запуске предупреждал, что для нормальной работы ему нужна [6] нормальная видеокарта, которую «пробросить» в виртуальную машину не получилось. Несмотря на то, что после такого грозного предупреждения эмулятор нормально работал, кнопку ОК нужно было кому-то нажать. Предполагалось, что эмулятор будет запускаться службой агента TeamCity и не будет появляться на экране, поэтому кнопку было нажимать некому.
В отчаянной попытке я пытался кодом нажать [7] эту злосчастную кнопку, но это не сработало при запуске эмулятора из службы агента.
Единственное, что я смог в итоге придумать — запускать билд-агента не как службу, а как приложение. В этом случае эмулятор запускался нормально, и на кнопку можно было нажать вручную (это достаточно делать только после перезагрузки сервера — будучи однажды запущенным, эмулятор используется для всех последующих сборок). Тем не менее я оставил код автоматического нажатия на кнопку, чтобы перезагрузка вирутальной машины была «необслуживаемой».
Запуск агента как приложения оказался несложным — в каталоге bin агента (c:BuildAgentbin по умолчанию) есть хороший набор bat-файлов, которыми можно снести ненужную уже службу агента, а также запустить агента как приложение (здесь [8] есть на эту тему).
Итак, прописываем нужный bat-файл в автозапуск, настраиваем автоматический вход после запуска системы [9] (для «необслуживаемой» перезагрузки) и вуаля! Сборка работает!
Последними штрихами явились запуск эмулятора и блокировка рабочей станции в скрипте запуска агента. Запуск эмулятора был нужен, чтобы исключить задержку в первой сборке, выполняемой после перезагрузки виртуальной машины, а блокировка — потому что админ попросил («негоже, когда консоль машины открыта!»).
Следующим этапом стало добавление в скрипт сборки запуска unit-тестов. Тесты, как я уже говорил, запускались под Unit Test Framework, идущем в комплекте с Silverlight Toolkit. Запуск на эмуляторе был уже пройденным этапом, поэтому тесты завелись без проблем:
Windows Phone и непрерывная интеграция в TeamCity

Передача результатов тестов в TeamCity

Далее логично захотелось, чтобы тесты не просто запускались, а «валили» сборку при непрохождении. Для этого нужно было передать в TeamCity результаты выполнения тестов.
Очередное отступление — мы управляли эмулятором из сборочных скриптов через CoreCon API, за что [10] спасибо Justin Angel [11] и arty87 [12]. То есть, в скрипте сборки запускалось консольное приложение, которое собственно и управляло эмулятором.
Первое решение, которое пришло в голову и сразу было опробовано — «повалить» сборку при поваленном тесте, выйдя из приложения управления эмулятором с ненулевым exit code. При этом можно было ещё написать что-то в консоль — потом это можно будет увидеть в build log-е TeamCity.
Но хотелось красоты, чтобы TeamCity вел статистику тестов, да ещё и по ходу сборки было видно, как они выполняются (очень медитативно, между прочим). Поэтому было раскопано Service Message API [13], которое по факту оказалось «отловом» специального вида тегов из консольного вывода. Тут всплыло две особенности:

  1. Теги должны быть однострочными. TeamCity воспринимает разрыв строки как конец тега, соответственно тег оказывается неверного формата и игнорируется. В документации [13] в примере показана передача символов перевода строк последовательностью "|n|r", но у меня это не заработало.
  2. Теги не должны быть слишком длинными. Попытка вырезать переводы строк из стектрейса и передать его как details в теге testFailed привела к тому, что TeamCity самовольно разбил получившуюся длинную строку где-то на 300 символах со всеми вытекающими. Интересно, что остаток строки длиной явно больше 300 символов показывался в Build Log без всяких разбиений. Детально я не экспериментировал и решил стектрейс ошибок выводить просто в Build Log

Осталось решить две задачи: отловить факт запуска и завершения тестов и передать это всё из кода, выполняемого в эмуляторе, в утилиту управления эмулятором (в консоль ведь должна писать утилита, а не эмулятор, на котором запускаются тесты).
Первая задача решилась просто. В фреймворке для Unit-тестов есть возможность подписаться на события запуска и завершения тестов, тестовых классов и целых сборок, в обработчиках которых мы можем узнать много информации (результат теста, Exception при сбое теста, время начала и завершения и т.п.). Документации по фреймворку я особо не искал, проще оказалось изучить «методом тыка».
Итак, для запуска unit-тестов с выводом результата мы немного модифицируем стандартный стартовый код:

private void MainPage_Loaded(object sender, RoutedEventArgs e) 
{ 
  var testPage = UnitTestSystem.CreateTestPage(GetSettings()) as IMobileTestPage;
  BackKeyPress += (x, xe) => xe.Cancel = testPage.NavigateBack(); 
  (Application.Current.RootVisual as PhoneApplicationFrame).Content = testPage; 
}

Модификация заключается в вызове функции GetSettings. Вот эта самая функция:

public static UnitTestSettings GetSettings()
{
    var settings = UnitTestSystem.CreateDefaultSettings();
    settings.TestHarness.TestClassStarting += TestHarnessTestClassStarting;
    settings.TestHarness.TestClassCompleted += TestHarnessTestClassCompleted;
    settings.TestHarness.TestMethodStarting += TestHarnessTestMethodStarting; 
    settings.TestHarness.TestMethodCompleted += TestHarnessTestMethodCompleted;
    return settings;
}

Теперь мы пристально следим за нашими тестами. Код обработчиков событий тривиален, приводить его не буду.
Теперь нам нужно передать результаты слежки в программу управления эмулятором, где и вывести их в консоль в виде тегов Service Message API.
В качестве канала передачи использовался Isolated Storage, как собственно и было описано в статье [10]. Правда, в отличие от предложенного FileDeployer для обмена файлами использовался класс RemoteIsolatedStorage.
Однако этот способ передачи оказался не очень хорош. Поскольку хотелось передачи информации о тестах в режиме реального времени, результаты тестов сразу записывались в файл Isolated Storage на эмуляторе, а утилита управления периодически вычитывала этот файл и выводила в консоль вновь полученные результаты. Из-за блокировок на файл запись периодически «падала», что никак не отражалось на выполняемых тестах, но приводило к потере информации о них. Решили проблему «костылём» — ловлей ошибок записи и повторами при ошибках. Конечно, по фен-шуй надо использовать более подходящий способ обмена данными с эмулятором, например через IP. Однако заморачиваться на этом уже не хотелось, поскольку долгожданный результат уже был получен:
Windows Phone и непрерывная интеграция в TeamCity

Осталось нереализованным желание контролировать ещё и покрытие кода unit-тестами. Как оказалось, нормально контролировать покрытие кода в SIlverlight Runtime не получится. Большие люди советуют [14] перекомпилировать тестируемый код в обычном .NET CLR для получения покрытия. Однако с имеющимся объемом тестов, порой весьма жестко привязанных к Silverlight Runtime, это посчитали делать нецелесообразным. Тем не менее мечта осталась, и я попробую её реализовать на начавшемся небольшом проекте. Надеюсь, всё получится и я смогу поделиться своей радостью.

P.S. Это мой первый пост, поэтому готов к конструктивной критике и предложениям.

Автор: lvr

Источник [15]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/windows-phone/31732

Ссылки в тексте:

[1] Silverlight Toolkit: http://silverlight.codeplex.com/

[2] гугл и MSDN: http://blogs.msdn.com/b/astebner/archive/2010/05/02/10005980.aspx

[3] не был установлен Windows Media Player: http://www.kunal-chowdhury.com/2011/09/solution-for-program-can-start-because.html

[4] интуитивной: http://social.technet.microsoft.com/Forums/en-US/winservergen/thread/4db0c13a-9fb4-41a0-aebb-9c47df8a3536/

[5] Говорят: http://www.developer.nokia.com/Community/Wiki/Windows_Phone_8_SDK_on_a_Virtual_Machine_with_Working_Emulator

[6] нужна: http://habrastorage.org/storage2/abf/02a/7e6/abf02a7e621d9a1d6ef15152d441d024.jpg

[7] нажать: http://stackoverflow.com/questions/2744111/sending-keystrokes-to-a-program

[8] здесь: http://confluence.jetbrains.com/display/TCD7/Setting+up+and+Running+Additional+Build+Agents

[9] автоматический вход после запуска системы: http://guruadmin.ru/page/how-to-enable-autologon-in-windows-7-windows-server-2008

[10] за что: http://habrahabr.ru/post/117294/

[11] Justin Angel: http://justinangel.net/WindowsPhone7EmulatorAutomation

[12] arty87: http://habrahabr.ru/users/arty87/

[13] Service Message API: http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests

[14] советуют: http://channel9.msdn.com/posts/Code-Coverage-for-Silverlight-and-Windows-Phone-ViewModels

[15] Источник: http://habrahabr.ru/post/175661/