Часто в РФ приходится слышать мнение, что в Firmware разработке якобы в принципе не может быть никакого модульного тестирования. Во всяких военных НИИ даже бытует расхожее мнение
Не нужны никакие тесты. Если программист хороший, то и код он пишет без ошибок.
Попробуем разобраться какие есть плюсы и минусы в модульном тестировании и понять надо оно или нет.
Пролог
Что такое модульный тест? Это просто функция, которая тестируют другую функцию. Это способ получить обратную связь от того кода, который был написан Software In The Loop (SIL). Модульные тесты должен писать прежде всего сам автор основного кода. Также весьма полезны непредвзятые тесты от человека со стороны.
Достоинства модульных тестов
Зачем могут быть нужны модульные тесты?
1--Прежде всего для контроля работоспособности функционала. Тут всё очевидно. Тест доказывает, что какой-то код работает.
*2--Для безопасного перестроения программы (рефакторинга). Часто первый написанный код не самый понятный, переносимый и быстрый. Код просто минимально допустимо работает. При масштабировании оригинальный код однозначно придётся менять, упрощать. Модульные тесты дадут сигнал, если перестройка проекта вышла из-под контроля и что-то рассыпалось.
*3--Тесты как способ документировании кода. Посмотрите на тест и вы увидите как заполнять прототип функции и вам не придётся писать doxygen для каждой функции или параграфы из комментариев.
Хороший С-код понятен без комментариев.
*4--Модульные тесты служат критерием завершения работы на конкретном этапе. Разрабатываем код пока не пройдут тесты. Далее принимаемся за следующий программный компонент. Всё четко и понятно.
*5--Существующие тесты помогут для контроля исполнения работы. Team Lead может написать тесты, а инженер-программист разработает программные компоненты для прохождения этих тестов.
*7--Для снятия ответственности с программиста. Программисты должны быть сами заинтересованы в том, чтобы писать код с тестами. В этом случае они всегда смогут сказать:
Смотрите. Код, который я написал, прошел тесты позавчера. Значит я не причем в том, что сегодня прототип загорелся перед инвесторами
Если в фирме не принято тестировать код, то в такой организации как правило всю вину сваливают на программистов. Вам оно надо?
6--Для покрытия кода. Если есть тест для компонента, значит есть и покрытие кода в этом компоненте. При настроенном измерении покрытия кода можно при помощи модульных тестов выявлять лишний и недостижимый код.
*8--Когда практикуется тестирование кода, то и код естественным образом получается структурируемый, модульный, простой, понятный и переносимый. Это один из любимых побочных эффектов модульных-тестов, модульные тесты убивают спагетти-код.
![](https://habrastorage.org/getpro/habr/upload_files/bcd/044/8df/bcd0448df44f182375060ae0bc7c35b6.png)
Напротив, когда тесты не пишутся, то код получается похож на спагетти: циклопические функции, перемешанный аппаратно-зависимый и аппаратно-независимый код, куча магический циферок и прочее.
*9--Модульные тесты дадут вам гарантию, что в другом окружении (на другой платформе (PowerPC, AVR, ARC, ARM Cortex-M, x86, RISC-V) и другом компиляторе (IAR, CCS, GCC, GHS, Clang)) функционал будет вести тебя как и прежде и работать как и задумывалось изначально.
Без тестов при миграции на очередной микроконтроллер другого вендора или альтернативный компилятор языка С у вас будет просто не работать часть функционала и вы не будете понимать почему это происходит, далее вы и еще будете долго ловить гейзенбаги и шреденбаги в самый неподходящий момент.
*10--Модульные тесты полезны для регрессионного тестирования. Для гарантии, что новое изменение не поломало старый функционал. Так как все зависимости предугадать невозможно.
*11--Разработка с тестированием придает процессу создания софтвера положительный азарт. После коммита так и хочется открыть CI и проверить прошли ли все тесты. Если прошли, то наступает радость и гордость за проделанную работу.
Напротив при программировании без тестирования разработчика преследует чувство тревоги, неуверенности. Такие программисты так и ждут, что в любой день придут ругать и критиковать его за найденную ошибку в программе.
*12--Прогон модульных тестов ровным счетом ничего не стоит. Тесты можно прогонять хоть каждый день (например во время перерыва).
Напротив, ручное же тестирование программ по check-листу очень дорого так как это человеко-часы. Плюс при ручном тестировании в ход ступает целый калейдоскоп когнитивных искажений свойственный человекам.
*13--Модульные тесты это очень удобная метрика для менеджера проекта. Если тесты проходят и их количество день ото дня возрастает то значит что всё идет хорошо.
*14--Если код покрыть модульными тестами, то в пошаговой отладке нужда как таковая отпадает сама собой. Чтобы найти баг надо только прогнать скопом все тесты и отчет сам покажет, какая функция оказалась бажной.
Недостатки модульных тестов
1--Нужна память для хранения кода с тестами. Часто можно услышать высказывание: "Я не буду добавлять тесты в сборку так как у меня мало flash памяти в микроконтроллере". Разруливается эта ситуация очень просто. Если все тесты не помещаются в NorFlash память то делим общее количество тестов на несколько сборок. Записываем их по очереди на Target, прогоняет группы тестов и сохраняем их логи в отчет или генерируем XML таблицу с полным отчетом. Всё это можно легко сделать автоматически в том же CI CD на основе Jenkins. При условии что в прошивке есть загрузчик и интерфейс командной строки Shell.
2--Сам код тестов может содержать ошибку. Вот тут придется организовать инспекцию программ. Тесты надо писать так чтобы в них нечему было ломаться.
3--Можно написать чрезмерное и избыточное количество тестов. Повторные тесты только с разными именами. Это реальная проблема. Для ликвидации этого по хорошему надо измерять покрытие кода. А это весьма высокоорганизованный процесс. Нужен программный инструментарий для измерения покрытия кода в RunTime прямо на Target(е). Как правило такие технологии платные (Testwell CTC++, LDRA).
Однако есть и второй более простой путь. Писать тесты исходя из технический требований (если они есть). Тогда все понятно. Есть требование будет тест. Нет требования - не будет теста.
4--Модульные тесты не могут протестировать весь нужный функционал. Нужны еще интеграционные тесты. На эту тему есть множество мемов.
![](https://habrastorage.org/getpro/habr/upload_files/a6d/065/237/a6d065237365c769ebb25d7f93c1ed80.png)
![](https://habrastorage.org/getpro/habr/upload_files/885/e35/693/885e35693e8e9de51769f6448ed9e543.jpg)
Или, например, uTest для дверей в метро. По отдельности они могут отлично открываться, но это не значит, что дверцы откроются одновременно в одну сторону.
![](https://habrastorage.org/getpro/habr/upload_files/c1e/5b4/ec6/c1e5b4ec6e3926d3664fe75dfd6f3b28.png)
Ок. Допустим, что походили про граблям и пришли к выводу, что модульные тесты всё таки нужны. Как же делать это пресловутое модульное тестирование?
Общие принципы модульного тестирования
*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--Любой предыдущий тест не должен ломать последующий тест. Даже если все тесты по отдельности проходят. То есть любая перестановка порядка исполнения модульных тестов должна проходить успешно.
*20--Модульные тесты надо прогонять на Target устройстве. То есть прямо на микроконтроллере (см HIL). В естественной среде обитания. Запускать тесты можно по команде через UART-CLI/Shell при помощи Putty/TeraTerm
В чем недостаток авто тестов?
Дело в том что система тестирования как правило оказывается сложнее самого продукта.
Вот вам яркий пример.
Вы написали код для воспроизведения звука через микросхему аудиокодека SGTL5000XNAA3, MAX9860 или WM8731. Как это автоматически протестировать? Как проверить что звук в самом деле есть? А вот как. Надо воспроизвести синус (например на 2000 Hz), записать 50...100ms звука встроенным в этот же аудио кодек ADC, затем вычислить DFT, найти максимум, сверить максимум DFT с настройками воспроизведения. Если есть совпадение, то тест пройден. Иначе нет. В результате нет нужды в операторе чтобы подтверждать услышанный звук.
И затраты на разработку тестирование и отладку программных компонентов ADC, DFT окажутся в три-четыре раза больше, чем на разработку простецкого драйвера DAC.
Поэтому тут может оказаться лучше прибегнуть к покупке готового тестировочного оборудования, в данном примере какого-нибудь акустического спектро-анализатора с UART интерфейсом.
Второй пример
Для автоматического тестирования копеечного графического монохромного дисплея SSD1306 нужна какая-то камера с видео аналитикой, чтобы распознать и классифицировать то тестовое изображение, которое мы воспроизвели. Можно, например, отрисовывать на дисплее SSD1306 QR-коды и распознавать их считывателем DataMatrix кодов с UART интерфейсом.
В связи с этим общее правило автоматического тестирования таково
Разработка системы авто тестов всегда дороже разработки продукта для которого она предназначена.
Если вы затратили на разработку продукта N рублей, то на разработку стенда авто тестов вы потратите 3N рублей. Тут вариантов нет. При этом разрабатывать систему авто тестов должны более квалифицированные инженеры и программисты, чем те кто разрабатывают сам продукт. Вот так...
Вывод
Плюсов у модульного тестирования много. Минусов мало и они решаемые. Как по мне модульные тесты на самом деле нужны всем: программистам, team lead(ам) и менеджерам.
Если в сорцах прошивки нет модульных тестов, то это Филькина грамота
Пишите код firmware с тестами.
Links/ URLs
Запускаем юнит тесты на микроконтроллере. https://www.youtube.com/watch?v=-hM38-JDt8c
Модульное тестирование для малых встраиваемых систем https://www.youtube.com/watch?v=idlKlSHc0wU