Сколько стоят юнит тесты?

в 16:52, , рубрики: npv, tdd, закон брукса, управление проектами, финансы в IT, юнит-тесты

image

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

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

Что такое стоимость

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

$0 < NPV=FCFF / WACC + FCFF / WACC ^ 2 + FCFF / WACC ^ 3 …$

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

На самом деле, это несколько упрощенная формула. Строго говоря, ставка будет меняться год от года, поэтому в знаменателе стоило бы использовать WACC1 * WACC2 * WACC3 и т.д., но на практике этим пренебрегают даже профессиональные оценщики, т.к. в силу методики расчета WACC, в сегодняшнюю ставку уже заложены ожидания рынка относительно будущих ставок и строить свои предположения на этот счет непродуктивно.

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

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

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

$WACC=Re * P / EV + Rd * (1 – P / EV)$

Здесь Re – стоимость собственного капитала, Rd – стоимость заемного капитала (то есть эффективная ставка по долгам компании), P – рыночная стоимость собственного капитала, EV – общая стоимость предприятия (EV = P + D, где D – это долг).

Дальше нам нужно определить Re, для этого есть разные модели, но проще всего взять модель CAPM, где Re = Rb + β * Premium, где Rb – это безрисковая ставка, Premium – это премия к доходности за инвестирование в собственный капитал, а не в заемный, а β – это коэффициент риска, который показывает, насколько более рискованным является наш проект относительно бизнеса некой усредненной компании.

Как обеспечивается качество и что такое юнит-тесты

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

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

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

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

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

Что ж, у нас есть тестировщики, которые тестируют весь проект на соответствие требованиям, есть тесты для того, чтобы автоматизировать их работу и есть тесты, которые тестируют части проекта, написанные разными разработчиками, что можно сделать еще? Юнит-тесты как раз и претендуют на то, чтобы быть четвертым уровнем контроля качества. Они проверяют код, написанный одним программистом, причем, как правило, тестируется минимальная часть кода, в принципе пригодная для тестирования, например, отдельный класс. На практике чаще всего написанием юнит-тестов для собственного кода занимается сам разработчик, а их количество и необходимость контролируется слабо. По моим наблюдениям типичным количеством затрат времени разработчика на юнит-тесты можно назвать около 40% времени на разработку самой фичи, хотя это соотношение может сильно варьироваться. Широко известен кейс open source проекта SQLite, где из-за избытка низкоквалифицированной бесплатной рабочей силы, обеспечиваемой большим количеством желающих поработать над известным проектом, эта рабочая сила утилизируется армейским способом, то есть написанием бесполезных юнит-тестов, чей объем в какой-то момент в 100 раз превысил объем кода самой СУБД. Не являются чем-то удивительным и обратные случаи, когда юнит-тесты не пишутся или пишутся в минимальном объеме. В конце-концов, практически все ПО, разработанное до конца нулевых, то есть до эпохи аутсорсинга и Agile, было создано без юнит-тестов.

Затраты, поправка на сложность и мифический человеко-месяц

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

Когда-то давно у меня был бесплатный SVN репозиторий на небезызвестном сервисе Assembla, предоставлявшем услуги хостинга исходников и инструменты совместной работы, то есть трекер, статистику и прочую ерунду. Позже халява закончилась, но присылать новостные рассылки и оповещения они не перестали. Так вот, в 2015 году их сотрудница опубликовала короткий пост, который назывался “How many people should discuss a task?” Сейчас он сохранился только в Web Archive. Суть поста заключалась в следующем: сотрудница собрала статистику по клиентам, построив график зависимости продолжительности задачи от количества человек, которые ее обсуждали, результат оказался следующим:

image

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

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

Если объяснять нелинейность данного графика исключительно ростом размера команды, то связанные с ним издержки можно оценить по следующей таблице зависимости доли времени, потерянного на коммуникацию, от размера рабочей группы:

image

Например, если в команде пять разработчиков, и вы полагаете, что вам понадобится нанять двоих, чтобы каждый мог тратить дополнительно 40% своего времени на юнит тесты, будьте готовы к тому, что затраты на разработку могут вырасти больше, чем на 40%. Команда вырастет и станет менее эффективной, вместо 5 * 0,625 = 3,125 условных единиц производительности, она будет обладать 7 * 0,539 = 3,77 единиц, а объем работы увеличится с 1 до 1,4 условной единицы работы, соответственно, время, необходимое на разработку увеличится на 16%.

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

Конечно, трудно утверждать, что нелинейность графика от Assembla связана только лишь с падением эффективности в результате роста команды, но она хорошо согласуется с интуитивным пониманием сложности и законом Брукса, поэтому, если вы не хотите рисковать и вам нужна именно консервативная оценка, эти данные могут стать хорошим подспорьем.

Польза юнит тестов

Кроме затрат, юнит тесты приносят и пользу. Конечно, в подавляющем большинстве случаев баг, который мог бы быть пойман юнит тестами, будет пойман на других уровнях контроля качества, но всегда есть вероятность технического сбоя и теоретически юнит тесты могут ее снизить. Лично мне такие случаи неизвестны, к счастью, все тестировщики, с которыми мне доводилось работать, были исключительно ответственными людьми, но, когда речь идет о настолько низких вероятностях, личный опыт может быть нерепрезентативен. Сбои могут иметь разные последствия, например, у компании может быть SLA, нарушение которого повлечет вполне определенные финансовые потери, скажем, компания будет вынуждена подарить клиентам в качестве компенсации один месяц бесплатного пользования ее сервисами, лишившись 1 / 12 части выручки. В этом случае ужесточение контроля качества, которое снижает вероятность нарушения SLA в течение года с 10% до 8%, снизит среднегодовые убытки примерно на 0,17% выручки. Эти деньги и будут тем положительным компонентом денежного потока, который необходимо добавить в модель.

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

Пример первый: компания Бизон

Бизон – крупный интернет-магазин, сами они называют себя онлайн ритейлером №1 в России. Компания не публична, однако в рамках недавней сделки по докапитализации ее общая капитализация была оценена в 50 миллиардов рублей, что вдвое больше годовой выручки. Докапитализация понадобилась в связи с операционными убытками, однако акционеры надеются выйти на рентабельность по операционной прибыли в 10% после того, как компании удастся завоевать более высокую долю рынка и удвоить выручку в течение года, после чего она должна будет начать зарабатывать, а рост выручки замедлится до 30% во второй год, 20% в третий год и, наконец, установится на уровне в 10% в четвертый и последующие годы. Впрочем, банки в этом не очень уверены и дают Бизону вдолг с осторожностью, общий долг компании составляет всего 10 миллиардов рублей по ставке 11%. Бизон достаточно неуклюжая и плохо управляемая на операционном уровне компания, бесконтрольный найм сотрудников уже привел к тому, что в ней работает 600 программистов, чей общий ФОТ составляет 1,5 миллиарда рублей в год и которые тратят на юнит тесты около 30% рабочего времени. У компании нет обязательств перед клиентами и технический сбой может привести только к временной остановке продаж, при этом в случае сбоя откат на старую версию сайта занимает около часа.

Каков NPV от использования юнит тестов в Бизоне?

Выручка Бизона должна составить 50, 65, 78 и 86 миллиардов в первый, второй, третий и четвертый год соответственно. Вероятность сбоя возьмем равной 33%, то есть инцидент, способный завалить их сайт надолго может произойти примерно раз в три года, что не так уж плохо. Допустим, использование юнит тестов может снизить ее до 25% просто потому что помимо ошибок разработчиков есть еще вероятность различных аппаратных сбоев, DDOS атак и прочих неприятностей. Если сайт интернет магазина недоступен в течение часа, ритейлер теряет не больше 0,023% выручки даже с учетом того, что покупатели активны в среднем только 12 часов в сутки. Иными словами, юнит тесты сокращают потери компании на 11,5 миллиона рублей в первый год, 14,8 во второй, 17,8 в третий и 19,6 миллиона в четвертый год.

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

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

Вернемся к разработчикам, допустим, что ФОТ растет на 10% в год, тогда, суммарный эффект от использования юнит тестов составляет -438, -480, -527 и -579 миллионов рублей операционного убытка в первый, второй, третий и четвертый год соответствено, после чего убыток растет на 10% ежегодно. Юнит тесты в данном случае не влияют на чистые капитальные затраты и оборотный капитал, но убыток приводит к экономии на налогах, равной 20% от объема убытка, соответственно, его нужно умножить на 0.8: -351, -384, -421 и -463 миллиона рублей.

EV компании составляет 50 + 10 = 60 миллиардов рублей, на P приходится 83% капитала, на D 17%, нам известно, что стоимость долга составляет 11% годовых, тогда для расчета WACC остается только лишь найти стоимость собственного капитала. Бизон работает в России, поэтому в качестве безрисковой ставки нужно взять эффективную доходность государственных облигаций с наибольшей дюрацией, сейчас это 7,6%. Премия за инвестирование в собственный капитал варьируется год от года, но обычно она находится в районе 4-6% годовых, мы возьмем 5%, а для определения коэффициента β обратимся к справочнику и найдем там безрычаговый коэффициент риска для компаний из отрасли онлайн ритейла (unlevered beta) равным 1,3. Но у Бизона есть хоть и небольшие, но долги, поэтому нужно сделать поправку и подсчитать рычаговую бету (levered beta):

$βl=βu * (1 + (1 - T) * D / P)=1,3 * (1 + (1 – 0.2) * 10 / 50)=1,51$

Таким образом ставка дисконтирования WACC составит

$(7,6 + 1,51 * 5) * 0,87 + 11 * 0,17=15 процентов$

Наконец, подсчитаем, сколько Бизону стоят юнит тесты, для этого дисконтируем первые годы неравномерного роста отдельно, а для последующих лет с ростом по 10% в год воспользуемся моделью Гордона.

Приведенная стомость первого года составит $-351 / 1,15=-305$ миллионов рублей, второго $-384 / 1,32=-290$ миллионов, третьего $-421 / 1,52=-277$ миллионов.

Начиная с четвертого года убытки равномерно растут на 10% в год, соответственно после третьего года номинальный убыток от юнит тестов можно подсчитать по формуле $-463 / (1.15 – 1.1)=-9260$ миллионов, которые необходимо привести к первому году: $-9260 / 1,75=-5291$ миллион рублей.
В целом ущерб от использования юнит тестов составляет $305 + 384 + 421 + 5291=6,4$ миллиарда рублей.

Пример второй: компания Гиперсталь

Василий – подающий надежды выпускник Челябинского Колледжа Инновационных Технологий. Амазонов и Гуглов в Челябинске нет, зато есть множество сталелитейных компаний, в одну из которых ему и посчастливилось устроиться. Как выяснилось позже, бюджеты здесь скромные, денег хронически не хватает, поэтому она могла позволить себе нанять программиста только с зарплатой менее 50 тысяч рублей включая все налоги и обязательные выплаты. Первым заданием Василия стал софт для управления работой доменной печи. Этот проект должен занять не больше двух месяцев и вряд ли будет в дальнейшем поддерживаться и развиваться.
В ходе визита Василия в цех ответственный за производство специалист сказал ему в общих чертах следующее: «Уважаемый коллега! Пожалуйста, обратите внимание на этот гигантский ковш с расплавленным металлом. Если что-то пойдет не так, мы будем не только крайне обескуражены, но и столкнемся с технологическими трудностями. Дело в том, что если домна встанет, металл внутри нее застынет и на ликвидацию последствий аварии придется потратить три месяца. Будет нелегко разобраться с гигантским куском металла в цеху и заменить его новой домной». Позже Василий выяснил, что аварийная остановка домны может обойтись компании в 8 миллиардов рублей.
Вопрос: стоит ли Василию обеспокоиться написанием юнит-тестов?
Поскольку у меня уже нет сил и терпения подсчитывать очевидное, сразу скажу ответ: конечно же да. У Василия нет опыта, вероятность допустить ошибку у него высока (даю процентов 50, что в одиночку без помощи коллег и без адекватного контроля качества его программа где-нибудь заглючит и процентов 10, что это приведет к аварии), его время ничего не стоит, а цена ошибки крайне велика. Поскольку в данном примере речь идет о коротком проекте, который будет написан и забыт, нет необходимости что-либо дисконтировать, достаточно сопоставить зарплату Василия за два месяца, равную 100 тысячам рублей и матожидание убытков порядка 10% * 8 миллиардов = 800 миллионов рублей.

Пример третий: XSoft

XSoft – успешная аутсорсинговая компания, которая только что заключила контракт с очередным западным заказчиком. Заказчик планирует нанять 7 программистов, его бюджет в этой части составляет 15 миллионов рублей в год, из которых XSoft заберет себе 3 миллиона. Заказчик – лопух и ничего не смыслит в разработке. С точки зрения XSoft, должны ли разработчики писать юнит тесты?

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

Послесловие

Статья получилась большой, и я надеюсь, что старался не зря. Как и любой проект в любом бизнесе, решение о применении юнит тестов на вашем проекте должно быть экономически обоснованным. Когда компании в других отраслях планируют купить станок, открыть завод или магазин, они обязательно рассчитывают NPV и / или IRR. Печально видеть, насколько беспечной отраслью в этом плане остается IT. Зато знание основ финансов и привычка вовремя открывать Excel может дать вам заметное преимущество перед конкурентами.

Автор: Karl_Marx

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js