Модульные тесты для проектов Ардуино

Модульные тесты для проектов Ардуино: https://habr.com/post/419445/
По результату получился эталонный срач на 170 коментов (для меня, кажется, это рекорд, на хабре точно) СТМ vs Ардуина (про тесты почти ничего не говорили), спасибо Олегу Артамонову, без него бы ничего не получилось.
На следующий день товарищ Артамонов запилил свою статью про СТМ-ардуину (mbed) с более лучшим текстовым редактором (IDE!), более лучшим HAL, таймерами (в официальной поставке!) и без бесконечного цикла (пожалуй, за это можно правда зацепиться): Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих
Весь срач переехал туда (и быстро переплюнул мой по количеству коментов), по результату топик-стартера забанили на несколько дней.
автапати раз, автапати два
«Серьезные» разработчики встраиваемых систем (читай: стмщики) время от времени любят шпынять голозадых «ардуинщиков», у которых среда разработки, помимо всего прочего, не поддерживает даже аппаратные отладчики с точками останова и просмотром значений переменных под курсором мышки или в специальной табличке в реальном времени. Что ж, обвинение вполне справедливо, окошко Монитора последовательного порта (Serial Monitor) плюс Serial.println — не самый лучший инструмент отладки. Однако грамотный ардуинщик сможет с легкостью парировать атаку и поставить зарвавшегося стмщика на место в том случае, если он (ардуинщик) использует модульные тесты.
Кому лень идти на Хабру, традиционно,
Итак, модульные тесты (unit tests, юнит-тесты) облегчают жизнь при поиске проблемных мест приложения, предотвращают повторение уже найденных проблем (регрессий), дают измеримую уверенность в надежности написанного кода. Это тем более важно при разработке встраиваемых приложений и всевозможных мобильных роботов, для которых процесс отладки, отлова и воспроизведения (особенно, воспроизведения) ошибок особенно затруднителен по сравнению с классическими настольными, серверными или мобильными приложениями.
Однако переход к использованию автоматических тестов в проекте требует специальной внутренней дисциплины, особого подхода к написанию кода и организации рабочего пространства проекта.
При подготовке к внедрению в проект модульных тестов следует иметь ввиду:
- Тесты требуют дополнительного времени для написания кода (на самом деле, нет: время, потраченное на автоматические тесты, вполне сравнимо со временем, потраченным на ручную отладку того же участка, а на долгой дистанции оно еще многократно окупится), при этом код теста может превышать по размеру код тестируемого участка.
- В покрытом тестами проекте может быть сложно проводить глобальную реорганизацию кода (рефакторинг) — особенно актуально на начальном этапе разработки, когда кодовая база и внутренний API еще не достаточно устаканились (с другой стороны, рефактор проекта, не покрытого тестами, повлечет все те же регрессии, просто вы про них не узнаете)
- Нужно писать модули приложения так, чтобы их можно было запускать как в рамках приложения, так и внутри отдельных тестов
- Необходимо проработать структуру и связи внутри проекта так, чтобы в нем нашлось место коду основного приложения, исполняемой прошивке основного приложения, коду тестов, исполняемой прошивке («запускальщик»/ланчер) для запуска тестов.
Я более не буду распространяться про философию модульного тестирования, а просто покажу, как технически внедрить простые модульные тесты в ваш проект на Ардуино.
Далее рассмотрим:
- Несколько стратегий организации рабочего пространства проекта с модульными тестами с учетом особенностей платформы Ардуино.
- Вариант «все в одном» (и код и тесты в одном файле скетча),
- вынесение тестов в отдельный модуль в каталоге скетча,
- вынесение тестов в отдельный проект.
- Запуск тестов на устройстве,
- запуск этих же тестов на настольном компьютере без загрузки на устройство, заглушки для API Ардуино
Выбор библиотеки для модульного тестирования
Нам нужен фреймворк модульного тестирования:
- Для Си/С++
- Должен работать на устройствах семейства Ардуино
- Должен работать на настольных системах
- Люблю легковесные библиотеки (моё персональное предпочтение)
Для программирования Ардуино используется язык С++ вперемешку с Си, поэтому, теоретически, пойдет любой фреймворк модульного тестирования для С++, но мы хотим запускать тесты и на настольном компьютере и на устройстве. Дело в том, что для Ардуино реализованы кое-какие вызовы стандартной библиотеки libc, но далеко не все, поэтому не каждый фреймворк, работающий с libc, скомпилируется для Ардуино. Верно и в обратную сторону: если фреймворк сделан специально для Ардуино, то он может не заработать на настольной системе с libc.
Я просмотрел несколько фреймворков и остановился на 2х:
- ArduinoUnit: https://github.com/mmurdoch/arduinounit. В общем, он удовлетворяет ключевым исходным требованиям: работает как на Ардуино (очевидно из названия), так и на настольных системах (см раздел «En Vitro Testing» на сайте проекта), но на беглый взгляд показался тяжеловатым и я решил посмотреть другие варианты.
- Библиотека Sput (Sput Unit Testing Framework for C/C++) https://www.use-strict.de/sput-unit-testing/. Это библиотека легкая настолько, насколько это возможно: всего один заголовочный файл, даже без пары с исходником «.cpp» (все сделано на нескольких макросах). Однако вывод сообщений идет через std::out (что совершенно естественно для libc), который на Ардуино как раз не реализован.
И все-таки мои симпатии перевесили в пользу sput, а проблему с std::out удалось решить несколькими исправлениями (заменой printf на sprintf+Serial.print).
В итоге получился проект sput-ino — порт библиотеки sput на платформу Ардуино с сохранением совместимости с настольными системами с libc
— пример однофайлового скетча с тестами
/sput-ino/examples/sput-ino-monolith/
— пример с разделением основного кода и тестов на модули
sput-ino/examples/sput-ino-modules/
— запуск тестов на настольной системе
sput-ino/example-desktop/
— пример с разделением основного кода и тестов на разные проекты
— в отдельном репозитории
https://github.com/sadr0b0t/sput-ino-demo
Установим библиотеку
Просто клонируйте репозиторий git https://github.com/sadr0b0t/sput-ino.git в каталог $HOME/Arduino/libraries:
https://github.com/sadr0b0t/sput-ino/ нажмите кнопку Клонировать или скачать > Скачать ZIP (Clone or download > Download ZIP), после этого установите архив sput-ino-master.zip через меню установки библиотек Ардуино: Скетч > Подключить библиотеку > Добавить .ZIP библиотеку....
Примеры появятся в меню Файл > Примеры > sput-ino (File > Examples > sput-ino)
Простой вариант: однофайловый
скетч с кодом и тестами
При внедрении тестов в проект Ардуино придется учитывать некоторые особенности её сборочной системы. В простейшем случае проект (скетч) состоит из одного файла с расширением «.ino». При сборке файл «.ino» с незначительными изменениями конвертируется в «.cpp» (подключается заголовок Arduino.h и еще кое-чего по мелочи), сгенерированный файл компилируется в прошивку.
Создаем новый скетч
sput-ino/examples/sput-ino-monolith/sput-ino-monolith.ino
добавляем какой-то полезный код:
(int a, int b) (int pin, int num)http://www.use-strict.de/sput-unit-testing/tutorial.html):
(int a, int
b)
include">
());
sput_fail_unless(a_plus_b();
);
}
());
sput_fail_unless(a_minus_b();
}
() led#13 on");
led#13 on");
sput_fail_unless(!led_on_even( led#13 off");
sput_fail_unless(digitalRead( led#13 off");
sput_fail_unless(led_on_even( led#13 on");
sput_fail_unless(digitalRead( led#13 on");
}
Комплектуем наборы тестов (тест-сьюты).
Все тесты в одном наборе:
());
sput_run_test(test_a_plus_b);
sput_enter_suite();
sput_run_test(test_a_minus_b);
sput_enter_suite();
sput_run_test(test_led_on_even);
sput_finish_testing();
());
sput_run_test(test_a_plus_b);
sput_finish_testing();
());
sput_run_test(test_a_minus_b);
sput_finish_testing();
());
sput_run_test(test_led_on_even);
sput_finish_testing();
());
);
}
Добавляем обычные setup/loop, запускаем тесты с run_tests в setup в самом начале, предварительно инициировав последовательный порт Serial.begin, чтобы тесты могли печатать сообщения:
());
pinMode( ()#################### ==
[ pass
[ pass
[ pass
==
[ pass
[ pass
==
[ led#13 on" pass
[ led#13 on" pass
[ led#13 off" pass
[ led#13 off" pass
[ led#13 on" pass
[ led#13 on" pass
#################### Start testing...
[1:1] testaplus
[1:1] testa 2 check(s), 2 ok, 0 failed (?%)
==> 2 check(s) in 1 suite(s) finished after ? second(s),
[1:1] test led#13 on" pass
[1:2] test_led led#13 on" pass
[1:3] test led#13 off" pass
[1:4] test_led led#13 off" pass
[1:5] test led#13 on" pass
[1:6] test_led led#13 on" pass
--> 6 check(s), 6 ok, 0 failed (?%)
==> 6 check(s) in 1 suite(s) finished after ? second(s),
https://habr.com/post/419445/
|
</> |