...

вторник, 2 декабря 2014 г.

[Из песочницы] Юнит тесты на Си — нет ничего проще

Прочитав статью «Тестирование встроенных систем» и комментарии к ней я был несколько поражен тем фактом, что многие хабровчане знакомы с книгой «Test Driven Development for Embedded C (Pragmatic Programmers)» и framework-ом Unity, но не используют весь арсенал средств, которые предлагают ребята из throwtheswitch.org.

Хочу кратко поделится опытом использования этих самых средств.



О себе




Так получилось, что я нарабатывал свой опыт в программировании встраиваемых систем через тесты (Unit, Integration, System, Stress). За три года мне посчастливилось пройти путь от Junior'a и написания тестов, покрывающих код других специалистов, до Senior'a с опытом разработки систем с использованием TDD методологии.

Обещанное




Упомянутый выше framework Unity очень прост и удобен в использовании. Но это всего лишь вершина айсберга. На странице throwtheswitch.org есть следующие инструменты.

CMock — инструмент позволяющий автоматически генерировать Си-код mock-ов для Ваших тестов. Написан на Ruby. Утверждаю, как человек, который на протяжении трех лет «генерировал» mock-и руками — это просто подарок для Си-разработчика. Но использовать его автономно без следующего инструмента, на мой взгляд, не рационально.


Ceedling — это целая билд-система, как утверждают сами авторы. Но по сути — это все, что Вам нужно для работы. Данный пакет содержит в себе все необходимое: Unity («тест-раннеры» и «чекалки» значений), CMock (генератор моков) и поддержку командной строки через ruby make.


Other — под этим странным заголовком находится очень, полезный, на мой взгляд инструмент — CException. Невероятно маленькая библиотека для Си позволяющая получить некое подобие исключений. Но дезинформировать не буду. В проектах использовать не довелось.


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


Прежде всего, Ceedling должен быть корректно установлен и проверен на работоспособность как указано тут.


После установки создаем папку и тестовое окружение проекта командой:



ceedling new MyNewProject




В результате будет создана папка MyNewProject внутри которой будут сгенерированы следующие папки и файлы:


  • build — сюда будут помещаться все артефакты при сборке и прогоне тестов

  • src- это место для нашего «боевого» кода, который подлежит тестированию

  • test — будут лежать все наши тесты

  • vendor — собственно сам framework, с документацией и плагинами

  • project.yml — конфигурационный файл тестового проекта. Позволяет делать хороший тюнинг, но это с опытом

  • rakefile.rb — знатоки Ruby точно знают зачем этот файл. Я же, просто знаю что он необходим. Простите мою некомпетентность в данном вопросе


Пора писать первый тест.


Поместим в папку test файл test_calc.c следующего содержания:



#include "unity.h"
#include "calc.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_add( void )
{
int result = 0;
result = calc_add(2,2);
TEST_ASSERT_EQUAL_INT( result, 4 );
}




Запускаем тест командой:

rake test:test_calc.c




Результат ожидаемый. Тест есть, кода нет. Проект не может быть собран.

Добавляем код.

В папку src помещаем два файла:


«calc.h»



#ifndef CALC_H
#define CALC_H
int calc_add(int a, int b);
#endif




«calc.c»

#include "calc.h"
int calc_add(int a, int b)
{
return a + b;
}




Повторяем сборку и попытку прогнать тест:

rake test:test_calc.c




Если все сделано правильно, то в консоли должны быть результаты теста:

Test 'test_calc.c'
------------------
Compiling test_calc_runner.c...
Compiling test_calc.c...
Compiling calc.c...
Compiling unity.c...
Compiling cmock.c...
Linking test_calc.out...
Running test_calc.out...

-------------------------
OVERALL UNIT TEST SUMMARY
-------------------------
TESTED: 1
PASSED: 1
FAILED: 0
IGNORED: 0




Этот короткий пример показывает, что test-runner был сгенерирован и добавлен в сборку автоматически. Его код можно найти в папке build/test/runners.

Попробуем усложнить задачу и предположим, что наш «боевой» файл должен уметь считать только при определенном условии, проверка которого осуществляется в другом программном модуле (например, rules.c). Модифицируем код, для иллюстрации:


«calc.c»



#include "calc.h"
#include "rules.h"
int calc_add(int a, int b)
{
if (rules_is_addition_allowed())
{
return a + b;
}
return 0;
}




Добавим еще один файл в папку src:

«rules.h»



#ifndef RULES_H
#define RULES_H
int rules_is_addition_allowed(void);
#endif




Попытка запустить тест будет неудачной, так как нет определения для функции rules_is_addition_allowed().

Самое время воспользоваться CMock.

Изменим тест следующим образом:



#include "unity.h"
#include "calc.h"
#include "mock_rules.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_add( void )
{
int result = 0;
rules_is_addition_allowed_ExpectAndReturn(1);
result = calc_add(2,2);
TEST_ASSERT_EQUAL_INT( result, 4 );
}
void test_add_off_nominal( void )
{
int result = 0;
rules_is_addition_allowed_ExpectAndReturn(0);
result = calc_add(2,2);
TEST_ASSERT_EQUAL_INT( result, 0 );
}




Таким образом, мы получили автоматически сгенерированный mock одним лишь указанием "#include «mock_rules.h». Исходный код данного файла можно найти в директории build/test/mocks. Его изучение даст хорошее представление о том, каким образом можно менять поведение подменяемого модуля.

Оговорочки




1. Я использую данный framework только для тестирования кода на PC. Это диктует определенные правила к архитектуре разрабатываемого ПО. Прогонять юнит тесты на реальном железе смысла не вижу. HAL — он либо работает либо нет и тестируется мануально (мое видение ситуации);

2. Я не использую данный framework для тестирования нескольких потоков. Потокобезопастность данного инструмента мной не исследовалась;

3. Данная статья не учит как правильно писать код и/или тесты, а всего-лишь дает краткое представление об упомянутых выше инструментах разработки.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий