четверг, 15 мая 2014 г.

Кирпичики 2

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

SCRUM


 Суть методологии SCRUM настолько проста и бесхитростна, что всю теорию можно изучить за пару часов, прочитав пару статей. Всё становится интереснее, когда это знание пытаются применить на практике. Судя по общению с коллегами из разных контор, ВСЕ так или иначе адаптируют его под себя и частенько до такой степени, что от оригинала остается мало. Но тем не менее итеративная модель разработки, построенная на идеях командной, работы имеет большие плюсы. Последние годы мы с небольшими вариациями используем следующий процесс:
  1. У нас есть бэклог, в который мы стараемся заносить все наши планы и так или иначе его приоритезировать и оценивать.
  2. Мы используем 2х недельные итерации.
  3. В начале итерации (спринта) разбиваем истории на задачки и вешаем их на доску вида STORY\TASKS\IN PROGRESS\REVIEW\DONE.
  4. Каждый день проводим короткие standup'ы.

Одним из очевидных плюсов всего этого является вовлеченность команды во все аспекты разработки. Но увы многие, умудренные опытом разработчики занимают позицию в стиле "вы мне скажите что сделать - я сделаю" и пытаются самоустраниться из процесса принятия решений.


РОЛЬ PRODUCT OWNER'а



В SCRUM-процессе одна из основных ролей это роль Product Owner'а. Без человека, который понимает, что он хочет, и готов инвестировать в это приличное время, работать не получается. К сожалению, в больших продуктовых компаниях  формальным Product Owner'ом является Product Manager, на котором висит несколько проектов и куча бюрократии. Надеяться на то, что он будет детально прорабатывать истории и вникать в детали, не приходится. Хорошо, если он может в общих чертах описать проблему и ожидаемый результат. Опыт использования разного рода аналитиков, проксирующих через себя функции Product Owner'а, в моей практике оказалась не очень удачным.  Для себя я сделал вывод, что менеджер проекта сам должен взять на себя функцию прокси между командой и настоящим product owner'ом и вести продукт опираясь на мнение Product Managment'а, stackholder'ов и команды. Ну это при условии, что ему не все равно, что будет на выходе. :)

Definition of Done


При работе в команде важно, чтобы у всех было единое понимание готовности той или иной части. Очень часто бывает, что разработчик считает свой код рабочим, если ему удается его просто собрать.

Мы применяем достаточно простой чеклист:
  1. Фича собрана на CI сервере.
  2. Код должен пройти Code Review командой.
  3. Код покрыт unit тестами.
  4. Все тесты зеленые.
  5. Фичу посмотрели члены команды в тестовой лабе.
  6. Нет ошибок\варнингов.
  7. Нет серьезных замечании со стороны статических анализаторов кода.

Мы живем в реальном мире и, конечно же, часть пунктов этого списка часто нарушается, но при этом всем немного стыдно. :)

BACKLOG


Backlog и его ведение должно быть как можно проще, в противном случае на его поддержку быстро начнут забивать. Актуальный бэклог с простым механизмом оценки и приоритетов может сослужить хорошую службу и позволит не забыть что-то важное. Я использую PivotalTracker - он очень прост, и в этом его прелесть. Легко написать историю, оценить ее, изменить приоритет и поменять состояние.
По бэклогу мы строим burndown chart, который с некой долей вероятности показывает нам насколько далеки мы от релиза. Надо понимать, что backlog постоянно изменяется и дополняется, потому на графике неизбежны всплески и провалы.

SPRINT BURNDOWN CHART


При планировании спринта истории разбиваются на конкретные задачки и вешаются на доску. В процессе спринта они постепенно переходят в состояние Done. График этого процесса достаточно интересен. По росту количества незапланированной работы можно понять насколько хорошо мы планируем спринт, а по общему количеству часов понять, сколько стоит брать в следующий раз. Не стоит особо париться, если окажется, что средняя скорость члена команды будет около 2х часов в день. Важно, чтобы эта цифра постепенно увеличивалась или, как минимум, не уменьшалась. Вообще, метрики в SCRUM - это не повод помахать шашкой, а лишь повод задуматься.

CODE REVIEW


Ревью кода членами команды - одна из полезнейших практик. И самое главное в этом даже не поиск ошибок, их как раз выявляют на этом этапе достаточно редко. Главное это то, что в процессе ревью с кодом знакомятся остальные члены команды. Это ведет к расширению области знании как в конкретной технологии, так и в продукте в целом. Мы для ревью кода используем механизм pull request'ов GitHub'а. При этом ревьюятся не отдельные коммиты, а законченная функциональность или существенная ее часть. В этом нам помогает модель gitflow, где на каждую новую фичу создается отдельная ветка. 
Мне кажется более правильной модель, когда ревьюер кода не назначается, а код смотрит как можно большее число членов команды.  Совместное владение кодом ведет к улучшению состояния этого кода.

SOURCE CONTROL


Активная фаза разработки требует наличия системы контроля версии. Я начинал с CVS, прошел SVN, StarTeam, Mercurial и остановился на git. В основу этого выбора легла статья "A successful Git branching model". На мой взгляд, подход, позволяющий независимо разрабатывать фичи продукта и постоянно иметь рабочую ветку, готовую к поставке, повышает качество и снижает напряженность при возникновении непредвиденных трудностей в процессе разработки фичи. Последнее время наша команда придерживается следующих простых правил.

  1. Бренч master содержит результаты итерации в виде squash'ных  коммитов (по одному на итерацию), в комментарии которого максимально подробно описывают все, что было сделано по каждой фиче, и по сути являются ChangeLog'ом.
  2. Бренч dev - завершенные фичи текущей итерации. Каждый коммит - завершенная фича с комментариями максимально подробно ее описывающими.
  3. Бренчи для фичи могут содержать любые коммиты.

В основном я доволен этим подходом. Меня немного огорчает тот факт, что при использовании --squash теряется информация об исходных коммитах, а без использования squash в ветках dev и master появляется огромное число мелких коммитов с малоинформативными сообщениями, что затрудняет понимание истории разработки.

Так же к достоинствам git хочу отнести очень демократичный набор инструментов. В нашей команде есть люди использующие как консоль, так и разные оконные клиенты.


Continues Integration


Я уже не могу себе представить, как можно вести разработку, не имея Continues Integration, в задачи которого входит быстрая сборка последней версии, сборка всяких артефактов типа MSI\CD, прогон тестов и установка на тестовые машины. Причем основная причина такой любви к CI это банальная лень. Да, мне совершенно не хочется тратить время на сборку, установку и проверку базовой работоспособности новой версии вручную. Пусть это делает компьютер. За те 3-10 минут, которые  идет процесс сборки, я лучше посмотрю в окно или почитаю почту.
Я прошел длинный путь в сборке проектов, начав с nmake и bat, пройдя  через скрипты на Visual Build, прежде чем пришел к готовым  CI серверам.
Попробовав за 6 лет CruiseControl.NET, Hudson, Jenkins и TeamCity , я остановился именно на последнем. Прежде всего TC поставляется из коробки с кучей полезных вещей, которые к Jenkins надо еще прикручивать, ко всему прочему я полностью укладываюсь в бесплатные рамки TeamCity.
Еще один из аспектов CI серверов, к котором я пришел: их инфраструктура должна быть как можно проще. В идеале, помимо самого агента TeamCity я бы хотел видеть там только Visual Studio (если без нее никак). Чем билдер проще, тем легче добавлять агентов и восстанавливать его после падений.

Спасибо за внимание.

вторник, 6 мая 2014 г.

Кирпичики 1

Что такое опыт для программиста или менеджера? Почти в каждой вакансии мы читаем о том, что нужен опыт от N лет. Но из чего состоит этот опыт?

Практически все проекты сводятся к достаточно похожим решениям, как с точки зрения реализации, так и с точки зрения самих подходов к разработке. Освоив и обкатав тот или иной процесс на одном из проектов, применить его в новом будет легко и просто. Наступив на те или иные грабли однажды, скорее всего их удастся избежать в будущем. Получается, что опыт
-  это именно умение решать что-то не изобретая велосипед и не наступая на известные грабли.

Опыт состоит как бы из кирпичиков. И из них можно быстро и качественно построить часть нового проекта. Попробую описать тот опыт и те кирпичики, которые накопились у меня. Сразу оговорюсь, что моя стезя - это продуктовая разработка, а продукты сосредоточены в русле Windows и последнее время используют .NET\C#. Я условно разделю свои "кирпичики" на технические и организационные и начну с общих вопросов.

THE TEAM


Самый главный строительный блок разработки - это команда. Вложения в подбор, наим, продвижение, построение хороших отношении внутри команды и обучение никогда не пройдут бесследно. Методологии и технологии ничто, если некому их применять.

Мне нравится работать с молодыми и активными ребятами, но в то же время брать совсем вчерашних студентов, мне кажется слишком рискованным. Лично у меня наиболее удачным срезом для поиска новых членов команды являются ребята до 30 лет с опытом работы около 3х лет. У них уже есть какой-то опыт за плечами, и в то же время они еще не устали и не обросли непоколебимыми убеждениями. Они хорошо учатся, с радостью осваивают новое и весьма быстро становятся незаменимыми членами команды. Так же учитывая гибкую природу нашей работы, где важно мнение каждого члена команды, человек должен быть коммуникабелен.

МЕСТО


Рабочее место имеет огромное значение. Идеально когда каждая команда имеет свое помещение. Полный open-space мешает сосредоточится и в то же время не дает командам собираться вместе иначе как в переговорках. Отдельные же кубики мешают образованию команды. Порою жители кубиков даже спустя годы не знают своих соседей.

Имея свое помещение, команда может не только проводить спонтанные брейнштормы не мешая окружающим, но и использовать стены для размещения схем, белых досок и scrum-board.  Когда своего помещения нет, люди вынуждены придумывать всякие хитрости. Например, я знаю один open-space офис, где каждая команда каждый день приносит свою SCRUM-доску в переговорку на время стендапа.

Ко всему прочему в офисе помимо мест для работы и переговорок должна быть кухня или столовая, в противном случае сотрудники начинают кушать на рабочих местах, и это бывает напрягает окружающих. Когда за соседним столиком кушают редьку или доширак, очень сложно оставаться толерантным. Насчет же прочих плюшек у нас есть кофе\чай\вода\сахар\молоко и одноразовая посуда за счет компании. Это безусловно приятно, но когда этого не было, то тоже не особо напрягало.

Так же я не могу себе представить современный IT офис, в котором нет нормального WiFi, который открыт не 24h в сутки и к которому у сотрудников нет возможности подключаться через VPN.

Но, как ни странно, я все еще слышу об офисах, в которых программисты работают с 9 до 18, где на ночь просят выключать компьютеры, опечатывать комнаты и сдавать съемные диски под роспись. Мне удивительно, что так еще кто-то работает. :)

Кстати о времени работы. Опросы коллег показывают, что они оценивают свободный график, возможность поработать из дома и легко перенести отпуск в сумму равную 20% зарплаты.... Лично я вряд ли уже соглашусь работать с 9 до 18 - это адски неудобно. :)

ПЛЮШКИ


Сейчас картина рынка ИТ-вакансий на стороне работников: вакансий больше, чем реальных кандидатов. Это приводит не только к росту зарплат, но и к тому, что компании пытаются привлекать сотрудников не только деньгами, но и прочими "плюшками". Для себя я понял, что из плюшек важны относительно свободный график, парковка (увы, это большая редкость в больших городах), страховки для себя и семьи и увеличенный отпуск (например 36 дней против стандартных 28). Ну и самая главная плюшка - это дружный коллектив и адекватное руководство.

Что касается размера зарплаты, то тут у каждого своя цель. Если же хочется сравнить себя с другими, то интернет полнится обзорами зарплат в ИТ. Могу сказать, что в отношении программистов и тестеров эти обзоры дают вполне адекватные результаты. Для менеджеров, а тем более синьор-людей ситуация сложнее. Это обусловлено, как малым количеством ответов на опросы от таких людей, так и спецификой этих должностей в каждой компании.

ЖЕЛЕЗО


На мой взгляд, чрезвычайно важно иметь качественные машины для разработчиков. Если процесс сборки, запуска тестов и отладки работает медленно, то не надо питать иллюзии - на них очень быстро забьют. В результате разработчики будут чекинить код, который даже не собирается.
Так же очень важно позволить разработчикам иметь сносные виртуальные машины, легко их поднимать, откатывать и настраивать. В противном случае ответ: "А на моей машине все работает!", будет вполне легитимным.
  Для примера: сейчас у нас на проекте есть более дюжины серверов в стойках, на которых стоит Hyper-V, и любой разработчик может использовать как имеющиеся там виртуалки, так и с легкостью поднимать новые для любых нужд. Я считаю эту ситуацию просто идеальной.
Умение конфигурировать систему, ставить свой продукт и понимать его требования к системе очень помогают в разработке и отладке.
 Кстати о Hyper-V. Я пришел к нему пройдя VMWare Server и VMWare ESXi и пока не жалею. Лично для меня основным драйвером к переходу с ESXi стал тот факт, что бесплатная версия ESXi не поддерживает автоматизацию запуска\остановки\отката машин и не имеет единой морды для всех серверов разом. Hyper-V же все это прекрасно поддерживает, а учитывая, что в рамках MSDN подписки есть тот же SCVMM, то пользоваться им удобно. Так же мне как давнему пользователю Windows гораздо легче управлять такими серверами через привычные интерфейсы. В плане же быстродействия у меня сложилось ощущение, что Windows машины под управлением Hyper-V работают чуть быстрее и меньше подвержены деградации производительности со временем.

Виртуалки еще очень удобны возможностью в любой момент и очень быстро откатиться к некому эталонному образу системы и тем самым быть уверенным в чистоте системы от разных побочных эффектов вроде прошлых версий, других продуктов, апдейтов и вирусов, которые могут оказывать неожиданное влияние.

Увы, я часто слышу, что разработчики вынуждены поднимать виртуалки прямо у себя на машинах. Особенно это грустно видеть при работе с тяжелыми системами типа SharePoint, Exchange, AD etc, где для полноценной работы надо иметь далеко не одну виртуальную машину.

Сейчас проблема с ресурсами для виртуальных машин может быть эффективно решена через облачные технологии. Например, через тот же Amazon EC2, где за небольшие деньги можно получить вполне пристойные виртуалки, вместо того, чтобы инвестировать средства в свое собственное железо. Я знаю целые команды, которые не только ведут всю разработку на EC2, но и имеют кучу скриптов, которые поднимают там машины, гонят на них тесты, а потом их отключают для минимизации затрат. Причем траты на все это достаточно скромны.

Спасибо за внимание.

четверг, 10 октября 2013 г.

Linq2SQL How to improve paging perfomance

У меня есть код, который с помощью библиотеки Linq2Rest и еще ряда условии осуществляет доступ к достаточно большой таблице данных. При этом доступ должен осуществляться с возможностью пэиджинга в стиле Take() и Skip(). 

К сожалению Linq2Sql при генерации запроса использует для организации Take\Skip механизм ROW_NUMBER что приводит к вот такому вот запросу:

SELECT * FROM ( SELECT *   FROM (
        SELECT ROW_NUMBER () OVER ( ORDER BY [t0] . [ID]) AS [ROW_NUMBER] , [t0] .*
        FROM [TABLE] AS [t0]
        ) AS [t1]
    WHERE [t1] .[ROW_NUMBER] BETWEEN @p0 + AND @p0 + @p1
    ) AS [t2]
ORDER BY [t2]. [ROW_NUMBER]

Использование ROW_NUMBER приводит к существенному ухудшению производительности в зависимости от количества данных и номера страницы. В моем случае все было еще отягощено тем, что выборка осуществлялась по достаточно сложному SQL VIEW. 

Как я уже упоминал, условия фильтрации и выборки задавались в виде параметров URL в формате ODATA и разбирались c помощью Linq2Rest.

В общем виде код выглядит так:

         IEnumerable  GetData(NameValueCollection filterQuery, Expression <Func bool >> whereExp)
            where  T: class
        {
            using ( var db = new MyDB())
            {
                var table = db.GetTable();
                var filtered = table.Where(whereExp);

                var par = new ParameterParser ();
                var modelFilter = par.Parse(filterQuery);
                return modelFilter.Filter(filtered).Cast().ToList();
            }
        }

И поддерживает как фильтрацию так и сортировку через стандартные механизмы ODATA.

После ряда экспериментов с пэиджингом был найден вариант, который работал за весьма адекватное время и не зависел от номера страницы.

Вот такой SQL запрос:

DECLARE @temp table( [RN] int identity ( 1 ,), [ID] int)
INSERT INTO @temp ( [ID])
    SELECT ID FROM [TABLE] AS [t0]
        ORDER BY   [t0]. [TIME] DESC
SELECT [t0] .* FROM [TABLE] AS [t0] WHERE ID in ( SELECT ID from @temp WHERE RN BETWEEN 68001 and 680100 )
ORDER BY [t0]. [TIME] DESC
  
Время запроса сократилось с 48 секунд до 3, что стало для меня победой.

Но оказалось что подсунуть этот запрос вместо того, что генерит Linq2Sql не просто.

Для начала я отключил генерацию Take и Skip в Linq2Rest, и тем самым получил от Linq2Sql полностью рабочий запрос со всеми условиями и сортировками, но без пэиджинга.

После этого по готовому выражению можно получить подготовленный Linq2SQL запрос и его параметры с помощью GetCommand. Изменить его и передать на выполнение ExecuteQuery.

Хитрость в том. что выражение полученное GetCommand не будет работать если его просто передать ExecuteQuery в случае если поля вашего объекта типа T имеют названия отличные от колонок базы. ExecuteQuery ожидает, что поля таблицы будут соответствовать тем именам, которые заданы атрибутом Column, а GetComand создает запрос вида

SELECT  [t0] .[MESSAGE_ID] AS [MessageId] FROM ...

В результате поля результирующего объекта просто, без всяких ошибок, будут иметь пустые значения.

При процессинге преобразовал запрос вот так:

         private string ModifySqlQuery( string sql, int skip, int top, string tablename)
        {
            var newSqlQuery = new StringBuilder ();

            newSqlQuery.AppendLine( "DECLARE @temp table([RN] int identity (1,1 ), [ID] int)");
            newSqlQuery.AppendLine( "INSERT INTO @temp ([ID])" );
            newSqlQuery.AppendFormat( "    SELECT ID FROM [ {0} ] AS [t0] ", tablename);

            var whereTerminator = string.Format( "FROM [ {0}] AS [t0]" , tablename);
            var wherePos = sql.IndexOf(whereTerminator, System.StringComparison .InvariantCultureIgnoreCase) + whereTerminator.Length;
            var orderPos = sql.IndexOf( "ORDER BY" , wherePos, System.StringComparison .InvariantCultureIgnoreCase);

            newSqlQuery.AppendLine(sql.Substring(wherePos));
            newSqlQuery.AppendFormat( "SELECT [t0].* FROM [ {0}] AS [t0] " +
                                     "WHERE ID in (SELECT ID from @temp WHERE RN BETWEEN {1} and {2} )", tablename, skip, skip + top);
            newSqlQuery.AppendLine();

            if (orderPos != -1)
                newSqlQuery.AppendLine(sql.Substring(orderPos));

            return newSqlQuery.ToString();
        }

Итого результирующий код получился таким:

         IEnumerable  GetData(NameValueCollection filterQuery, Expression <Func bool >> whereExp)
            where  T: class
        {
            using ( var db = new MyDB())
            {
                var table = db.GetTable();

                // remove original skip and top from expression
                int skip;
                if (! int.TryParse(filterQuery.Get( "$skip" ), out skip)) skip = 0;
                int top;
                if (! int.TryParse(filterQuery.Get( "$top" ), out top)) top = 100;
                filterQuery.Remove( "$skip" );
                filterQuery.Remove( "$top" );

                //Filter and sort all data
                var par = new ParameterParser ();
                var modelFilter = par.Parse(filterQuery);
                var filtred = modelFilter.Filter(table.Where(whereExp)).Cast();

                //Get SQL query
                var cmd = db.GetCommand(filtred);               
                var sql = cmd.CommandText;
                var sql_parms = ( from DbParameter param in cmd.Parameters selectparam.Value).ToArray();

                //Convert query
                var newSqlQuery = ModifySqlQuery(sql, skip, top, "TABLE" );

                var newfiltred = db.ExecuteQuery(newSqlQuery, sql_parms);
                return newfiltred.ToList();
            }
        }

Enjoy. 

воскресенье, 21 октября 2012 г.

Run Powershell deploy scripts in parallel

Я являюсь большим поклонником Continues Integration и считаю, что каждый современный разработчик должен иметь возможность в один клик (или автоматически) собрать свой продукт, поставить его на тестовые машины и прогнать по всему этому все имеющиеся тесты. Причем чем быстрее этот процесс проходит и чем надежнее, тем лучше.
Так получается, что части моего продукта ставятся в процессе билда уже на 5 виртуальных машин (а один из прошлых проектов делал тоже самое почти на 40 виртуалок). Сам процесс состоит из отправки HyperV команды на откат машины в snapshot, запуска ее, ожидания загрузки и инсталляции продукта.

В большинстве случаев, с которыми я сталкивался, инсталляция продукта на одну машину совершенно независима от инсталляции на другие. Почему бы это не делать параллельно? Мне замена последовательной установки на параллельную позволила сократить фазу отката с 24 минут до 8 минут.

В итоге у меня получился вот такой вот скрипт, который в свою очередь запускает для каждой машины скрипт, уже непосредственно занимающийся деплоем.

$ps = "powershell.exe" 
$paramstr = "lab1;lab2;lab3;lab4;lab5;lab6;lab7;lab8;lab9;lab0" 
$psparam = "-File .\build\InstallToOneLab.ps1 " 
[System.Diagnostics.Process[]] $procs = $() 
foreach ($labname  in $paramstr.split(";")) 
{ 
    $currentpath = (gl).Path
    $args = $($psparam, $labname, $currentpath)     
    Write-Host "Install new to lab='$labname' with params '$args'" 
    $p = Start-Process $ps -ArgumentList $args  -PassThru -NoNewWindow -RedirectStandardOutput ".\$labname.out" 
    $procs += $p 
} 
$procs | Wait-Process 
Write-Host "Finished all"

Я пытался реализовывать этот скрипт через script-блоки и Job'ы, но увы эти механизмы в моей среде отказывались работать.

суббота, 17 марта 2012 г.

Мотивация писать тесты


Достаточно часто я слышу вопросы от коллег на конференциях, сводящиеся к тому, как мотивировать разработчиков писать тесты к своему коду и, вообще, повысить их ответственность за результат.

Прежде всего, хочу сказать, что профессиональной разработкой программного обеспечения (читай "за деньги") я занимаюсь вот уже 11 лет, и так уж получилось, что все эти годы я работал без тестеров и привык отвечать за то, что я делаю сам. Посему, мне несколько дико слушать истории, в которых "разработчики" позволяют себе отдавать код, который они даже не проверили сами, по принципу "компилируется - значит работает".

Получается, что перед тем, как начинать говорить о том, как мотивировать разработчиков писать тесты, стоит убедить программистов относится к результатам своей работы более внимательно. Тесты станут уже следствием осознания своей ответственности за результат.

Многие руководители не понимают, зачем им что-то менять, если система и так работает. Для компании Agile методики несут в перспективе прежде всего сокращение затрат на разработку за счет сокращения части подразделений QA, занимающихся неблагодарным трудом ручного тестирования. Сейчас стоимость квалифицированного тестера на рынке не многим ниже стоимости разработчика. Даже если скорость разработчиков и упадет в полтора раза, затраты все равно сократятся. В реальных условиях средняя скорость, скорее всего, вырастет за счет исключения длительного периода ручного регрессионного тестирования в конце каждой итерации.
Также за счет укорачивания фазы QA и уменьшения времени на стабилизацию продукта удастся уменьшить длинну итерации, что позволит бизнесу быть более гибким, легче реагировать на изменения и планировать релизы. Совершенствуя эту модель, можно будет достичь нирваны в виде непрерывной поставки (Continuous Delivery).

Чтобы там не говорили, но умение писать легко тестируемый код, и умение создавать эти самые тесты не берется из воздуха, а ему придется учиться.  А следовательно, как минимум по-началу разработчикам придется выйти из своей зоны комфорта и поучиться чему-то новому. Увы, тяга к познанию и обучению зачастую падает с возрастом. К тому же, если у человека 10-летний опыт работы "по старинке", ему очень не просто объяснить, зачем ему все это надо.  Часто это становится причиной некого противостояния в коллективе: на одной стороне которого оказываются матерые разработчики и тестеры, а на другой те, кто пытается что-то изменить. Хуже когда в этом противостоянии на стороне "ретроградов" находится и начальство. Я самолично общался с директором одной аутсорсинговой компании, который придерживался мнения, что все эти новомодные штуки явление проходящее. 

При наличии же поддержки руководства, понимания им преимуществ, которые эта система дает, и его гибкости постепенно в коллективе образуются группы людей, которые хотят работать по-новому. Не стоит ожидать мгновенного результата. Вначале неизбежны шероховатости: провалы в скорости, чрезмерное увлечение метриками, попытки автоматизировать всё и вся и попытки написать очередной тестовый фреимворк. Со временем команды обретут опыт и чутье, и будут с радостью нести это знание другим. Не зря в сети активно циркулирует термин test infected, инфицированные этим вирусом люди заразят других. 

Внедрять автоматизацию тестирования лучше начинать с небольших новых проектов, которые ведут маленькие молодые команды. Это уменьшит потенциальные риски, и поможет легче оценить результат. Для начала внедрения автоматизации лучше те проекты, что имеют веб-интерфеис, и используют среды, для которых уже существует большое количество библиотек и средств для тестирования (например, .NET, Java, Python). Писать тесты для приложении на С\C++ может оказаться не самой легкой, хотя и решаемой задачей.

Наличие автоматических тестов вовсе не избавляет команду от необходимости эксплоритарного тестирования продукта. На мой взгляд для этого нет нужды в выделенных тестерах. Должна просто сложиться практика внутри команды, когда сами разработчики, их менеджеры и заинтересованные лица смотрят продукт, его новые возможности и волей-неволей проверяют продукт, сталкиваются с ним, то есть начинают "Есть корм своих собак" (с) Джоел Спольски. 

Очень важно создать у команды разработчиков ощущение ответственности за продукт. Например, неплохо работает механизм, когда тестеров у команды нет вообще, и разработчики вовлечены в процесс поддержки продукта. После того, как разработчик в ночи на ломаном английском языке пытается решить проблему клиента, он начинает нутром чувствовать, как надо писать логи, какие настройки нужны продукту и какие тесты стоит написать, чтобы впредь эта ситуация не повторялась.

Порой многие считают, что их приложение самое критичное в мире. Возможно, есть и такие. Например, если вы пишите (или лучше сказать писали) софт для небезызвестного спутника "Фобос Грунт", для робота-хирурга или марсохода. Но большинство компании, на мой взгляд, несколько пероценивают критичность своих продуктов. Например, софт, работающий на вебе, очень толерантен к ошибкам. Гиганты веб-индустрии работают практически без тестеров, проверяя продукты на небольшом проценте пользователей и имея возможность легко откатить изменения. Многие продукты имеют встроенные средства обновления, которые тоже снижают критичность ошибок, т.к. обновление может быть доставлено пользователям даже без их ведома. В то же время пользователи вполне готовы тестировать софт на себе, часть из них даже очень радуется, если удалось найти ошибку.

Поэтому не стоит бояться трудностей, а стоит попробовать начать их решать.