Как мне сделать TDD на встроенных устройствах?

17

Я не новичок в программировании, и я даже работал с некоторыми низкоуровневыми C и ASM на AVR, но я действительно не могу разобраться с более масштабным проектом встроенного C.

Будучи перерожденным философией Ruby TDD / BDD, я не могу понять, как люди пишут и тестируют подобный код. Я не говорю, что это плохой код, я просто не понимаю, как это может работать.

Я хотел больше узнать о программировании на низком уровне, но я действительно не представляю, как к этому подойти, поскольку это выглядит как совершенно другое мышление, к которому я привык. У меня нет проблем с пониманием арифметики указателей или с тем, как работает распределение памяти, но когда я вижу, как выглядит сложный код C / C ++ по сравнению с Ruby, это просто кажется невероятно сложным.

Поскольку я уже заказал себе плату Arduino, я бы хотел больше узнать о каком-либо низком уровне C и действительно понять, как все делать правильно, но, похоже, ни одно из правил языков высокого уровня не применимо.

Можно ли вообще использовать TDD на встроенных устройствах или при разработке драйверов или таких вещей, как собственный загрузчик и т. Д.?

Якуб Арнольд
источник
3
Привет, Дарт, мы действительно не можем помочь тебе преодолеть страх перед C, но вопрос о TDD на встраиваемых устройствах уместен здесь: я пересмотрел твой вопрос, чтобы включить его вместо этого.

Ответы:

18

Прежде всего, вы должны знать, что пытаться понять код, который вы не написали, в 5 раз сложнее, чем писать его самостоятельно. Вы можете изучать C, читая производственный код, но это займет намного больше времени, чем обучение на практике.

Будучи перерожденным философией Ruby TDD / BDD, я не могу понять, как люди пишут и тестируют подобный код. Я не говорю, что это плохой код, я просто не понимаю, как это может работать.

Это навык; Вы становитесь лучше в этом. Большинство программистов на C не понимают, как люди используют Ruby, но это не значит, что они не могут.

Можно ли вообще использовать TDD на встроенных устройствах или при разработке драйверов или таких вещей, как собственный загрузчик и т. Д.?

Ну, есть книги на эту тему:

введите описание изображения здесь Если шмель может это сделать, вы тоже можете!

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

Pubby
источник
2
Каждый TDD, который я видел для своих встроенных систем, обнаруживал только ошибки в системах, в которых было легко устранить ошибки, которые я мог бы легко найти самостоятельно. Они бы никогда не нашли то, с чем мне нужна помощь, зависящие от времени взаимодействия с другими чипами и взаимодействия прерываний.
Кортук
3
Это зависит от того, над какой системой вы работаете. Я обнаружил, что использование TDD для тестирования программного обеспечения в сочетании с хорошей аппаратной абстракцией, на самом деле, позволяет мне гораздо проще моделировать эти зависящие от времени взаимодействия. Другое преимущество, на которое люди часто обращают внимание, состоит в том, что тесты, будучи автоматизированными, могут быть запущены в любое время и не требуют, чтобы кто-то сидел на устройстве с логическим анализатором, чтобы убедиться, что программное обеспечение работает. TDD сэкономил мне несколько недель на отладке только в моем текущем проекте. Часто ошибки, которые мы считаем легко обнаружить, вызывают ошибки, которых мы не ожидаем.
Ник Паскуччи
Кроме того, это позволяет разрабатывать и тестировать вне цели.
cp.engr
Могу ли я следовать этой книге для понимания TDD для не-Embedded C? Для любого пользовательского пространства программирования C?
сверхобмена
15

Большое разнообразие ответов здесь ... в основном решение проблемы различными способами.

Более 25 лет я пишу встроенное низкоуровневое программное обеспечение и прошивки на разных языках - в основном на C (но с путями в Ada, Occam2, PL / M и множестве ассемблеров).

После долгих размышлений, проб и ошибок я остановился на методе, который довольно быстро получает результаты и довольно легко создает тестовые обертки и жгуты (где они ДОБАВЛЯЮТ ЗНАЧЕНИЕ!)

Метод идет примерно так:

  1. Напишите модуль кода драйвера или аппаратной абстракции для каждого основного периферийного устройства, которое вы хотите использовать. Также напишите один, чтобы инициализировать процессор и настроить все (это создает дружественную среду). Как правило, на небольших встроенных процессорах (например, ваш AVR) может быть от 10 до 20 таких устройств, все маленькие. Это могут быть единицы для инициализации, аналого-цифрового преобразования в немасштабированные буферы памяти, побитовый вывод, ввод с помощью кнопки (без выборки, только выборка), драйверы широтно-импульсной модуляции, UART / простые последовательные драйверы, использующие прерывания и небольшие буферы ввода / вывода. Может быть еще несколько - например, драйверы I2C или SPI для EEPROM, EPROM или других устройств I2C / SPI.

  2. Затем для каждого из модулей аппаратной абстракции (HAL) / драйвера я пишу тестовую программу. Это зависит от последовательного порта (UART) и инициализации процессора - поэтому первая тестовая программа использует только эти 2 модуля и выполняет только некоторые основные операции ввода и вывода. Это позволяет мне проверить, что я могу запустить процессор и что у меня есть базовая поддержка последовательного ввода-вывода поддержки отладки. Как только это сработает (и только тогда), могу ли я затем разработать другие тестовые программы HAL, построив их поверх известных хороших модулей UART и INIT. Поэтому у меня могут быть тестовые программы для чтения побитовых входных данных и отображения их в удобной форме (шестнадцатеричной, десятичной или любой другой) на моем терминале последовательной отладки. Затем я могу перейти к более крупным и более сложным вещам, таким как тестовые программы EEPROM или EPROM - я делаю большинство этих меню управляемыми, чтобы выбрать тест для запуска, запустить его и увидеть результат. Я не могу СКАРИТЬ это, но обычно я не

  3. После того, как все мои HAL запущены, я нахожу способ получить регулярный таймер. Это обычно со скоростью от 4 до 20 мс. Это должно быть регулярно, генерируется в прерывании. Как правило, это можно сделать с помощью опрокидывания / переполнения счетчиков. Затем обработчик прерываний увеличивает размер семафора в байтах. На этом этапе вы также можете возиться с управлением питанием, если вам нужно. Идея семафора состоит в том, что если его значение> 0, вам нужно запустить «основной цикл».

  4. EXECUTIVE запускает основной цикл. Он просто ожидает, что этот семафор станет ненулевым (я абстрагирую эту деталь). На этом этапе вы можете поиграть со счетчиками, чтобы подсчитать эти тики (потому что вы знаете частоту тиков), и поэтому вы можете установить флаги, показывающие, является ли текущий тик руководителя интервалом в 1 секунду, 1 минуту и ​​другие общие интервалы, которые вы используете. Возможно, захотите использовать. Как только руководитель узнает, что семафор> 0, он выполняет один проход через каждую функцию «обновления» процессов «приложения».

  5. Процессы приложения эффективно расположены рядом друг с другом и регулярно запускаются с помощью галочки «обновление». Это просто функция, вызываемая исполнительной властью. Это, по сути, многозадачность для бедняков с очень простой домашней ОСРВ, которая опирается на все приложения, входящие, выполняющие небольшую часть работы и выходящие. Приложения должны поддерживать свои собственные переменные состояния и не могут выполнять долгосрочные вычисления, потому что нет приоритетной операционной системы для обеспечения справедливости. Очевидно, что время выполнения приложений (совокупно) должно быть меньше, чем период основного тика.

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

Этот подход работает практически для любой коммуникационной системы, которую вы хотите назвать - он может (и уже работал) для многих проприетарных систем, систем связи с открытыми стандартами, он даже работает для стеков TCP / IP.

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

Я сделал еще один шаг вперед (идея, которую я получил от кого-то еще, кто сделал это) и заменил слой HAL аналогом для ПК. Так, например, вы можете использовать C / C ++ и winforms или аналогичные на ПК и, написав код ВНИМАТЕЛЬНО, вы можете эмулировать каждый интерфейс (например, EEPROM = файл диска, считываемый в память ПК), а затем запускать все встроенное приложение на ПК. Возможность использования дружественной среды отладки может сэкономить огромное количество времени и усилий. Только действительно большие проекты могут оправдать такое количество усилий.

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

Я надеюсь, что вышеизложенное дает некоторую изюминку ... этот подход работает для небольших встроенных систем, которые работают в нескольких килобайтах с агрессивным управлением батареями, вплоть до монстров 100К или более линий источников, которые работают постоянно. Если вы используете «встроенный» в большой ОС, такой как Windows CE или около того, то все вышеперечисленное совершенно несущественно. Но это не РЕАЛЬНОЕ встроенное программирование, во всяком случае.

quickly_now
источник
2
Большинство аппаратных периферийных устройств вы не можете протестировать через UART, потому что довольно часто вы в основном заинтересованы в характеристиках синхронизации. Если вы хотите проверить частоту дискретизации АЦП, рабочий цикл ШИМ, поведение некоторого другого последовательного периферийного устройства (SPI, CAN и т. Д.) Или просто время выполнения какой-либо части вашей программы, то вы не сможете сделать это с помощью UART. Любое серьезное тестирование встроенного программного обеспечения включает осциллограф - вы не можете программировать встроенные системы без него.
1
Ах да, абсолютно. Я просто забыл упомянуть об этом. Но как только вы запустите UART и начнете работать, его очень легко будет устанавливать тесты или тесты (о чем речь шла), стимулировать вещи, позволять вводить данные пользователем, получать результаты и дружелюбно отображать результаты. Это + ваш CRO делает жизнь очень легкой.
quick_now
2

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

Суть C в том, что он на самом деле способен охватывать платформы в широком диапазоне API и аппаратной производительности (и их отсутствия). MacVim работал быстро на машинах с более чем в 1000 раз меньшей производительностью памяти и процессора, чем у типичного смартфона сегодня. Может ли ваш код на Ruby? Это одна из причин, по которой это может выглядеть проще, чем выбранные вами зрелые C-примеры.

hotpaw2
источник
2

Я нахожусь в обратном положении: большую часть последних 9 лет я работал программистом на C и недавно работал над некоторыми интерфейсами Ruby on Rails.

В C я работаю в основном с пользовательскими системами среднего размера для управления автоматизированными складами (типичная стоимость от нескольких сотен тысяч фунтов до нескольких миллионов). Примером функциональности является пользовательская база данных в памяти, взаимодействующая с оборудованием с некоторыми короткими требованиями времени отклика и более высоким уровнем управления рабочим процессом хранилища.

Прежде всего, я могу сказать, что мы не делаем TDD. Я несколько раз пытался ввести модульные тесты, но в Си это больше проблем, чем стоит - по крайней мере, при разработке программного обеспечения на заказ. Но я бы сказал, что TDD гораздо менее необходим в C, чем Ruby. Главным образом, это только потому, что C компилируется, и если он компилируется без предупреждений, вы уже выполнили довольно похожий объем тестирования с автоматически генерируемыми тестами скаффолдинга rspec в Rails. Рубин без юнит-тестов невозможен.

Но я бы сказал, что C не должен быть таким сложным, как это делают некоторые люди. Большая часть стандартной библиотеки C - это беспорядок непонятных имен функций, и многие программы на C следуют этому соглашению. Я рад сообщить, что у нас этого нет, и на самом деле у нас есть много оберток для стандартных функций библиотеки (ST_Copy вместо strncpy, ST_PatternMatch вместо regcomp / regexec, CHARSET_Convert вместо iconv_open / iconv / iconv_close и т. Д.). Наш внутренний код на C читается лучше, чем большинство других вещей, которые я читал.

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

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

Одна очень интересная вещь, которую вы, возможно, захотите просмотреть, это исходный код C для Ruby. В документации по Ruby API (http://www.ruby-doc.org/core-1.9.3/) вы можете щелкнуть и посмотреть исходный код для различных методов. Интересно то, что этот код выглядит довольно красиво и элегантно - он не выглядит таким сложным, как вы можете себе представить.

asc99c
источник
« ... вы также можете использовать функции более высокого уровня C ... », как есть? ;-)
алк
Я имею в виду более высокий уровень, чем манипулирование битами и указатель на волшебство указателя, которое вы обычно видите в коде типа драйвера устройства! И если вы не беспокоитесь о накладных расходах пары вызовов функций, вы можете сделать код на C, который действительно выглядит достаточно высоким.
asc99c
« ... вы можете сделать код на C, который действительно выглядит достаточно высоким уровнем. » Абсолютно, я полностью согласен с этим. Но хотя « ... функции более высокого уровня ... » не принадлежат С, но у вас в голове, не так ли?
алк
2

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

Пол Натан
источник
2

Там нет причин, почему вы не можете. Проблема в том, что не может быть хороших готовых платформ для модульного тестирования, как в других типах разработки. Ничего страшного. Это просто означает, что вы должны использовать «тестируемый» подход к тестированию.

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

Если вы сталкиваетесь с сопротивлением использованию слова «TDD», назовите его «DVT» (тест проверки проекта), что сделает EE более удобным для этой идеи.

Angelo
источник
0

Можно ли вообще использовать TDD на встроенных устройствах или при разработке драйверов или таких вещей, как собственный загрузчик и т. Д.?

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

Поэтому я решил интегрировать функции их загрузчика в наши. Поскольку это был коммерческий код, я должен был убедиться, что все работает как положено. Поэтому я изменил QEMU, чтобы эмулировать IP-блоки этого ЦП (не все, только те, которые касаются загрузчика), и добавил код в QEMU для «printf» всех операций чтения / записи в регистры, управляющие такими вещами, как контроллер PLL, UART, SRAM и скоро. Затем я обновил наш загрузчик для поддержки этого процессора, и после этого сравнил выходные данные, которые дают наш загрузчик и их на эмуляторе, это помогает мне обнаружить несколько ошибок. Он был написан частично на ассемблере ARM, частично на C. Также после этого модифицированный QEMU помог мне поймать одну ошибку, которую я не смог отловить с помощью JTAG и реального процессора ARM.

Так что даже с C и ассемблером вы можете использовать тесты.

Евгений
источник
-2

Да, это можно сделать TDD на встроенном программном обеспечении. Люди, говорящие, что это невозможно, не имеет отношения к делу или не применимы, не правы. Существует серьезная ценность, которую можно получить от встроенного TDD, как и с любым программным обеспечением.

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

Когда вы делаете TDD, вы будете создавать и запускать множество тестов. Вам нужно программное обеспечение, чтобы помочь вам сделать это. Вам нужен тестовый фреймворк, позволяющий быстро и легко сделать это с автоматическим обнаружением тестов и генерацией макетов.

Лучший вариант для C прямо сейчас - Ceedling. Вот пост о том, что я написал об этом:

http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling

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

Чёрно
источник
Ожидается, что ответы будут стоять самостоятельно. На Stack Exchange («прочитайте статью или ознакомьтесь с Ceedling») вынуждают читателей обращаться к внешнему ресурсу, чтобы узнать о веществе . Рассмотрите возможность редактирования, чтобы оно соответствовало нормам качества сайта
gnat
Есть ли у Ceedling какие-либо механизмы для поддержки асинхронных событий? Одним из наиболее сложных аспектов встроенных приложений реального времени является то, что они имеют дело с получением входных данных от очень сложных систем, которые сами по себе трудно моделировать ...
Джей Элстон,
@Jay У этого нет ничего, чтобы поддержать это. Однако я успешно тестировал подобные вещи с помощью насмешек и создавая архитектуру для их поддержки. Например, недавно я работал над проектом, в котором события, управляемые прерываниями, помещались в очередь, а затем использовались в конечном автомате «обработчик событий». По сути, это была просто функция, которая вызывалась всякий раз, когда происходило событие. При тестировании этой функции я мог высмеивать вызов функции, который вытягивал события из очереди, и, таким образом, мог имитировать любое событие, происходящее в системе. Тест-драйв помогает и здесь.
Черно