Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Часто в РФ приходится слышать мнение, что в Firmware разработке якобы в принципе не может быть никакого модульного тестирования. Бытует даже расхожее мнение
Не нужны никакие тесты. Если программист хороший, то и код он пишет без ошибок.
Попробуем разобраться какие есть плюсы и минусы в модульном тестировании и понять надо оно или нет.
Что такое модульный тест? Это просто функция, которая тестируют другую функцию. Это способ получить обратную связь от того кода, который был написан. Модульные тесты должен писать прежде всего сам автор основного кода. Также полезны непредвзятые тесты от человека со стороны.
Достоинства модульных тестов
Зачем могут быть нужны модульные тесты?
1--Прежде всего для контроля работоспособности функционала. Тут всё очевидно. Тест доказывает, что какой-то код работает.
*2--Для безопасного перестроения программы (рефакторинга). Часто первый написанный код не самый понятный, переносимый и быстрый. Код просто минимально допустимо работает. При масштабировании оригинальный код однозначно придётся менять, упрощать. Модульные тесты дадут сигнал, если перестройка проекта вышла из-под контроля и что-то рассыпалось.
*3--Тесты как способ документировании кода. Посмотрите на тест и вы увидите как заполнять прототип функции и вам не придётся писать doxygen для каждой функции или параграфы из комментариев.
Хороший С-код понятен без комментариев.
*4--Модульные тесты служат критерием завершения работы на конкретном этапе. Разрабатываем код пока не пройдут тесты. Далее принимаемся за следующий программный компонент. Всё четко и понятно.
*5--Существующие тесты помогут для контроля исполнения работы. Team Lead может написать тесты, а инженер-программист разработает программные компоненты для прохождения этих тестов.
*7--Для снятия ответственности с программиста. Программисты должны быть сами заинтересованы в том, чтобы писать код с тестами. В этом случае они всегда смогут сказать:
Смотрите. Код, который я написал, прошел тесты позавчера. Значит я не причем в том, что сегодня прототип загорелся перед инвесторами
Если в фирме не принято тестировать код, то в такой организации как правило всю вину сваливают на программистов. Вам оно надо?
6--Для покрытия кода. Если есть тест для компонента, значит есть и покрытие кода в этом компоненте. При настроенном измерении покрытия кода можно при помощи модульных тестов выявлять лишний и недостижимый код.
*8--Когда практикуется тестирование кода, то и код естественным образом получается структурируемый, модульный, простой, понятный и переносимый.
Напротив, когда тесты не пишутся, то код получается похож на спагетти: циклопические функции, перемешанный аппаратно-зависимый и аппаратно-независимый код, куча магический циферок и прочее.
*9--Модульные тесты дадут вам гарантию, что в другом окружении (на другой платформе (PowerPC, AVR, ARC, ARM Cortex-M, x86, RISC-V) и другом компиляторе (IAR, CCS, GCC, GHS, Clang)) функционал будет вести тебя как и прежде и работать как и задумывалось изначально.
Без тестов при миграции на очередной микроконтроллер другого вендора или альтернативный компилятор языка С у вас будет просто не работать часть функционала и вы не будете понимать почему это происходит, далее вы и еще будете долго ловить гейзенбаги и шреденбаги в самый неподходящий момент.
*10--Модульные тесты полезны для регрессионного тестирования. Для гарантии, что новое изменение не поломало старый функционал. Так как все зависимости предугадать невозможно.
*11--Разработка с тестированием придает процессу создания софтвера положительный азарт. После коммита так и хочется открыть CI и проверить прошли ли все тесты. Если прошли, то наступает радость и гордость за проделанную работу.
Напротив при программировании без тестирования разработчика преследует чувство тревоги, неуверенности. Такие программисты так и ждут, что в любой день придут ругать и критиковать его за найденную ошибку в программе.
*12--Прогон модульных тестов ровным счетом ничего не стоит. Тесты можно прогонять хоть каждый день (например во время перерыва).
Напротив, ручное же тестирование программ по check-листу очень дорого так как это человеко-часы. Плюс при ручном тестировании в ход ступает целый калейдоскоп когнитивных искажений свойственный человекам.
*13--Модульные тесты это очень удобная метрика для менеджера проекта. Если тесты проходят и их количество день ото дня возрастает то значит что всё идет хорошо.
Недостатки модульных тестов
1--Нужна память для хранения кода с тестами. Часто можно услышать высказывание: "Я не буду добавлять тесты в сборку так как у меня мало flash памяти в микроконтроллере". Разруливается эта ситуация очень просто. Если все тесты не помещаются в NorFlash память то делим общее количество тестов на несколько сборок. Записываем их по очереди на Target, прогоняет группы тестов и сохраняем их логи в отчет или генерируем XML таблицу с полным отчетом. Всё это можно легко сделать автоматически в том же CI CD на основе Jenkins. При условии что в прошивке есть загрузчик и интерфейс командной строки Shell.
2--Сам код тестов может содержать ошибку. Вот тут придется организовать инспекцию программ. Тесты надо писать так чтобы в них нечему было ломаться.
3--Можно написать чрезмерное и избыточное количество тестов. Повторные тесты только с разными именами. Это реальная проблема. Для ликвидации этого по хорошему надо измерять покрытие кода. А это весьма высокоорганизованный процесс. Нужен программный инструментарий для измерения покрытия кода в RunTime прямо на Target(е). Как правило такие технологии платные (Testwell CTC++, LDRA).
Однако есть и второй более простой путь. Писать тесты исходя из технический требований (если они есть). Тогда все понятно. Есть требование будет тест. Нет требования - не будет теста.
4--Модульные тесты не могут протестировать весь нужный функционал. Нужны еще интеграционные тесты. На эту тему есть множество мемов.
Или, например, uTest для дверей в метро. По отдельности они могут отлично открываться, но это не значит, что дверцы откроются одновременно в одну сторону.
Ок. Допустим, что походили про граблям и пришли к выводу, что модульные тесты всё таки нужны. Как же делать это пресловутое модульное тестирование?
Общие принципы модульного тестирования
*1--Код отдельно тесты отдельно. Тесты и код разделять на разные программные компоненты. Распределять по разным папкам. Один и тот же тест тестирует разные версии своего компонента.
*2--Каждый тест должен тестировать только что-то одно
*3--Прежде чем начать чинить баг надо написать тест на этот баг. Это позволит отладить сам тест.
*4--Тест должен быть полностью детерминированным. Алгоритм теста должен быть постоянным. Не стоит использовать в тесте генератор случайных чисел. Иначе можно получить тест который, то будет проходить, то не будет.
*5--Тест должен легко пониматься. Юнит тест должен быть простым как табуретка.
6--Тест должен быть коротким
7--У теста минимальные затраты на сопровождение
*8--Тест должен проверять предельные случаи
*9--Тесты должны быть интегрированы в цикл разработки
10--Тест должен легко запускаться. Например по команде из UART CLI.
*11--Тест должен быть устойчив к рефакторингу
12--Тест проверяет только самые важные участки кода
*13--Чинить тесты надо в первую очередь. После продолжать писать production код.
14--Тесты либо работают либо удалены.
15-- Желательно чтобы тест писал не разработчик тестируемого компонента. Нужен непредвзятый взгляд. Например разработчик другого компонента может добавить часть своих тестов для компонента своего смежника.
*16-- Тесты составлять по принципу три A: Arrange, Act, Assert AAA
17-- В модульных тестах не должно быть оператора if в явном виде
*18-- Модульный тест должен воспроизводится. Успешный тесты (зеленые) должны быть успешными при повторном запуске. Красные тесты на бажном коде должны постоянно падать. Результат модульного теста не должен зависеть от случайных величин таких как "угол Солнца над горизонтом или фаза Луны".
*19--Любой предыдущий тест не должен ломать последующий тест. Даже если все тесты по отдельности проходят. То есть любая перестановка порядка исполнения модульных тестов должна проходить успешно.
Вывод
Плюсов у модульного тестирования много. Минусов мало и они решаемые. Как по мне модульные тесты на самом деле нужны всем: программистам, team lead(ам) и менеджерам.
Если в сорцах прошивки нет модульных тестов, то это Филькина грамота
Пишите код firmware с тестами.