Ранее мы уже упоминали, что, прежде чем отправить код в продакшн, его обычно отдают на растерзание тестировщикам. А они его проверяют — либо вручную, либо автоматически.
Ну и тут возникает вопрос: раз код проходит проверки, то почему тогда сайты и приложения работают не так, как мы ожидали? Вылетают, тормозят, неправильно считают и так далее.
В этом параграфе разберёмся. А заодно расскажем о работе тестировщиков чуть подробнее.
Что такое баг
Мы уже много раз употребляли это слово, оно означает «ошибка». Пора рассказать, откуда оно взялось. Если вы сейчас подумали о программистке Грейс Хоппер и мотыльке в компьютере — вы правы, но отчасти.
Итак. Баг в переводе означает «жук» (англ. bug). Точно неизвестно, почему инженеры стали называть так ошибки, но один из первых случаев употребления этого слова в таком контексте зафиксирован в 1878 году.
Тогда американский изобретатель Томас Эдисон писал президенту компании Western Union Уильяму Ортону:
Вы были отчасти правы. Я действительно нашёл «жука» в своём аппарате, но не в самом телефоне. Он принадлежал к роду Callbellum. Похоже, это насекомое обитает во всех вызывающих устройствах телефонов.
Тут изобретатель использовал игру слов в названии «жука»: call (англ.) означает «звонок», bellum (лат.) — «война» или «битва». Получается, он иронизировал над тем, что долго и упорно боролся с технической неисправностью, но пока ничего не вышло.
Как бы то ни было, в 20 веке слово «баг» уже прочно входило в сленг технических специалистов и военных. А Грейс Хоппер была не просто компьютерным энтузиастом: она состояла в военно-морском резерве США, носила звание младшего лейтенанта (после отставки получила звание контр-адмирала) и работала в проекте по вычислениям Бюро кораблестроения при Гарвардском университете.
Хоппер служила в команде программистов компьютера Mark I. Собственно, тут и начинается популярная история: однажды она обнаружила неисправность в компьютере и извлекла мотылька, который замкнул электромеханическое реле.
Она вклеила мотылька в журнал неисправностей и подписала: «Первый действительный случай обнаружения бага», — что очевидно было игрой слов с отсылкой к техническому сленгу.
Почему возникают баги
Если бы программисты могли их не делать, они бы их не делали.
Как вы уже знаете, раньше программы были проще. Зачастую их целиком писал один человек, максимум — несколько. И всё, от чего зависели программы, — это только навыки программистов, их внимательность и способность удерживать в голове всю необходимую информацию. Но компьютеры становились крупнее и сложнее, появлялось всё больше технологий.
Сегодняшние программы — это результат работы тысяч и тысяч людей, даже если конечную программу пишет только один человек. Есть операционная система, есть протоколы для работы с интернетом, есть куча библиотек — их программист применит в своей программе. И ещё много-много внешних факторов, влияющих на работу программы, вплоть до антивируса.

И вы удивитесь, но о большей части всех этих вещей программист имеет общее представление. Он знает, что «там есть какие-то нужные мне свойства», или даже ещё меньше.
И когда он подключает какую-то библиотеку к программе, чтобы работать с картинками, последнее, чего он ожидает, — это то, что при работе с картинкой библиотека начнёт проверять и синхронизировать время в интернете.
И в 99% случаев всё будет в порядке, но у кого-то каждая картинка будет открываться по 30 секунд (это таймаут интернет-запроса по умолчанию). А почему так происходит, ни пользователи, ни программист не знают. Баг? Определённо, но программист тут вроде бы и ни при чём.
Это нормально для любой сложной системы
Например, в конце 2000-х Toyota заказала у поставщика новые педали газа, которые должны были улучшить «ощущение от нажатия». Поставщик заказ выполнил: модифицировал материал и конструкцию узла трения.
Новые педали были установлены в миллионы автомобилей, включая Camry, Corolla и Lexus ES. А потом водители стали массово жаловаться на опасные случаи самопроизвольного ускорения: автомобиль продолжал набирать скорость, даже если убрать ногу с педали газа. Некоторые инциденты закончились серьёзными авариями и гибелью людей.
Оказалось, что при высокой влажности или загрязнении педаль действительно могла залипать в нажатом положении. Плюс иногда коврики сдвигались и зажимали её физически.
В результате Toyota была вынуждена отозвать 9 млн автомобилей по всему миру и потерпела убытки — материальные и репутационные.
А теперь вопрос: мог ли проектировщик педали предвидеть, что в Х автомобилях из 100 000 штук после Y км пробега с вероятностью Z педаль будет залипать?
Нет конечно. Он же не компьютер. Тем более многое зависит от того, как устанавливалась и использовалась педаль. Он не мог это предугадать.
По результатам проверок виновной признали Toyota: внутри компании обнаружили неисправность, устранили её в новых авто, но старые отзывать не стали, — пока правда не всплыла.
Но возьмём следующий пример — программу посложнее, которую пишет целый отдел программистов, распределяя задачи между собой. Им нужно написать комплекс программ для курьерской компании: и сайт, и приложение для пользователей, и приложение для курьеров, и сервис для руководства, чтобы получать аналитику, и многое другое.
Один программист пишет часть программы, которая отправляет из курьерского приложения файл на сервер. Другой программист организует хранение файлов на сервере. Первый программист решил передавать на сервер для хранения часовой пояс отправителя файла, а программист, отвечавший за серверную часть, сохранил файлы без информации о часовом поясе. И у них был такой напряженный график разработки, что обговорить этот момент они не успели.
Директор дал задание аналитикам проанализировать сроки доставки по разным направлениям. Отдел аналитики обнаружил, что быстрее всего посылки доставляются из Петропавловска-Камчатского в Калининград.
Бросились проверять, а на сервере у файлов указано время отправки без часового пояса, из-за чего аналитика поломалась. Баг? Несомненно, но кто из этих двоих в нём виноват? Или, может быть, не виноват никто?
В таких случаях обычно говорят: «Виновата система».
Похожих причин, почему могут возникать тривиальные баги, много, очень много. Неизвестные зависимости, нечёткое техническое задание, банальная нехватка времени для проверок на все случаи, внешние библиотеки, компоненты операционных систем, другой софт, который внезапно почему-то влияет на работу программы. И это мы ещё не упомянули, что программисты тоже люди и они тоже ошибаются, причём иногда в элементарных вещах.
А теперь давайте представим, что мы создаём большую и сложную игру, в которую добавим сотни и сотни разных механик. Но каждая новая может сломать уже существующую или то, как они работают с остальными.
Дальше задачка на комбинаторику:
- для 5 механик нужно 25 проверок;
- для 6 — 36;
- для 16 — 256.
И так далее. А их надо проводить при каждой успешной сборке игры внутри команды разработчиков (внутреннем билде), не говоря уже о том, что это проверки необходимы до того, как проект будет представлен игрокам.
И вот мы дошли до тысячи разных механик, а это уже миллион проверок. Поверьте, это много. Крупная RPG, по типу World of Warcraft, может содержать 50 тысяч разных механик, требующих проверок. А теперь давайте вспомним, что и тестировщики тоже люди и они тоже могут ошибаться или что-то пропускать.
Качество тестирования зачастую прямо пропорционально заданным срокам и бюджету компании-разработчика. Если компания торопится к определённому дедлайну, не успевает, но решает всё равно выпустить продукт… Ну вы поняли.
Или если на старте проекта решили экономить на всём, включая тестирование, итог обычно закономерен. Но многие компании честно стараются выпускать качественные продукты и тестируют их изо всех сил. И бывает очень обидно, когда в тестирование версии вложили десятки тысяч человеко-часов (месяц работы команды из 50 человек), но на релизе пользователи находят какие-то очень специфичные случаи, которые умудрились пропустить.
А ещё иногда происходят непредсказуемые события вроде обновления популярного антивируса или драйверов видеокарты. Всего неделю назад всё было хорошо, а вот сегодня из-за непредвиденных обстоятельств — наоборот. Исправление неполадок требует времени, а плохо работающая версия у пользователей — уже сейчас.
Парочка примеров
Можно вспомнить ошибку в старых процессорах Pentium, которые иногда некорректно выполняли деление чисел с плавающей запятой. Можно было сколько угодно проверять программу, но на конкретных процессорах она просто иногда будет работать некорректно.
Сегодня похожие проблемы чаще всего встречаются в мобильной разработке под Android. Ваше приложение может отлично работать на всех телефонах, кроме какого-нибудь редкого представителя малоизвестного бренда.
Там будет тот же процессор, та же материнская плата и та же версия Android, что и на других, однако проблема будет возникать только на нём.
Другие странные неочевидные баги в программах — бесконечные циклы и состояние гонки.
Бесконечный цикл — зависание программы, когда она раз за разом циклически перебирает одни и те же данные, а условие окончания перебора почему-то не наступает.
Например, вы хотите добавить в графический редактор сразу 10 картинок, а редактор должен для каждой картинки создать отдельный слой. Ему нужно перебрать 10 картинок — вроде бы всё просто. Но если в подсчёте файлов произошла ошибка, цикл «открыть 10 файлов» не завершается, а для пользователя программа зависнет.
Состояние гонки, или race condition, возникает при работе программ в режиме многопоточности, когда программа одновременно выполняет несколько задач:
- Задачи могут использовать один и тот же, общий для них, ресурс, это может привести к некорректному результату.
- Чтобы избежать такого результата, они могут проверять доступность общего ресурса или активность конкурирующих задач и зависнуть в процессе этой проверки, если она неправильно запрограммирована.
Как программы реагируют на ошибки
Типы поведения программы при встрече с ошибкой тоже бывают разными.
Столкнувшись с каким-то неожиданным участком кода или данными, программа может выдать предупреждение. Это намёк пользователю, что всё ещё, конечно, работает, но что-то уже не в порядке.
При работе программа может получить неверные данные, или при выполнении функции может произойти ошибка. Для таких случаев программисты заранее прописывают обработки возможных ошибок, называемых исключениями.
Обычно их используют для проверки введённых пользователем данных и, если данные были некорректны, чтобы инициировать повторный ввод данных. Но если при работе программы случается сбой, который программист не предусмотрел (и не прописал его обработку), программа или молчаливо закроется без предупреждения, или выдаст грустное окошко с невнятным кодом ошибки и предложением отправить данные разработчику для починки.
Кстати, такая отправка важна для многих компаний-разработчиков, эти данные собираются, обрабатываются, и ошибки чинятся. А благодаря отчётам о вылетах сразу становится понятно, что чинить.
Тестировщики — супергерои для пользователей
Тестировщики, они же QA (Quality Assurance), защищают пользователей от багов.
Чаще всего QA занимаются ручным тестированием, их называют мануальными тестировщиками. Они запускают программу и по какому-то скрипту или в свободном режиме пользуются ею, смотрят, работает ли всё как должно. Такие скрипты для тестировщиков называют текст-кейсами, и обычно они выглядят примерно так:
- запустить программу;
- нажать в меню пункт «Файл», проверить, что открылось меню;
- в меню выбрать пункт «Открыть файл», проверить, появился ли диалог выбора файла;
- выбрать файл, нажать «Ок»;
- проверить, что файл открылся корректно.
Выглядит скучно, но да, именно так выглядят ручные проверки софта. И те, кто занимается тестированием, проводят сотни таких проверок ежедневно.
В работе им помогают средства автоматизации — автоматическое тестирование. Об этом мы в деталях поговорили ранее, тут останавливаться не будем. Скажем только, что некоторые компании даже следят за тем, какое количество строк кода попадает под проверки автоматическими тестами. Это называется специальной метрикой, покрытием кода (тестами), или Code Coverage.

Конечно, чем выше это число, тем лучше, но не стоит забывать, что автоматические тесты — это тоже код и его пишут люди, а значит, в тестах тоже могут быть ошибки. А ещё автоматические тесты не проверят, работает ли приложение, как задумано, с точки зрения пользователя.
Но тут не обходится без споров. Например, что при разработке каких-то библиотек всё как раз проверить можно, а также есть возможность писать и проверять пользовательские сценарии.
Пользовательский сценарий может выглядеть так:
- Запустить приложение.
- Открыть файл.
- Поменять в файле текст.
- Сохранить файл.
Такой тест сразу проверяет работу приложения с файлом, процесс открытия и сохранения, он будет сравнивать исходный и итоговый файлы и обнаружит, что они разные, появился добавленный текст.
Но далеко не все типы приложений можно проверить таким образом, а создавать такие тесты обычно намного сложнее, чем тест-кейсы. Тестирование с помощью скриптов только облегчает нагрузку для ручного тестирования, а также подсказывает тестировщикам: «Сломалось вот тут. Возможно, что-то связанное с этим — тоже».
Есть ещё много разных типов тестирования программ, например подход обезьянье тестирование (Monkey Testing). В этом подходе имитируется случайное нажатие всех кнопок подряд и ввод случайных данных во все доступные поля запущенной программы или открытого сайта. Получается стресс-тест для программы, но такое тестирование полезно тем, что часто позволяет найти неочевидные места, где программа может сломаться.
Для больших и сложных систем также применяют нагрузочное тестирование. На реальный сервис, но запущенный на тестовом сервере, дают нагрузку, как будто в секунду им пользуются тысячи и сотни тысяч пользователей, — так проверяют, как он справится с реальной нагрузкой и что при этом откажет.
К тестированию программ, которые влияют на жизнь и здоровье людей, предъявляются совсем другие требования.
Например, для тестирования медицинского или авиационного программного обеспечения написаны специальные сертификации, которым должен соответствовать процесс. Согласитесь, никто не хочет, чтобы в самый неподходящий момент медицинский прибор завис или начал работать неправильно.
Одной из самых громких историй, после которых появилась такая сертификация, стал случай с аппаратом для лучевой терапии Therac-25 в 1982 году. Он не был первым в серии, и предыдущие аппараты много лет работали исправно, но в Therac-25 впервые убрали аппаратную защиту и оставили всё на специальном ПО.
Хотя это ПО и базировалось на предыдущих версиях, в нём было несколько неочевидных ошибок. Например, если быстро вводить настройки, итоговое значение мощности могло получиться неправильным. Ошибка также могла произойти, если задать настройки положения устройства, применить их и после ввести другие настройки.
Ошибки обрабатывались программой. Медики о них узнавали, но в инструкциях отсутствовало как описание ошибок, так и предупреждение о том, что эти ошибки могут навредить больным. К сожалению, эти проблемы стали причиной гибели нескольких пациентов. С тех пор (а точнее, после судебных разбирательств с компанией-разработчиком) к медицинскому ПО применяются куда более строгие требования.
В общем, тестировщик — это важная, ответственная и почётная работа. А чтобы не заканчивать ни на грустной, ни на пафосной ноте — вот классический анекдот про тестирование:
Тестировщик заходит в бар и заказывает:
кружку пива,
2 кружки пива,
0 кружек пива,
999999999 кружек пива,
ящерицу в стакане,
–1 кружку пива,
qwertyuiop кружек пива.
Первый реальный клиент заходит в бар и спрашивает, где туалет. Бар рушится.
На этом всё! Переходите к последнему параграфу, коротко подведём в нём итоги главы.
