Pull to refresh

Comments 16

Я как-то просматривал эту книгу. Она сильная, но там по большей части методология тестирования приведена — например, паттерны и стратегии, тестирование легаси кода. Я в статье постарался сделать упор на реализацию на практике. К тому же в данной книге, насколько мне известно, примеры приведены на Unity, что в какой-то мере закрывает лишь вопрос с unit тестированием.
P.S. Кстати говоря, Unity синтаксически хуже чем googletest, как минимум потому что в нем приходится проделывать лишние действия для регистрации тест кейзов.
Недавно начал её читать. В первой половине примеры приведены на Unity, во второй на Cpputest. Автор книги — один из разработчиков второго фреймворка. В нём больше возможностей, чем в Unity.
TDD объясняется для полных новичков, но в то же время объясняется, как его применять в embedded.
А как запускаются тесты в вашем фреймворке для unit-тестирования?
И кстати, вы этим фреймворком поделитесь или просто похвастаться хотели? :)
Внутри макроса TEST_CASE происходит регистация функции, которая будет вызвана при запуске теста:
#define __TEST_CASE_NM(_description, test_case_nm, run_nm) \
	static void run_nm(void);                            \
	static const struct test_case test_case_nm = {       \
		/* .run         = */ run_nm,                     \
		/* .description = */ _description,               \
		/* .location    = */ LOCATION_INIT,              \
	};                                                   \
	ARRAY_SPREAD_ADD(__TEST_CASES_ARRAY, &test_case_nm); \
	static void run_nm(void)

Структуры struct test_case попадают в некий глобальный массив, а далее все эти функции run_nm будут вызваны при старте системы одна за одной:
static const struct __test_assertion_point *test_run(test_case_run_t run) {
	struct test_run_context ctx;
	int caught;

	current = &ctx;
	test_emit_buffer_init(&current->emitting, emit_buffer, EMIT_BUFFER_SZ);
	if (!(caught = setjmp(ctx.before_run))) {
		run();
	}
	current = NULL;

	return (const struct __test_assertion_point *) caught;
}


Фреймворк на данный момент в составе проекта (кстати, проект открытый). Но в будущем мы планируем его отделить.
Я спрашиваю потому, что сам читал книжку из комментария выше и там как основной минус Unity как раз приводилась его невозможность регистрировать новые тесты автоматически.
И мне, собственно, казалось, что на чистом С этого сделать и нельзя. Не могли бы вы рассказать поподробнее?

В частности, поэтому сам я использую cppUtest, который тоже в той книжке упоминался, в симуляторе среды Keil. На мой взгляд он не лишен проблем, в частности, ему нужно море динамической памяти и вообще для embedded он толстоват.
Да, средствами чистого Си такую регистрацию тестов сделать не возможно. У нас это реализовано при помощи gcc расширения __attribute__ ((section(«name»))). То есть все структуры struct test_case из моего комментария выше упорядочиваются в отдельных линкер секциях.
Часть кода, которая реализует добавление в такой «массив»:
/* The relative placement of sections within a particular array is controlled
 * by the value of order_tag argument. */
#define __ARRAY_SPREAD_SECTION(array_nm, order_tag) \
	".array_spread." #array_nm order_tag ".rodata,\"a\",%progbits;#"

/* Every array entry, group of entries or marker symbols are backed by an
 * individual array (empty for markers) defined as follows. */
#define __ARRAY_SPREAD_ENTRY_DEF(type, array_nm, entry_nm, section_order_tag) \
	type volatile const entry_nm[] __attribute__ ((used,                  \
			section(__ARRAY_SPREAD_SECTION(array_nm, section_order_tag)), \
			aligned(__alignof__(array_nm[0]))))

То есть под каждый массив создается секция ".array.spread__array_nm*" и в нее помещаются элементы (section(__ARRAY_SPREAD_SECTION(array_nm, section_order_tag)) в макросе __ARRAY_SPREAD_ENTRY_DEF)
Офигеть. Могущественно!
Как ни странно, у компилятора armcc (родного для keil) есть атрибут с точно таким же именем (и, вероятно, действием).
Здорово!
Да мы в Си делаем аналог секции .ctor в плюсах, для этого мы используем линкер скрипты, то есть помещая адреса функций в какую то секцию. Затем мы можем пробежаться по ней, ведь адреса начала и конца секции мы знаем, и вызвать данные функции. Мы используем __attribute__ ((section(«name»))) для gcc, для keil думаю есть аналоги.
Идея была обойтись без динамической памяти, переложив работу на линкер, вроде у нас получилось.
Тест на ntpdate некорректен.
Во-первых, никак не учтена вероятность того, что за время исполнения теста может измениться секунда.
Во-вторых, нет проверки того, что время было выставлено правильно именно из-за вызова ntpdate — как минимум, нужна проверка на то, что время хоста и девайса не совпадают до вызова, а возможно и сброс времени девайса в начале.
Да, Вы правы, тест не совсем корректный. Но:
1. «date -u --rfc-3339=date» выдает только дату без времени, то есть проблемы с секундами не возникает.
2. Соглсен, по хорошему нужно сбрасывать время на целевой платформе макросом TEST_SETUP_TARGET, в котором вызвать команду «date» c нужными параметрами. Но кроме как с помощью ntpdate точное время в нашей системе никак не выставить, только если эта команда не вызывается в стартовом скрипте. Таким образом, если считать, что стартовый скрипт заранее подготовлен и ntpdate в нем не вызывается, то тест корректен (в таком предположении)
Тоже интересовался данной тематикой, только условия были еще жестче, не было операционной системы и памяти сильно не хватало, потому приходилось «отключать» часть функционала что бы тесты влазили в память. Мне очень помогла вот эта книга Test Driven Development for Embedded C, ссылку на нее привели в первом комментарии. В книге все крутится вокруг того, что нужно разделять логику приложения от аппаратно зависимого кода, тогда становится возможным unit-тестирование логики не на целевой платформе, а на рабочем PC, что сильно упрощает задачу.
Ну на счет жесче я бы поспорил.
Дело в том, что Embox о котором идет речь в статье, изначально не был ОС, а был вспомогательной утилитой для разработки и отладки железа, в частности FPGA (в ПЛИС стоял процессор Leon). Тесты применялись в том числе для потактовой симуляции, а это очень медленно. То есть скорость старта теста должна была быть очень высокой. Поэтому нам пришлось делать систему сборки которая бы включала только требуемые модули и ничего лишнего.
Уже потом проект развился в ОС поскольку оказалось, что когда отлаживаешь оборудование появляются драйвера, ну а полноценный тест сетевухи можно сделать только имея сетевой стек ну и так далее. При этом свойство включения только требуемых модулей сохранилось и Embox запускаемся на контроллере EFM32ZG222F32 Zero Gecko c 16кБ ПЗУ и 4 кБ ОЗУ
Ну я не знал как развивался проект, потому и сделал такой вывод.
Полностью согласен, что логику нужно отделять от аппаратно зависимого кода. Это помогает, но не является решением всех проблем. Но, как выше писал Антон, сделать тест сетевухи без сетевого стека не получится, также не получится сделать его и на PC. То есть получается, что нам все равно нужно запускать тесты либо на железке, либо на qemu. Ну а раз мы запускаем тесты на таймер, mmu, сетевуху, то почему бы не запустить тесты на список тоже на qemu? С интеграционным тестированием еще сложней. Если список можно протестировать и на хосте, так это всего лишь алгоритм, то исполнение интеграционных тестов может зависить глубоко внутри от планировщика, файловой системы, да и от частоты процессора в конце концов. То есть все равно исполнения на PC и на целевой платформе могут различаться.
Про тестирование Embedded — когда мне пришлось тестировать плату я поступил схожим образом.
Часть тестов выполняются внутри устройства при старте (список модулей я тоже делал через __attribute__ ((section(«name»)))) — здесь я провожу тестирование основных «железных» компонентов, необходимых для запуска платы. Т.к. здесь тестируются жизненно важные компоненты (работоспособность тактового генератора, например), то эти тесты никак отдельно не выделяются, а встроены в код инициализации платы.
А тесты качества и интеграционные выполняются «снаружи», подачей команд через COM-порт. А в качестве framework я выбрал pyunit, выполняющийся на хосте. Вообще автоматизация тестирования на Python мне приглянулась из-за своей интерактивности. Я могу в интерпретаторе писать какой-то код, посылать команды на целевое устройство и сразу же видеть отклик в логах. Мне не надо для этого пересобирать код.
Sign up to leave a comment.