- PVSM.RU - https://www.pvsm.ru -
Всем известна польза юнит-тестирования. Прежде всего, написание тестов одновременно с кодом позволяет раньше выявлять ошибки и не тратить впоследствии время на трудоемкую комплексную отладку. В случае embedded-разработки у юнит-тестирования есть особенности, связанные, во-первых, с тем, что код выполняется где-то глубоко в недрах устройства и взаимодействовать с ним довольно сложно, и, во-вторых, код сильно завязан на целевое железо.
Если в проекте есть фрагменты, не зависящие от аппаратуры и при этом реализующие достаточно сложную логику, для них применение модульных тестов даст наибольшую выгоду. Например, это может быть реализация какого-то протокола передачи данных, различные расчеты или управляющий конечный автомат.
Существует три способа запуска юнит-тестов для встраиваемых платформ:
В этой публикации приведен способ настройки юнит-тестов в среде STM32CubeIDE, основанной на Eclipse и предназначенной для разработки для контроллеров семейства STM32. Язык разработки – С, но сами тесты пишутся на С++. Тесты будут запускаться на хост-машине c Windows с использованием Cygwin. В качестве тестового фреймворка используется Google Test. Результаты будут отображаться в специальном окне плагина для юнит-тестирования, и их можно будет запустить одной кнопкой из проекта для STM32:

Описанный способ подойдет и для других сред разработки на основе Eclipse, если конечно добрые производители не слишком сильно их урезали в угоду удобству разработчиков. Также этот метод будет работать и c CubeIDE под Linux, при этом не потребуется возиться с Cygwin.
Устанавливаем Cygwin, версия x86. В инсталляторе выбираем дополнительные пакеты: gcc-core, g++, binutils, automake, autoconf, cmake, libtool, gdb, make. Можно ставить последние стабильные версии пакетов.

Также нужно прописать переменные среды:
PATH: …;C:<path_to_Cygwin>Cygwinbin; C:<path_to_Cygwin>Cygwinlib
classpath: C:<path_to_Cygwin>Cygwinlib
Среда устанавливается как обычно. Желательно ставить CubeIDE после Cygwin, потому что в этом случае Cube сам подхватит существующий Cygwin тулчейн.
Сначала создадим проект С++ для x86 Cygwin платформы. Он нам понадобится, чтобы, во-первых, проверить работоспособность тулчейна, а во-вторых, мы будем использовать его как «донора» конфигурации сборки для основного проекта.
Выбираем File > New > C/C++ Project. Выбираем C++ Managed Build. Создаем проект типа hello world для тулчейна Cygwin GCC:

Далее нужно будет выбрать, какие конфигурации сборки создавать. Достаточно только Debug.
Теперь можно проверить, что проект собирается, выбрав Project > Build All. Также желательно проверить и отладку под Cygwin, запустив Run > Debug As > Local C/C++ Application. Приложение выведет «Hello world» в консоль внутри CubeIDE.
Для того, чтобы отладчик мог показывать исполняемые строки в файлах исходного кода, нужно настроить отображение путей. В окне Window > Preferences во вкладке С/С++ > Debug нужно выбрать Source Lookup Path и добавить новое отображение: Add > Path Mapping. В окне нужно назвать как-нибудь новое отображение и добавить строчки для дисков, которые есть в системе:


Для красивого запуска тестов нам также понадобится плагин для Eclipse с поддержкой юнит-тестов для С++. Он ставится прямо из STM32CubeIDE: меню Help > Install New Software, далее выбрать репозиторий Eclipse Repository и установить плагин С/С++ Unit Testing Support.

Исходный код библиотеки можно взять по ссылке: https://github.com/google/googletest/tree/release-1.8.1 [1]
Распаковываем исходники, заходим в директорию googletest-release-1.8.1 с помощью Cygwin terminal, и запускаем:
cmake .
make
После успешной сборки файл статической библиотеки будет лежать в ./googlemock/lib/libgtest.a, а заголовочные файлы будут находиться в каталоге ./googletest/include/gtest/. Их нужно будет скопировать в наш проект (или прописать путь к этим файлам в настройках проекта).
Проект для отладочной платы STM32L476G-DISCO. Пример будет не слишком изощренным – на плате есть два светодиода, пусть показывают двоичный счетчик от 00 до 11. Реализуем для счетчика отдельный модуль, описанный в паре .h и .c файлов, и напишем для него тест.
Проект можно создавать как обычно, с помощью конфигуратора Cube, главное убедиться, что выводы PB2 и PE8 настроены как цифровые выходы. При создании проекта лучше будет указать тип – С++, это понадобится для компиляции тестов (основной код будет по-прежнему компилироваться С-компилятором). Сконвертировать проект из C можно будет и позже, нажав на название проекта ПКМ и выбрав «Convert to C++».
Для компиляции под МК и для тестов нам понадобятся две разные конфигурации сборки. В этих конфигурациях будут собираться разные наборы файлов – в основную попадут модули для работы с железом и тестируемые модули, а в тестовую – те же тестируемые модули и файлы тестов. Поэтому создадим в корне проекта разные каталоги – Application c кодом приложения для МК (можно просто переименовать директорию Src, которую создал Cube), Common для модулей, не зависящих от железа (которые мы будем тестировать) и Tests для тестов. Директории можно исключать из сборки, кликнув ПКМ по их названию, меню Resource Configuration > Exclude from build.
Добавим в каталог Common наш модуль счетчика:
(led_counter.h):
#ifndef LED_COUNTER_H_
#define LED_COUNTER_H_
#include <stdint.h>
void Led_Counter_Init();
uint8_t Led_Counter_Get_Next();
#endif /* LED_COUNTER_H_ */
led_counter.cpp:
#include "led_counter.h"
static uint8_t led_cnt_state = 0;
void Led_Counter_Init()
{
led_cnt_state = 0;
}
uint8_t Led_Counter_Get_Next()
{
if(++led_cnt_state > 3)
led_cnt_state = 0;
return led_cnt_state;
}
Директории Common и Tests нужно добавить в путь поиска include-файлов: свойства проекта (Properties) > С/С++ General > Paths and Symbols > Includes.
Добавим в main работу со светодиодами
main.c:
…
/* USER CODE BEGIN Includes */
#include "led_counter.h"
/* USER CODE END Includes */
…
int main(void)
{
…
/* USER CODE BEGIN WHILE */
Led_Counter_Init();
uint8_t led_state = 0;
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
led_state = Led_Counter_Get_Next();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, led_state & (1<<0));
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, led_state & (1<<1));
HAL_Delay(500);
}
/* USER CODE END 3 */
…
}
Проект должен компилироваться и запускаться, а светодиоды – мигать.
Теперь то, ради чего все затевалось.
Создадим новую конфигурацию сборки через свойства проекта – Properties > C/C++ Build > Settings > Manage Configurations. CubeIDE просто так не даст создать конфигурацию для сборки под Cygwin, поэтому скопируем ее из проекта, который мы создали ранее:

Теперь нужно переключиться на эту конфигурацию и настроить пути к файлам исходников и заголовочным файлам. В свойствах проекта во вкладке Paths and Symbols прописываем (при добавлении записи лучше ставить галку в поле «add to all languages»):
Далее копируем в проект библиотеку gtest – файл .a в директорию Tests/Lib, а заголовочные файлы в папке gtest – в папку Tests/Inc. В папке Tests создаем новый файл main.cpp, в котором будут запускаться тесты. Его содержимое стандартное:
main.cpp:
/*
* Unit tests main file
*/
#include "gtest/gtest.h"
int main(int argc, char *argv[])
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Также для проверки работы сетапа создадим один тест, который будет проверять, что в нашем окружении размер указателя 32 бита (мы хотим убедиться, что он такой же, как и на микроконтроллере, для этого мы ставили 32-битный Cygwin).
Создаем такой файл теста test_platform.cpp:
#include "gtest/gtest.h"
TEST(PlatformTest, TestPointerSize)
{
//Check pointer size is 32 bit
ASSERT_EQ(sizeof(void*)*8, 32U);
}
Теперь, если проект запустить как обычное С++ Application, в отладочном выводе будет сообщение от Google Test о том, что все тесты пройдены.
Структура проекта должна иметь примерно такой вид:

Теперь напишем тесты для нашего модуля светодиодного счетчика. Файлы тестов можно расположить в папке Tests:
#include "gtest/gtest.h"
extern "C" {
#include "led_counter.h"
}
// Test fixture
class LedCounterTest: public ::testing::Test
{
protected:
void SetUp()
{
Led_Counter_Init();
}
};
// Check initial value
TEST_F(LedCounterTest, TestInitialValue)
{
Led_Counter_Init();
ASSERT_EQ(Led_Counter_Get_Next(), 1);
}
// Check how value is incremented
TEST_F(LedCounterTest, TestIncrementValue)
{
Led_Counter_Init();
unsigned int val = Led_Counter_Get_Next();
for(int i=0;i<1;i++)
{
ASSERT_EQ(Led_Counter_Get_Next(), ++val);
}
}
// Check how value return to 0 after 3
TEST_F(LedCounterTest, TestZeroCrossing)
{
Led_Counter_Init();
for(int i=0;i<3;i++)
{
Led_Counter_Get_Next();
}
ASSERT_EQ(Led_Counter_Get_Next(), 0);
}
Чтобы результаты тестов отображались в красивом окошке, нужно создать новую конфигурацию запуска в меню Run > Debug Configurations. Установленный плагин позволяет создавать конфигурации типа C/C++ Unit. Создадим ее, назовем Run Tests, выберем используемую конфигурацию сборки «Test» и снимем галку «stop on startup at» на вкладке Debugger. После этого конфигурацию можно запустить.
Для появления окна с результатами его нужно выбрать в Window > Show View > Other > C/C++ > C/C++ Unit.

Готово! Теперь проект можно компилировать и запускать под целевой МК как обычно. Когда нужно будет запустить локальные тесты, при запуске конфигурации Run Tests проект автоматически будет пересобран под x86, среда выполнит тесты и покажет результат.
Автор: amvasil-v
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/stm32/331528
Ссылки в тексте:
[1] https://github.com/google/googletest/tree/release-1.8.1: https://github.com/google/googletest/tree/release-1.8.1
[2] https://uncannier.com/unit-testing-of-embedded-firmware-part-1-software-confucius/: https://uncannier.com/unit-testing-of-embedded-firmware-part-1-software-confucius/
[3] http://blog.atollic.com/why-running-your-embedded-arm-cortex-code-on-a-host-pc-is-a-good-thing: http://blog.atollic.com/why-running-your-embedded-arm-cortex-code-on-a-host-pc-is-a-good-thing
[4] Источник: https://habr.com/ru/post/469367/?utm_source=habrahabr&utm_medium=rss&utm_campaign=469367
Нажмите здесь для печати.