Классика жизни
Классика жизни

Вы, наверное, слышали, что тесты - это хорошо, что тесты надо писать. И, возможно, даже согласны с этим.

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

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

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

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

В первой части мы разберём самую базу по тестированию: зачем именно нужны автотесты, сколько тестов достаточно, кто какие тесты должен писать ну и прочие мелочи. Эта часть будет наиболее полезной в первую очередь лидерам, техническим директорам и другим руководителям, которые занимаются организацией процессов.

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

Зачем нужны автотесты?

Очевидно, чтобы не делать их руками. Потому что: 

  • за ручное тестирование нужно платить людям, 

  • они делают все долго,

  • они, к тому же, ошибаются. 

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

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

Представьте, что у вас есть волшебная кнопка "проверить", нажав на которую, вы мгновенно узнаете, сломали ли вы что-нибудь. Как часто вы ее будете нажимать? Постоянно, каждую секунду, после малейшего изменения кода. Сколько вы готовы будете за неё заплатить? Вероятно, очень много.

Именно в "волшебной кнопке" и кроется вся магия тестов. Если есть волшебная кнопка, то вам больше не требуется тратить кучу времени на поиск и исправления ошибок. Чего искать-то, если вы сделали её только что?

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

Пример из HP

Вот вам очень показательный пример трансформации компании HP Laserjet в 2008-2011 годах. В результате переформатирования своих процессов, HP, в том числе, перешли с полностью ручного тестирования на практически полностью автоматическое. Трансформация позволила им сократить затраты на всю побочную деятельность, высвободив до 40% времени на разработку нового функционала, то есть инновации. В 2008 у компании оставалось на это всего 5%. То есть произошло ускорение в 8 раз!

Да, причина не только в автотестах. Да это только один частный случай. Но какой яркий пример.

Обратите внимание на строчку в самом низу. Цинк
Обратите внимание на строчку в самом низу. Цинк

Рассмотрим иллюстрацию выше. До трансформации компания тратила 15% своего времени на ручное тестирование. После - 28%. Из них 5% на ручное и 23% - на автоматическое. То есть общее количество времени, затрачиваемое на тестирование удвоилось, но при это скорость разработки увеличилась в 8 раз.

Тут интересна последняя строчка: сокращения цикла тестирования с 6 недель до 24 часов. Цинк
Тут интересна последняя строчка: сокращения цикла тестирования с 6 недель до 24 часов. Цинк

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

Показательный пример из Google

In Google’s early days, engineer-driven testing was often assumed to be of little importance. Teams regularly relied on smart people to get the software right. A few systems ran large integration tests, but mostly it was the Wild West. One product in particular seemed to suffer the worst, it was called the Google Web Server, also known as “GWS.”

GWS is the web server responsible for serving Google Search queries and is as important to Google Search as Air Traffic Control is to an airport. Back in 2005, as the project swelled in size and complexity, productivity had slowed dramatically. Releases were becoming buggier, and it was taking longer and longer to push them out. Team members had little confidence when making changes to the service, and often found out something was wrong only when features stopped working in production. (At one point more than 80% of production pushes contained user-affecting bugs that had to be rolled back).

To address these problems the Tech Lead (TL) of GWS decided to institute a policy of engineer-driven, automated testing. As part of this policy, all new code changes were required to include tests and those tests would be run continuously. Within a year of instituting this policy, the number of emergency pushes dropped by half. This drop occurred despite the fact that the project was seeing a record number of new changes every quarter. Even in the face of unprecedented growth and change, testing brought renewed productivity and confidence to one of the most critical projects at Google. Today GWS has tens of thousands of tests, and releases almost every day with relatively few customer-visible failures.

Software Engineering at Google

Сколько тестов надо?

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

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

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

Первый закон автотестов

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

Что такое полный регресс? Это набор тестов, после которого вы готовы отдать артефакт в пром. То есть вы в нём уверены. 

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

Неужели это возможно?

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

The Beyoncé Rule

We are often asked, when coaching new hires, which behaviors or properties actually need to be tested? The straightforward answer is: test everything that you don’t want to break. In other words, if you want to be confident that a system exhibits a particular behavior, the only way to be sure it will is to write an automated test for it. This includes all of the usual suspects like testing performance, behavioral correctness, accessibility, and security. It also includes less obvious properties like testing how a system handles failure. We have a name for this general philosophy: we call it the Beyoncé Rule. Succinctly, it can be stated as follows: “If you liked it, then you shoulda put a test on it”

Software Engineering at Google

Гонка тестов и функционала

Неужели, чтобы покрытие не отстало от функционала, надо нанимать автотестеров и разрабов в равных пропорциях? Нет, конечно!

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

It is vital that developers own the acceptance tests. It may be that in the very early stages of their initial creation someone in a different role may sketch the test, but developers are the people who will break the tests and so they are the people who are best placed to fix them and maintain them. This is, for me, an essential part of the Continuous Delivery feedback loop. I have never seen a successful automated testing effort based on a separate QA team writing and maintaining tests.

Dave Farley, Continuous Delivery

DORA’s research shows that when developers are primarily responsible for creating and maintaining suites of automated tests, and when it is easy for developers to fix acceptance test failures, this drives improved performance.

DORA

Even in companies where QA is a prominent organization, developer-written tests are commonplace. At the speed and scale that today’s systems are being developed, the only way to keep up is by sharing the development of tests around the entire engineering staff.

Software Engineering at Google

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

Виды тестов

Принято считать, что разработчики пишут только юнит-тесты. Неужели это тоже заблуждение?

Задам встречный вопрос: вы готовы отдать код в пром только по юнит-тестам? Скорее всего, вы бы хотели хотя бы проверить, что приложение в проде поднимется. В идеале бы ещё погонять тесты на поднятом приложении. Если есть ui, то желательно потыкать кнопки. Если в проде большая нагрузка, то и на тестах хотелось бы увидеть, нет ли деградации производительности.

Why Unit Tests Aren’t Enough

One day we deployed the latest build that had passed our unit tests into a test environment. This was a lengthy but controlled approach to deployment that our environment specialists carried out. However, the system didn’t seem to work.

We spent a lot of time trying to find what was wrong with the configuration of the environment, but we couldn’t find the problem. Then one of our senior developers tried the application on his development machine. It didn’t work there either. He stepped back through earlier and earlier versions, until he found that the system had actually stopped working three weeks earlier. A tiny, obscure bug had prevented the system from starting correctly.

This project had good unit test coverage, with the average for all modules around 90%. Despite this, 80 developers, who usually only ran the tests rather than the application itself, did not see the problem for three weeks.

Continuous Delivery

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

Где взять сознательных разработчиков?

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

Тесты - это командная практика. Если хотя бы один не пишет или пишет как-то не так, то вы больше не можете быть уверены в своих тестах. Никакого вам релиза по автотестам и безопасного рефакторинга. То есть даже один несогласный может угробить старания всех остальных.

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

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

Как это сделали в Google

Most of Google’s early orientation program concerned things like medical benefits and how Google Search worked, but starting in 2005 it also began including an hour-long discussion of the value of automated testing. The class covered the various benefits of testing such as increased productivity, better documentation, and support for refactoring. It also covered how to write a good test. For many Nooglers (new Googlers) at the time, such a class was their first exposure to this material. Most important, all of these ideas were presented as though they were standard practice at the company. The new hires had no idea that they were being used as trojan horses to sneak this idea into their unsuspecting teams.

As Nooglers joined their teams following orientation, they began writing tests and questioning those on the team who didn’t. Within only a year or two, the population of engineers who had been taught testing outnumbered the pretesting culture engineers. As a result, many new projects started off on the right foot.

Software Engineering at Google

Continuous delivery

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

В отчёте State of Devops от 2018 года такие команды называются элитой. Среди всех респондентов опроса их оказалось всего 7%. В таких командах от коммита от деплоя в пром проходит меньше часа, у них самая высокая стабильность, они быстрее всех восстанавливаются в случае каких-то проблем. Они тратят меньше всех времени на лишнюю работу, поэтому у них остаётся больше всех времени на новые фичи. В результате влияние таких команд на финансовые и нефинансовые успехи компании наибольшие.

This measure of organizational performance has also been found to be highly correlated to measures of return on investment (ROI), and it is robust to economic cycles—a great measure for our purposes. Analysis over several years shows that high-performing organizations were consistently twice as likely to exceed these goals as low performers

<...>

We found that high performers were also twice as likely to exceed objectives in quantity of goods and services, operating efficiency, customer satisfaction, quality of products or services, and achieving organization or mission goals.

Accelerate

Безусловно, не весь софт возможно поставить в эксплуатацию сразу даже после полноценного релиза артефакта. По разным причинам. Поэтому мы гораздо чаще используем зонтичный термин: Continuous Delivery, который включает в себя Continuous Deployment. Определение Continuous Delivery: процесс, при котором ваш софт всегда готов к развёртыванию. То есть вместо поставки каждого коммита в эксплуатацию мы требуем лишь готовность к поставке. Такую готовность, чтобы больше ничего с коммитом делать не требовалось: ни собирать, ни тестировать, ни что-то еще - бери и ставь.

То есть, вопреки укоренившемуся взгляду на CD, эта практика, по умолчанию, предполагает наличие автоматического тестирования. А если вы ещё глянете вот эту статью по Continuous Integration, то поймете, что в 99% случаях, когда кто-то говорит про CI/CD, то это не имеет никакого отношения ни к CI, ни к CD.

Архитектура и e2e

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

И этот вопрос надо задать вашей архитектуре: позволяет ли она командам тестироваться и релизиться независимо друг от друга? Если нет, то никакого continuous delivery можете не ждать, как и элитных процессов.

Loosely coupled architectures and teams are the strongest predictor of continuous delivery.

State Of Devops 2017

Что такое Loosely coupled architecture

The biggest contributor to continuous delivery — bigger even than test and deployment automation — is whether a team can do all of the following:

  • Make large-scale changes to the design of its system without permission from someone outside the team.

  • Make large-scale changes to the design of its system without depending on other teams to make changes in their own systems, or creating significant work for other teams.

  • Complete its work without needing fine-grained communication and coordination with people outside the team. For example, not having to check 12 Google calendars to get feedback.

  • Deploy and release its product or service on demand, independently of other services the product or service depends upon.

  • Do most of its testing on demand, without requiring an integrated test environment.

  • Perform deployments during normal business hours with negligible downtime.

Основные 2 фактора, необходимые для Continuous Delivery - это архитектура и автоматическое тестирование. Так что если хотите глубже погрузиться в вопросы архитектуры систем, то добро пожаловать в мой материал по этой теме.

Источник: THE ROLE OF CONTINUOUS DELIVERY IN IT AND ORGANIZATIONAL PERFORMANCE

Архитектурные практики разделения релизных процессов команд

Так а как тогда решать вопросы независимого изменения контрактов и совместного тестирования команд?

Здесь вас поджидает ещё одна ловушка: end-to-end (e2e) тесты, то есть, тесты от края до края, которые проходят через функционал нескольких команд. Такие тесты не позволят командам проводить независимое тестирование и, как следствие, релизить фичи и фиксы в любой нужный командам момент. Сквозные тесты делают релизные процессы зависимыми от готовности и работоспособности функционала соседних команд. 

Основные практики для отвязывания тестирования и релизных процессов команд друг от друга:

  • Обратная совместимость и версионирование контрактов. Вы должны одновременно поддерживать и новые, и старые версии апи, до тех пор, пока все потребители старых не перейдут на новые версии. Это позволит вам менять контракты и вводить новый функционал без поломки старого.

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

  • Заглушки. Отвяжите ваши автотесты от модулей других команд через заглушки. Контрактные тесты проверяют, что соседи делают свою часть правильно, ваши тесты проверяют, что вы делаете свою.

    Про заглушки

    Automated acceptance testing is not the same as user acceptance testing. One of the differences is that automated acceptance tests should not run in an environment that includes integration to all external systems. Instead, your acceptance testing should be focused on providing a controllable environment in which the system under test can be run. “Controllable” in this context means that you are able to create the correct initial state for our tests. Integrating with real external systems removes our ability to do this.

    <...>

    We usually create test doubles that represent the connection to all external systems that our system interacts with <...>

    Continuous Delivery

  • Фича-флаги. Если ваш функционал готов, а коллеги со своим ещё не закончили, то это не повод откладывать релиз. Выключите свой функционал фича-флагом и спокойно идите в пром.

  • Тестирование в проде. Все готовы включать новый функционал. Но вы его с соседями вместе не тестировали. Как его теперь проверить не на заглушках, а в связке с другими командами? 

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

    We define an integrated environment as one in which multiple independent services are deployed together, such as a staging environment. In many enterprises, integrated environments are expensive and require significant set-up time.

    Accelerate

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

Deployment pipeline

Чтобы ваш коммит заехал в прод, он должен пройти самое тщательное тестирование. Как я уже писал, это и проверка, что артефакт вообще может подняться, и прогон тестов на развёрнутом приложении, и нагрузочные, и UI-тесты. Каждый коммит проходит все 7 кругов ада буквально по конвейеру. Собственно, конвейер развёртывания, он же Deployment Pipeline - это и есть основной шаблон практики Continuous Delivery.

The pattern that is central to this book is the deployment pipeline. A deployment pipeline is, in essence, an automated implementation of your application’s build, deploy, test, and release process.

Continuous Delivery

Иллюстрация конвейера развертывания из оригинальной книги Continuous Delivery. Обратите внимание, какое слово встречается чаще всего. Запомните, пайплайн - это, прежде всего, про тестирование!
Иллюстрация конвейера развертывания из оригинальной книги Continuous Delivery. Обратите внимание, какое слово встречается чаще всего. Запомните, пайплайн - это, прежде всего, про тестирование!

Классический Deployment Pipeline делится на 2 основные фазы: CI-фаза и Acceptance-фаза. Если по-простому: быстрые тесты и медленные тесты.

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

Пример жахнувшегося Commit-стейджа. Это значит, что коммитить в main нельзя, пуллить тоже не стоит. Необходимо как можно скорее откатить последний коммит или починить его. Тут ветка сломана 25 минут подряд, косяк! Но именно благодаря этому косяку, у меня и остался этот скрин :)
Пример жахнувшегося Commit-стейджа. Это значит, что коммитить в main нельзя, пуллить тоже не стоит. Необходимо как можно скорее откатить последний коммит или починить его. Тут ветка сломана 25 минут подряд, косяк! Но именно благодаря этому косяку, у меня и остался этот скрин :)

Задача медленных тестов - принять решение о возможности поставки в прод. Медленные тесты - это инструмент поставки в прод.

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

Отступление про CI

Чтобы не было разночтений, обязан сразу сказать: оригинальный Continuous Delivery был сформулирован в начале нулевых людьми, которые практиковали экстремальное программирование.

Авторы Continuous Delivery об Экстремальном программировании

Suggested Practices

The following practices aren’t required, but we have found them useful, and you should at least consider using them for your project.

Extreme Programming (XP) Development Practices

Continuous integration is one of the twelve core XP practices described in Kent Beck’s book, and as such it complements and is complemented by the other XP practices. Continuous integration can make a huge difference to any team even if they are not using any of the other practices, but it is even more effective in conjunction with the other practices.

Continuous Delivery

Из-за этого оригинальный Continuous Delivery не подразумевает ни пулл-реквесты, ни фича-ветки. Во-первых, потому что на момент написания книги пулл-реквесты только зарождались, а книга была написана по следам проектов середины нулевых. Во-вторых, это противоречило одной из главных практик экстремального программирования - непрерывной интеграции. Под катом будут несколько цитат-пруфов из оригинальной книги.

Branch by Feature

This pattern is designed to make it easier for large teams to work simultaneously on features while keeping mainline in a releasable state. Every story or feature is developed on a separate branch. Only after a story is accepted by testers, it is merged to mainline so as to ensure that mainline is always releasable.
<...>
Having many long-lived branches is bad because of the combinatorial problem of merging. If you have four branches, each of them will only be merging from mainline, not with each other. All four branches are diverging. It only takes two branches performing a refactoring in a tightly coupled codebase to bring the entire team to a halt when one of them merges. It bears repeating that branching is fundamentally antithetical to continuous integration.
<...>
We are cautious about recommending this pattern because it is so closely related to one of the most common antipatterns of commercial software development. In this evil, but extremely common, mirror universe, developers branch to create features. This branch stays isolated for a long time. Meanwhile, other developers are creating other branches. When it comes close to release time, all the branches get merged into trunk.
<..>
It is worth emphasizing that branching by feature is really the antithesis of continuous integration, and all of our advice on how to make it work is only about ensuring that the pain isn’t too horrible come merge time. It is much simpler to avoid the pain in the first place. Of course, like all “rules” in software development, there are exceptions where this may make sense, such as open source projects or small teams of experienced developers working with distributed version control systems. However, be aware that you are “running with scissors” when you adopt this pattern.

Continuous Delivery

Version Control Horror Stories

By far the most common reason to branch is functional. However, creating branches for a release is just the beginning. One large network infrastructure provider we worked with had branches for every major customer of their product.They also had subbranches for each bugfix and new feature. Version numbers for their software went w.x.y.z where w was a major version, x was a release, y was a customer identifier, and z was a build. We were called in because it took them 12–24 months to make a major release. One of the first problems we spotted was that their tests were in a separate version control repository from their code. As a result, they had a really hard time working out which tests applied to which build. This, in turn, prevented them from adding more tests to their codebase.

Continuous Delivery

Про пулл-реквесты из 2010

DVCSs offer new and powerful ways to collaborate. GitHub, for example, pioneered a new model of collaboration for open source projects. In the traditional model, committers acted as gatekeepers to the definitive repository for a project, accepting or rejecting patches from contributors. Forks of a project only occurred in extreme circumstances when there were irreconcilable arguments between committers. In the GitHub model, this is turned on its head. Contributions are made by first forking the repository of the project you wish to contribute to, making your changes, and then asking the owners of the original repository to pull your changes. On active projects, networks of forks rapidly proliferate, each with various new sets of features. Occasionally these forks diverge. This model is far more dynamic than the traditional model in which patches languish, ignored, on mailing list archives. As a result, the pace of development tends to be faster on GitHub, with a larger cloud of contributors.

However, this model challenges a fundamental assumption of the practice of CI: That there is a single, canonical version of code (usually called mainline, or trunk) to which all changes are committed. It is important to point out that you can use the mainline model of version control, and do CI perfectly happily, using a DVCS.

<...>

To summarize: In general, distributed version control systems are a great advance and provide powerful tools for collaboration, whether or not you are
working on a distributed project. DVCSs can be extremely effective as part of a traditional continuous integration system, in which there is a designated central repository to which everybody regularly pushes their changes (at least once a day). They can also be used in other patterns that do not allow for continuous integration, but may still be effective patterns for delivering software. However, we caution against using these patterns when the right conditions, listed above, are not satisfied.

Continuous Delivery

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

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

Continuous Integration.

No code sits unintegrated for more than a couple of hours. At the end of every development episode, the code is integrated with the latest release and all the tests must run at 100%.

Extreme Programming Explained. Kent Beck. 1999

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

Обратно к пайплайнам

Чаще всего быстрые тесты происходят без запуска приложения, а приёмочные тесты гоняются  уже после деплоя на стенды против запущенного приложения.

Пример пайплайна с реального проекта. CI-сервер - GitLab CI. Все тесты, пайпы, ансиблы и терраформы написаны разработчиками. Технологии: Docker, Яндекс Облако, Cypress. Обратите внимание на общую продолжительность пайпа: 27 минут. Общее число быстрых тестов: полторы тысячи. Еще столько же медленных тестов. Скорость прогона пайпа достигается распараллеливанием тестов по разным машинам, и кешированием артефактов и образов.
Пример пайплайна с реального проекта. CI-сервер - GitLab CI. Все тесты, пайпы, ансиблы и терраформы написаны разработчиками. Технологии: Docker, Яндекс Облако, Cypress. Обратите внимание на общую продолжительность пайпа: 27 минут. ��бщее число быстрых тестов: полторы тысячи. Еще столько же медленных тестов. Скорость прогона пайпа достигается распараллеливанием тестов по разным машинам, и кешированием артефактов и образов.

Классическая идея такая: сразу после коммита в мастер CI-сервер стягивает изменения и запускает быстрые тесты без запуска приложения. Этот короткий шаг длится не более пяти минут.

Если CI-фаза завершилась успешно, то начинается acceptance-фаза: собирается артефакт "релиз-кандидат", раскатывается по стендам и подвергается автоматическому приёмочному тестированию. На этом шаге как раз проходят функциональные приёмочные тесты, ui-тесты, нагрузочные и инфраструктурные тесты. 

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

Testing every night is too slow, you need valuable feedback much more frequently than that. I think that you should be aiming for commit stage feedback in under 5 minutes (under 10 is survivable, but unpleasant) and acceptance stage feedback in under 30 minutes (60 is survivable but unpleasant).

Test Maintainability, Dave Farley's Blog

Главный приём ускорения acceptance-фазы - параллелизация тестов по нескольким тестовым машинам: ui-тесты на одной машине, нагрузочные - на другой, функциональные поделены на несколько групп и едут на разных машинах. Впрочем, это не должно отменять борьбы с очевидными неэффективностями тестов и иных шагов пайплайна.

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

Дисциплина

Когда сроки горят и нужно быстрее-быстрее, то команда может встать перед выбором: либо написать тесты на готовый функционал, либо отложить их на время и приступить к новому.

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

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

Когда ситуация ставит перед вами такую дилемму, невероятно трудно взять на себя ответственность и выбрать тесты. И именно тут мы теряем большинство единомышленников, у которых могли бы быть тесты, но, понимаете ли, сроки. 

Как обойти эту ловушку?

Мне известны 3 варианта: 

  1. Смириться с дырами в тест-сьюте. Некоторые команды согласны на то, что в их коде могут быть ошибки. Они готовы быстро их обнаруживать в проде и оперативно откатывать или чинить. Этот вариант рискованный и подходит далеко не всем, но все же это вариант. Так работают очень многие известные компании, которых мы считаем лидерами в IT. Например, Netflix, Facebook.

  2. Поддерживать железную дисциплину или бюрократию, не позволяя коду без тестов заезжать в репозиторий ни при каких условиях. Это вариант Гугла. 

    Every code change at Google is required to go through code review. And every change is expected to include both the feature code and tests. Reviewers are expected to review the quality and correctness of both. In fact, it is perfectly reasonable to block a change if it is missing tests.

    Software Engineering at Google

  3. Использовать подход, где тесты пишутся до кода. То есть, TDD. В таком случае вы гораздо реже будете оказываться перед соблазном отдать сначала код, а тесты дописать потом. Почему? Потому что по TDD, как правило, тесты-то у вас есть - вам кода не хватает. Этот вариант мы используем в нашей команде уже третий год, и он работает, как часы. Также TDD используется в Thoughtworks, Pivotal, LMAX и в многих других командах и компаниях. 

TDD

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

Для тех, кто вообще не представляет, что это такое, TDD - это когда тесты пишутся до кода.

TDD наглядно: red-green-refactor. Пишем красный тест, зеленим его и рефачим.
TDD наглядно: red-green-refactor. Пишем красный тест, зеленим его и рефачим.

В 90-е этот подход придумал обожаемый мною Кент Бек, автор моего любимого экстремального программирования (XP) и многих других крутых штук. Идея абсолютно в духе XP: сокращение всех возможных циклов до минимума. Вот и тут вы работаете с мгновенным циклом обратной связи по вашему коду: написали код и сразу по кнопке проверили, работает он или нет.

Как выглядел первый эксперимент с TDD можно увидеть вот на этом демо Дяди Боба. Этот тот, который автор Clean Code, Clean Architecture и SOLID.

Как это выглядит в нашей команде

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

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

Сначала разработчик пишет красный приёмочный тест по одному из кейсов и убеждается в том, что результат теста совпадает с его ожиданиями. Кстати увидеть красный тест и конкретную ошибку очень важно: на этом этапе мы можем поймать ситуации, когда мы неправильно понимаем работу нашей системы; неправильно понимаем то, как должна работать доработка; видим, что мы неправильно написали тест. 

После того, как у нас появился красный приёмочный тест, мы можем приступать к разработке. Почти. Перед тем, как реализовать решение мы пишем теперь быстрые тесты, которые необходимы для реализации отдельных частей приложения. То есть внутри одного большого цикла ATDD (acceptance test-driven development) у нас несколько небольших циклов TDD. 

После того, как все приёмочные кейсы реализованы, фича считается готовой к поставке.

Зачем такие сложности?

Строго говоря, низачем. Если у вас от этого описания задергался глаз, то забейте, не мучайте себя. Тесты, написанные после разработки, в огромном количестве команд вполне решают свои задачи. Но, в сравнении с тестами, написанными по TDD, у них есть определённые недостатки. Какие?

  1. Полнота тестов. 

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

  2. Простота решений.

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

    В рамках TDD вы пишете только тот код, который нужен для того, чтобы тест стал зелёным. Это значит, что на выходе вы получаете максимально простое ��ешение, никаких "универсальных" решений и заделов "на будущее". Ваш код предельно простой, а значит, читаемый, надёжный и легко изменяемый. А вот если тесты написаны после, то я обычно ожидаю увидеть в коде всякие переусложнения и ненужные космолеты.

  3. Уверенность в тестах и в решениях.

    Если вы написали тест после реализации и видите, что он зелёный, значит ли это, что ваша доработка работает? Значит ли это, что ваш тест ее проверяет? 

    Не факт! Вы могли ошибиться и написать тест, который и без вашей доработки был бы зелёный. Такое случается и кстати не очень редко. Получается, что зелёный тест не значит, что ваша доработка работает. В итоге, мы не уверены ни в тестах, ни в доработке.

    TDD заставляет вас увидеть переход теста из красного в зелёный. Это означает, что вы видите, что именно ваша доработка позеленила тест.

  4. Дисциплина

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

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

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

  5. Скука.

    Обычно, когда тесты пишутся после кода, вы уже все проверили каким-то другим способом. А значит, написание тестов - это самая скучная часть разработки: вы уже знаете, что все работает. 

    А вот в TDD это не так. Вы пишите тест, когда ничего ещё не работает. Сначала вы пытаетесь заставить тест вернуть то, что вы от него ждёте - нужную ошибку. Потом вы пытаетесь заставить код зазеленить тест самым простым путём. Потом вы критически осматриваете своё решение и наводите красоту путём рефакторинга. 

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

Итого по TDD

История безусловно опциональная и на любителя, но свои преимущества имеет. Кому-то может даже показаться, что так писать "медленнее", но последнее, на мой взгляд - это уже совсем вилами по воде: трудно понять, как перестановка одних и тех же действий местами может вас сильно замедлить.

Я с TDD уже лет 5 и по-старинке уже как-то и не получается писать. Мозг перестроился и даже непонятно, с чего начинать.

Техника безопасности

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

  1. Не для всех.

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

  2. Если это не для вас.

    Если вы чувствуете, что для вас эти условия невыполнимы, то хотя бы признайтесь себе в этом: сэкономьте ресурсы компании и не тратьте их на то, что не работает.

  3. Автотесты - не панацея.

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

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

    Есть еще куча всякого непредсказуемого, с чем необходимо считаться.

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

  4. Требовательность к качеству тестов.

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

  5. Continuous Delivery - это самое главное.

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

  6. Учиться, учиться и еще раз учиться.

    Автотесты - это очень сложный навык. Тем более, по TDD. Этому надо учиться. Я учился по книжкам, лекциям и докладам. Моя команда училась у меня и у друг друга. Просто сказать "пишите тесты" не получится - необходимо научить.


Меня зовут Саша Раковский. Работаю техлидом в расчетном центре одного из крупнейших банков РФ, где ежедневно проводятся миллионы платежей, а ошибка может стоить банку очень дорого. Законченный фанат экстремального программирования, а значит и DDDTDD, и вот этого всего. Штуки редкие, крутые, так мало кто умеет, для этого я здесь - делюсь опытом.

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

Комментарии (0)


  1. DjUmnik
    22.09.2025 04:34

    Скажите, где живут компании, где всё это практикуется? Я в таких не работал. Наверно для php это редкость.


    1. panzerfaust
      22.09.2025 04:34

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


      1. jdev
        22.09.2025 04:34

        Просто начните писать.

        Не всё так просто. Начать писать - необходимое, конечно, но недостаточное условие.


        Чтобы получить

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

        важно, чтобы вся команда писала тесты. И писала качественные тесты, а не отписки для лида, лишь бы отвалил.


        1. panzerfaust
          22.09.2025 04:34

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


    1. RakovskyAlexander Автор
      22.09.2025 04:34

      Это, в принципе, редкость, не только для php. Как я писал в статье - таких всего 7%. Есть примеры pivotal, thoughtworks, Google, Amazon и ещё кучи компаний, где к автоматическому тестированию относятся очень серьёзно.

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

      Конкретно про пхп, к сожалению, не подскажу.


  1. xentoo
    22.09.2025 04:34

    Извините. А где, собственно, гайд по автотестам? Я сам разработчик и нахожу в себе силы писать юнит тесты. Иногда практикую ТДД. А так же приходится писать и тесты которые покрывают целый процесс. Но это все надо писать.. ручками, создавать контейнеры данных, сценарии, и в конце нажимать эту самую заветную "кнопку". Как это все автоматизировать? Как сделать автотесты? Кто из генерирует? Какое они имеют покрытие и кто это может отследить?


    1. alexmasyukov
      22.09.2025 04:34

      Автоматизировать часть или полное написание тестов можно через AI, лично практикую на фронтенде с playwright (для конкретного пакета кейсов на больших компонентах). Главное точно описать сценарии тестирования, описать общую стилистику и требования к тестам (пишется один раз навсегда, и мелкими дополнениями время от времени), так же просить писать тест по описанию, но на предварительно изученных других подобных тестов на проекте (подготовка контекста диалога для минимизации ошибок при написании теста).


  1. FireHawk
    22.09.2025 04:34

    А что значит "пром" в тексте?


    1. powercfg
      22.09.2025 04:34

      Продакшн, рабочая система.


      1. FireHawk
        22.09.2025 04:34

        А почему тогда продакшн - не "прод", а "пром"?


    1. xentoo
      22.09.2025 04:34

      Имеется в виду "прод".

      "Development/DEV" система, где идет разработка продукта и исполняются юнит тесты;

      "Quality/QAS" система, где тестируется процесс и система, запускаются интеграционные тесты, юзер тесты итд;

      "Production/Productive/PROD" система, где уже все работает по настоящему, но и здесь можно провести нагрузочный тест (load test) стресс тест (stress test);


      1. FireHawk
        22.09.2025 04:34

        Наверное, автор тогда написал бы "прод", устоявший термин, но он упорно пишет "пром".


        1. jdev
          22.09.2025 04:34

          У людей из банковской тусовки часто встречаю "пром" - видимо от "Промышленная эксплуатация". В общем "устоянность" термина сильно зависит от тусовки:)


        1. xentoo
          22.09.2025 04:34

          Вероятно, если автор работает на каком-то заводе или фабрике, то так называют промышленные системы, станки