Comments 30
Но это не значит, что юнит-тесты везде отобьются. Большинству средней руки приложений, тесты нафиг не надо. Скажем, какой-нибудь UI на реакте, или там какой-нибудь server-side .NET — который из SQL в JSON перекладывает — это всё зачем тестами покрывать? Вот, скажем, хабрахабр тот же — какой смысл по TDD писать?
Да вообще никакого. Вот поломаете кусок сайта, полежит он денек-второй, ничего же не поменяется, правильно?
А потом рано или поздно получается так, что количество поломок при изменениях растет, и вот уже тим. лид/менеджер/бизнес запрещает вам трогать что угодно, что может как-то задеть другой функционал и вы начинаете строить сложные костыли вокруг. Количество костылей растет, они уже не спасают и потом становится все плохо.
Тесты это способ проверить, что ваш код работает как работал и будет работать. Если вы пишете продукт на один раз, то плевать, а если не хотите каждый релиз потом еще неделю фиксить — то стоит подумать про тесты.
Вообще, сейчас же весь веб пишется как в последний раз — чик-чик, и зарелизили. Поперло — будут деньги переписать, не поперло — хорошо что лишнего не потратили. Там вообще нечего «фиксировать» тестами — всё по-кругу переписывается с нуля.
Я вот почему-то уверен, что в коде того же Хабрахабра тесты если и есть, то где-то в самых ключевых местах только. И фичи они на нас через A/B тестирование проверяют. И ничего, норм всё едет.
У меня есть отличная картинка для вас.
Ну и в самой статье отлично написано — настоящий QA не дешевле автотестов, как минимум потому, что люди отвратительно масштабируются. А адекватность верстки и правописание отлично проверяется автоматическими тестами.
А касательно "переписать" — недавно на хабре уже была волна постов на эту тему. Вам может очень не повезти и переписывать никто не даст, так что лучше сначала делать нормально.
Ну и касательно Хабра — я не совсем понимаю, почему этот ресурс внезапно служит у всех каким-то эталоном продукта. Ребята не могли тысячи лет прикрутить markdown в посты и комментарии. И как бы "норм, все едет" — это отличная оценка для бизнеса, который не трогает код, а для программиста — такое себе. Вам работать с этим кодом и строить магические костыли вокруг непонятных участков кода, которые никто не может отрефакторить, потому что они непокрыты тестами и критичные.
Вот, скажем, хабрахабр тот же — какой смысл по TDD писать?
TDD полезен для проработки архитектуры в том числе. Ну, а на Хабре не такая тривиальная бизнес-логика, как кажется на первый взгляд.
Писать модульные тесты (как и любые другие) на все подряд (чтобы code coverage был почти 100%) — неверно. Я бы даже назвал это антипаттерном поведения. Вы правильно указали, что зачастую бывает достаточно закрыть модульными тестами только фундамент приложения, автоматизировать приемочные тесты, а все остальное отдать на откуп ручникам. На мой взгляд, это позволяет соблюдать баланс между качеством тестирования и скоростью реакции на изменчивые требования рынка во многих проектах.
А те, кто всё ещё не понял принципа применения тестов, должны наконец уяснить:
Есть СТОИМОСТЬ теста, его ВРЕМЯ ЖИЗНИ, его ПРИМЕНИМОСТЬ и наконец, стоимость ошибки. Если ничего из этого не учитывалось, ваши тесты НЕ НУЖНЫ.
Написание тестов должно поддерживаться со стороны менеджмента.… Менеджмент должен поддерживать написание тестов и понимать, зачем они вообще появляются в проекте.
Менеджмента ИТ или бизнеса?
Один маленький тест для проверки входных данных — и 2,6 млрд рублей были бы спасены.
А вы думаете, входные данные живут сами по себе? :) Увы, нет. Они пошли в математику и вызвали цепочку неких управляющих воздействий от программы в целом. Проверить такое — та ещё задача.
Вот вам будущая программа одного из устройств. Она на Си и должна работать на микроконтроллере (всего контроллеров 5 и они объединены ДОЗУ), но с обвязкой на Си++ (чтобы можно было отладить на PC — запускается в ОС QNX 6.3). Как бы вы написали бы тесты для такой программы? Я вот даже в принципе этого не представляю.
В коде, который вы прислали, нет ничего специфичного. Есть интерфейс классов или функции модулей, на них можно написать модульные тесты. Или написать интеграционные тесты на их взаимодействие. Вопрос, как и какими инструментами писать тесты, выходит за рамки этой статьи.
Можно было написать тест, что поставщик данных, который отдает их в блок математики, работает правильно.
В том-то и дело, что нельзя. Диапазон всех этих величин — вся сетка координат. На эти данные программа может реагировать совершенно по-разному.
Есть интерфейс классов или функции модулей, на них можно написать модульные тесты. Или написать интеграционные тесты на их взаимодействие.
Это чисто формальный подход. Но я ведь не рассказал, что делает эта программа. А делает она вот что — её основная задача пытаться бороться с отказами взаимозависимой аппаратуры (одно устройство подключено к другому). Так вот, ситуаций отказов на всех этапах работы алгоритмов (включения, отключения, работы модели, теста устройств и так далее) может произойти просто тьма. Все они поведут программу в совершенно разные состояния, сильно зависящие от того, на каких этапах и по каким признакам произошли отказы. Все эти случаи я даже перечислить не могу.
Вопрос, как и какими инструментами писать тесты, выходит за рамки этой статьи.
Этим грешат все статьи про тесты. Либо в примерах тестируют простую функцию, либо общие слова о полезности тестов. Ни разу я не встретил статьи, где бы тестировалось реальное, относительно крупное приложение на Си/Си++ по полочкам. А вот такая статья очень-очень нужна.
В том-то и дело, что нельзя. Диапазон всех этих величин — вся сетка координат. На эти данные программа может реагировать совершенно по-разному.
Для борьбы с диапазоном значений существует понятие классов эквивалентности тестов. Для обеспечения полноты покрытия можно использовать pairwise testing. С точки зрения программной реализации также будет полезным property-based testing. Для многих языков, в том числе и для C++, существуют готовые инструменты.
Это чисто формальный подход
Если тестировать по методу «черного» ящика, то неважно, как устроена внутри программа. Есть набор входных данных, есть набор ожидаемых выходных. Этого достаточно. Можно тестировать и по методу «белого» ящика. Оба подхода не отрицают друга друга, а лишь дополняют.
Все эти случаи я даже перечислить не могу
Тогда проблема не в сложности тестирования и его автоматизации, а в том, что у вас нет требований к системе. Без их фиксации разрабатывать тестовую модель бессмысленно. Если вы не знаете, что тестировать, то и тестировать нечего.
Ни разу я не встретил статьи, где бы тестировалось реальное, относительно крупное приложение на Си/Си++ по полочкам. А вот такая статья очень-очень нужна
За C++ не ручаюсь, но про то, как мы пишем тесты для iOS-приложения (Objective-C + Swift), расскажем в одной из будущих статей.
Для борьбы с диапазоном значений
Говорю же, проблема не в области значений, а в том, как отреагирует система управления на эти значения. И чтобы проверить, что реакция правильная нужно имитировать всю траекторию полёта ракеты со всеми возможными нюансами. Иными словами, модульный тест тут бесполезен — ошибка заключается в работе алгоритма отработки воздействий во время работы программы. Это похоже на работу аналоговой схемы, где разработчик не знал о реальных физических свойствах деталей (скажем, что резистор нагреется и изменит сопротивление, чем, например, переведёт некий ПИД-регулятор в зону неустойчивости). Приложения управления аппаратурой (в том числе, ракеты) всегда имеют массу нюансов работы этой аппаратуры, которых нет у обычных прикладных программ. Учесть всё это можно разве что в теории.
Есть набор входных данных, есть набор ожидаемых выходных.
Ожидаемые выходные не формализованы в своей последовательности. Примерно, как у человека. Протестируйте алгоритм выхода человека из аварийной ситуации (скажем, при пожаре). Увидите, сколькими способами и в какой последовательности человек будет решать эту задачу. Также вы увидите, какие решения оказались правильными. Кстати, примерно так играют в игры — эдакий метод проб и ошибок.
Без их фиксации разрабатывать тестовую модель бессмысленно. Если вы не знаете, что тестировать, то и тестировать нечего.
Нет, требования к системе как раз есть — она должна обеспечивать живучесть комплекса. Нет множества тестовых последовательностей (их число как в шахматах число ходов — комбинации что после чего отказало) и неизвестен порядок действий программы (он крайне сильно зависит от входных последовательностей и моментов времени, когда они происходят). Как так вышло? Программа ориентируется на здравый смысл, примерно, как человек. Вот эти «здравые смыслы» в неё и заложены. Вопрос только в том, как проверить, что они во всех ситуациях действительно здравые и не забыли ли мы что-то ещё добавить в здравый смысл, что в 0.01% ситуаций поставит программу на неверную алгоритмику исправления ситуации.
Да, иногда для полноценного тестирования системы нужно количество тестовых данных, которое вызывает комбинаторный взрыв. Так может быть. Тогда оставляют только крайние значения и что-то из середины, чтобы проверить соответствие критериям надежности. Но вот удостовериться, что для старта ракеты указаны правильные координаты стартовой площадки, не так уж и сложно. Тут одной проверки достаточно.
Для старта ракеты координаты нет нужды проверять — их в программе не хранят и передают извне. Программа формально работает с любыми координатами. Но вот нюансы управляющих воздействий могут получаться разными, в зависимости от координат. И не всегда они верные из-за каких-либо особенностей (просто вообще в принципе не учли какой-либо случай — даже в голову не пришло). Это похоже на решение интегралов в матлабе — в ряде случаев численное интегрирование дает существенно неверное значение из-за особенностей интегрируемой функции. И узнать об этом не так просто — чтобы об этом узнать, нужно как минимум такое искать. А в ПО ракеты просто не подумали о выявленной проблеме. И тест тут не поможет — нельзя тестировать то, что даже в голову не пришло.
Впрочем, я вас немного разочарую — мой опыт работы с НПО-разработчиком систем управления для военных ракет показывает, что со специалистами в части ПО там всё очень плохо (как вам контроль правильности массивов данных, сделанный как обычная сумма чисел? Для гарантированной доставки переданных данных их просто повторяют 4 раза и всё. Потом запрашивают устройство. Если устройство эти данные всё-таки не приняло (скажем, оно в неудачный момент перезагружалось), пробовать снова поработать с устройством нам не надо — мы бодро рапортуем об отказе комплекса. В одном приборе сделали прямой код для кодирования отрицательных целых чисел. Нахрена?! Переделывать не будут — сложно пересдать представителю заказчика. Протокол обмена придумал сумасшедший — в него всё добавляется по принципу «ой, а так не работает — нужен ещё и номер подмассива. А у нас свободных полей (страница максимум 64 байта — MIL STD) уже нет… А мы его засунем вот сюда. Если тут такое число, то это такой массив, а если другое, то это будет номер подмассива. Что вы говорите? То число тоже номером может быть? Хм… Ну и пусть будет.»).
Кто и как определяет, что решение удовлетворяет требованиям?
(я сейчас не про автотесты)
Нет, разве?
С тестированием плохо, когда требуется отладка реакций на отказы аппаратуры (аппаратура может отказывать множеством способов — от простого обрыва провода и залипания реле, до потери с ней связи). Тут мы тестируем только некоторые базовые вещи.
Вообще, было бы интересно узнать, как тестируют систему управления Space Shuttle…
Не все требования формализуются, и соответственно тесты к ним неприменимы.
Отдельная история — стоимость автотестов против ручных. Куча кейсов, когда ручные тесты легко переигрывают автотесты.
Недавно тут писали, что ПО зачуханого сервера тестируют гораздо больше, чем ПО ракет. Так вот, это не шутка была. ПО ракеты неотделимо от ракеты и тестируется вместе с ней же. Исключение составляет лишь маленькая часть ПО ракеты (скажем, какая-то относительно несложная математика). Её и тестируют более-менее. Всё, что работает с аппаратурой уже протестировать абстрактно не выйдет.
Как доказать важность тестов каждому участнику проекта