Как стать автором
Обновить

TDD для микроконтроллеров. Часть 1: Первый полет

Время на прочтение10 мин
Количество просмотров15K
Всего голосов 20: ↑18 и ↓2+16
Комментарии33

Комментарии 33

Все это к сожалению далеко от практической плоскости разработки для embedded.


Во первых вы не указали, что для того, чтобы писать тесты, в первую очередь нужны спецификации — т.е. формализованные требования, которые бы указывали, что ваша железяка вместе с ПО должна делать. Без них вы можете написать хоть тысячу тестов, которые не будут тестировать то, что вам надо.


Во-вторых проблема эмбеддед разработки в том, что требования выше обычно предъявляются к комбинации ПО+железо, а не к чистому ПО. Потому что сделать спецификацию на ПО многим откровенно лень, да и тестировать нужно все вместе — никому не нужно встраиваемое ПО, которое прекрасно работает в симуляторе, но не работает на реальном железе.


Ну и в третьих, если учитывать первые два пункта, разработка тестов для такого рода задач становится уже не тривиальной задачей написания кода за 5 минут, а вполне себе отдельным проектом, в котором код сочетается с тестовыми стендами и эмуляторами, которые имитируют внешние воздействия и считывают реакцию системы. И часто процесс разработки такого TDD стоит гораздо дороже разработки самого встраиваемого ПО и поэтому


в четвертых — обычный стол, пара кнопочек, светодиоды, мультиметр и осциллограф — наши друзья. Иногда ручной тестовый стенд. А автоматические тесты заменяем ручками :-) И это не потому, что так хорошо тестировать встраиваемое ПО, а потому, что по другому гораздо сложнее.

В корне с вами не согласен.
Скорее всего вы путаете системное тестирование (черный ящик) и юнит тестирование (белый ящик) .


т.е. формализованные требования, которые бы указывали, что ваша железяка вместе с ПО должна делать

Для юнит тестирование не нужны требования, нужна детальная архитектура. Т.е. описание (спецификация) функции, метода, класса. Вам все равно придется это делать. К вашей конкретной функции никаких требований никто не предъявляет, кроме вас самого… Таким образом, вы вначале сами описываете, что должна делать функция, входные, выходные параметры и можете либо сразу реализовать это, а потом покрыть функцию юнит тестами, либо вначале сделать юнит тесты, а потом сделать реализацию под эти тесты.


Во-вторых проблема эмбеддед разработки в том, что требования выше обычно предъявляются к комбинации ПО+железо, а не к чистому ПО.

Опять таки, скорее всего путаница между тестированием черного ящика, и белого. Ваш код функции по сути и есть спецификация функции и чтобы убедиться, что она делает, то, что вы задумали, вы и пишите юнит тест.
А вот как работает софт и железо вместе — уже пусть системные тесты проверяют, но если у вас нет требований, то тогда и проверять то нечего :)


Ну и в третьих, если учитывать первые два пункта, разработка тестов для такого рода задач становится уже не тривиальной задачей написания кода за 5 минут

Она точно такая же задача, как и написание кода. Вы можете точно также писать функцию, потом прошивать микроконтроллер, потом смотреть осцилографом, потом понять, что функция работает не так, снова менять функцию...(по сути будете делать ручной юнит тест). Вопрос зачем и сколько вы на это потратите время? Если все можно сделать юнит тестом?


Да еще и если потом кто-то поменяет вашу функцию, и она перестанет работать, так как вы задумали, вы даже об этом и не узнаете… Вообще замечательно.


С последним вашим абзацем почти согласен, юнит тесты используются, чтобы автоматизировать и проверить, что ничего с вашей функцией не случилось, что она работает так как вы задумали.


Или каждый раз прошивать и проверять, или просто запустить юнит тесты. Профит со всех сторон, экономия времени, ресурсов и затрат.

Да, я говорю о системном тестировании, поскольку слово юнит-тестирование в статье не встречается ни разу, а применимость TDD обсуждается в контексте подхода к разработке встраиваемого ПО в целом (типа встраиваемого ПО блока управления тормозной системой, а не одной из функций этого ПО)
То есть вы хотите сказать, что автор забыл упомянуть этот момент?

Дак автор описывает CppUTest — который как бы намекает на то, что это Unit Test Framework.

Во-первых, без формализованных требований сложно разработать что-либо конкретное, не говоря уже о тестах. В статье требования формализованы.

Во-вторых и в третьих, Обычно для теста разработанной системы «ПО + железо» используют системное тестирование. Действительно, это отдельная задача. В этой статье говорится о методологии TDD с целью разработки бизнес-логики, которая будет работать в конечном продукте. А тестирование системы «ПО + железо» в реальных условиях будем проводить в третьей части цикла статей вручную.
НЛО прилетело и опубликовало эту надпись здесь
В качестве IDE – Visual Studio, потому что нам нравится ее внешний вид, удобство отладки и рефакторинга кода. Данная IDE также подходит для написания кода на «чистом» C.

А как у нее с С99? Неужели добавили поддержку?

Вообще CppUTest можно использовать и с родным компилятором для микроконтроллера, а его можно подключить в IDE Visual Studio.

Но тогда их надо запускать или в симуляторе родной архитектуры МК, либо на тестовой плате и получать результаты тестов на комп каким-то образом.


Вообще, я может невнимательно читал, но в статье я не нашел, под какую платформу происходит компиляция и как происходит запуск.
Мне показалось, что тесты компилируются под х86 и запускаются там же...

Все верно, по моему опыту 90-95% юнит тестов нормально на симуляторе можно проверить. На IAR все это делается через C-SPY.


По поводу х86, да тоже не совсем понял из статьи на какой платформе запускаются тесты, но ничего не ограничивает (кроме ресурсов (например, ОЗУ) микроконтроллера) запустить их на целевой платформе, в данном случае ARM CortexM.

Мой опыт тестов в симуляторе Keil говорит примерно то же самое; единственное что тесты иногда довольно долго выполняются.

НЛО прилетело и опубликовало эту надпись здесь

У меня есть старая статья (которая, правда, не совсем про это), сейчас я пользуюсь другим фреймворком; тоже самодельным, но уже на С++, регистрировать тесты гораздо удобнее и boost не нужен.


Если коротко, то все сводится к запуску в симуляторе, с перенаправлением вывода printf в отладочное окно.


Как-то сходу ничего больше на нашел. Но если хотите, могу статью написать :)

+1 за статью о юнит тестировании в Keil
Так C-SPY это просто расширенная отладка. Или там уже assert можно задавать?

C-SPY чтобы запустить юнит тесты на целевой платформе, либо в симуляторе, либо прямо на плате. Не совсем понял, что вы имели ввиду под задавать assertы. Но результаты с тестов выдаются через Terminal I/O.


Подробнее можете здесь прочитать:
https://cpputest.github.io/stories.html

Да, Вы правы, тесты компилируются и запускаются на х86
Поддержка не полная, поэтому можно использовать другую IDE на свой вкус и цвет, как написано в статье.
В чем смысл проверки указателя на ноль перед вызовом free() и обнуления указателя перед выходом из функции?
void Configurator_Destroy(Configurator * self)
{
    if (self == NULL) // Зачем?
    {
        return;
    }
    free(self);
    self = NULL; // Зачем?
}
Согласно спецификации free() сама должна проводить проверку параметра на неравенство нулю. Обнуление self перед выходом так же не имеет никакого смысла.
К сожалению, мы живем не в идеальном мире. По нашему опыту не все реализации функции free() соответствуют спецификации, поэтому у нас сформировалось правило всегда проверять указатели на NULL.
В какой-то древней книжке еще в университете читал, что перед вызовом free обязательно надо проверять указатель, иначе UB. Естественно это может быть в целом ложным утверждением, справедливым только для той конкретной древней книжки и какого-то особенного Си, описанного в ней, но в памяти отложилось навсегда, посему делаю так же всю жизнь…
C99 функция void free(void *ptr)
If ptr is a null pointer, no action occurs. — передавать null безопасно.

Для чего это все на микроконтроллерах, если есть отладка?
Как решается проблема взаимодействия с реальным железом?

Для ускорения разработки. При правильном подходе к юнит тестам, отладку можно вообще не делать.


Отладка может делаться в двух случаях:


  • вы не знаете, как что должно вообще работать (например, не прочитали документацию, или первый раз знакомитесь с этим модулем и не до конца знаете как его настраивать, либо скудная документация (как например, на Миландр) с кучей ошибок) и тогда вы делаете отладку, но только для того, чтобы понять, как вообще это все работает — это считайте не разработка — это исследование.
  • отлавливание багов, вынесенных с системного тестирования

В нормальной разработке, когда все в мануалах на микроконтроллер прописано, достаточно сделать юнит тесты один раз, чтобы быть увереным, что ваш модуль работает как надо и никто ничего не поменял.


К примеру, настраиваете вы модуль SPI на нужные вам настройки (полярность, фазу и так далее) в методе Config(). Вы можете сделать юнит тест, который проверит, что после вызова этого метода установлены нужные биты регистров SPI. И запускать этот тест каждый раз при сборке. Если какой-то программист Снежинка взял и поменял фазу (считывание данных с переднего на задний фронт), у вас и дальше все может продолжить работать, в том числе и под отладкой, и вы не заметите подвоха, а вот пользователь вашего устройства, работающий на -50С уже заметить, так как до этого SPI работал на пределе, небольшой сдвиг от температуры и вуаля, ничего не работает


А вот юнит тест это бы увидел — настройка в регистре не та… — тест провалился.


При этом возникает резонный вопрос, если вы настройку сделали в полном соответствии с документацией на микросхему, зачем вообще это отлаживать?

Отладка обычно используется для решения конкретной проблемы. В то время как TDD — это методология разработки, у которой есть определенные преимущества. Применение TDD может сократить время отладки, так как множество ошибок может быть выявлено с помощью тестов. В результате разработчик будет более сосредоточен на написании кода, а не на поиске бага.
А проблему взаимодействия с «железом» обсудим в следующей части этого цикла статей.
НЛО прилетело и опубликовало эту надпись здесь
Хотелось бы в следующей статье увидеть использование данной методологии на конкретном рабочем примере. Например реализуйте простейшую мигалку на ws2812b и покажите что можно отследить тестами, а чего нельзя.

TDD концепция применима только к тривиальным приложениям. Потому что в более менее средней по сложности системе изначально не понятно как все будет выглядеть в конце, и соответсвенно о каких тестах можно говорить. Сначала пишется прототип, потом он дорабатывается и затем делают рефакторинг и покрывают тестами.
Это не только в embedded, а вообще везде в IT. Мне интересно те кто говорит про TDD вообще хоть что то сами писали?
p.s.
Тем более мне непонятно как TDD совмещается с Agile, TDD это более waterfall model — это реальный парадокс.

Когда пишется что-то, то всегда есть выбор, как это тестировать: отлаживать и проводить системное тестирование в конце уже готового изделия\продукта, инкрементно разрабатывать, и на каждый такой инкремент писать юнит тест, или сначала прорабатывать требования, делать предварительное проектирование модуля\класса\метода, писать для этого функционала-инкремента тест, а потом писать реализацию. Да, действительно, все требования почти никогда не ясны с самого начала. Но ТЗ\требования бизнеса\заказчика присутствуют, а проектирование «нетривиального приложения» всегда есть инкрементный процесс, в самом начале которого идет (явно или неявно) фаза предварительного проектирования и формализация требований на следующее планируемый функционал. Так или иначе, когда программист начинает что-то сложное писать, он обязан понимать, что у него будет на выходе и какую задачу будет решать. Величина того, что он хочет сделать может помещаться в методе\классе или в какой-то большей сущности, но всегда есть требования на нее, а значит, можно и написать тест. Мне интересно, те, кто говорит про неприменимость TDD вообще когда-нибудь пробовали эту методологию или хоть пробовали писать что-то действительно сложное, когда отладка превращается в ад и сжирает намного больше времени, чем разработка?

P.S. Тем более не понятно, как TDD совмещается с waterfall моделью, когда как эта техника требует проведение маленьких итераций разработки.
Мне интересно, те, кто говорит про неприменимость TDD вообще когда-нибудь пробовали эту методологию или хоть пробовали писать что-то действительно сложное, когда отладка превращается в ад и сжирает намного больше времени, чем разработка?

Пробовали, и тут получается палка о двух концах — если упереться в TDD, то на написание тестов и их поддержку может тратиться больше ресурсов, чем на саму разработку, но зато имеем сокращенное время отладки. И наоборот.
То есть в одних случаях может быть выгодно первое, а в других второе. Например, если вносить изменения в прошивки после выпуска не требуется или требуется, но очень редко, то держать наготове TDD фреймворк имеет мало смысла — он просто устареет к тому времени, когда понадобится в следующий раз. А вот если у вас, как у Google, прошивки выходят каждый месяц, то да — есть смысл.

Ну так никто не говорит о TDD как о панацее! Вы совершенно правы, иногда это оправдано, иногда — нет. Но возможно)

А что у TDD фреймворка может устареть? Мне кажется что вы не совсем поняли концепцию. К примеру, сокращение времени отладки — это не единственный профит от применения TDD, их там небольшой список.
Как уже сказали, TDD и в самом деле не панацея. Но я считаю что это важный и нужный инструмент, приносящий пользу.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий