Pull to refresh

Автоматический запуск unit-тестов для C

Reading time3 min
Views11K
Я использую C для научных расчётов. А в этом случае, стоит крепко подумать, а надо ли вам вообще C?

Язык C нужен только в случае, если в ваших расчётах очень критична производительность или критичен доступ к железу. Во всех остальных случаях, я очень рекомендую высокоуровневые языки типа Ruby или Python (почти что стандарт языка для научных расчётов, очень много научных пакетов разного толка от математики до биологии) или, что лучше, сразу научные пакеты типа Sage (надстройка над python с возможностью использования символьных вычислений и очень много чего ещё, а также с возможностью подключения других математических пакетов, в случае, если возможностей Sage не хватает, прямо внутри Sage программы; о Sage, кстати, писали на хабре).

Для Python же, если производительность важна и вы не готовы вылизывать C-код до совершенства, есть Cython (авторы которого являются также авторами Sage), который компилирует почти питоновский код в C-код, достигая очень высоких показателей производительности.

Так что на этом этапе призываю вас ещё раз: подумайте, прежде чем использовать C для научных или иных расчётов! Иначе, поехали!

Итак, вы всё-таки решили использовать C. В этом случае надо организовать рабочую тестовую среду, а именно:


Потому, что какой это научный расчёт, если код, его осуществляющий, не протестирован должным образом? Ну, это просто помимо того, что с тестированием разработка в протекает сильно быстрее. Но, чтобы скорость действительно была высока, нужен автоматический запуск тестов.

На тему выбора фреймворка для модульного тестирования существует мощная ветка на Stackoverflow, пост на хабре, сравнение фреймворков модульного тестирования на страницах википедии и даже rspec для тестирования кода на C и C++ (у меня сходу не завелось).

Из всего этого многообразия самым приятным мне показался Google Testing Framework (о нём тоже писали на хабре).

Единственное, что при работе с ним у меня, как человека не очень опытного в вопросах тестирования языка C, возник ряд проблем (например, как в Ubuntu по-человечески скомпилировать и запустить чёртов тест?!). Этот вопрос, почти нигде не освещается должным образом, кроме «поместите gtest в папку с проектом и следуйте инструкциям из README», что, конечно, далеко не по-человечески, или к инструкциям для старых Ubuntu (старше 11.10), которые не подходят к Oneiric Ocelot.

Итогом всех моих стенаний стал следующий простой текст: установка на Ubuntu 11.10 сводится к sudo apt-get install libgtest-dev (или yaourt -S gtest в ArchLinux). После чего в вашем файле my_app_test.cpp надо прописать нечто вроде:
#include "gtest/gtest.h"

int add(int value1, int value2) {
  return (value1 + value2);
}

TEST (AddTest, PositiveNos) {
  EXPECT_EQ (16, add(8, 8));
}

Где AddTest — имя группы тестов, а PositiveNos — название теста. EXPECT_EQ, как почти очевидно из названия, проверяет значение двух выражений на равенство. Аналог assert 16 == 8+8. Впрочем, ASSERT_TRUE тоже есть в арсенале.

Всё, тест готов. Конечно, в реальной жизни надо делать #include "my_app.h", но в примере мы обойдёмся тем, что есть.

Запуск теста — это выполнение следующей команды g++ my_app_test.cpp -lgtest_main -lgtest -pthread && ./a.out.

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

Проиллюстрирую неуспешный прогон тестов (красный)



И успешный (зеленый)



Тестирование налажено, теперь нам надо наладить автоматический прогон тестов после изменения кода. Иначе весть этот подход с unit-тестами просто не сработает.

Для этого мы будем использовать инструмент watchr (популярный в Ruby среде). Чтобы всё было красиво и выводились симпатичные уведомления, вам необходима установленная библиотека libnotify (чтобы всё было совсем красиво, надо в папку с проектом пихнуть файлы .watchr_images/failed.png и .watchr_images/passed.png).

Ставим ruby (нужен для watchr) и libnotify: sudo apt-get install ruby libnotify4, и после устанавливаем watchr: sudo gem install watchr.

Берём файл watchr отсюда. Приведу текст скрипта:
ENV["WATCHR"] = "1"
system 'clear'

def run(cmd)
  puts(cmd)
  system(cmd)
end

def growl(result)
  message = result ? "OK" : "FAILED"
  growlnotify = `which notify-send`.chomp
  title = "Watchr Test Results"
  image = (result) ? ".watchr_images/passed.png" : ".watchr_images/failed.png"
  options = %("#{title}" "#{message}" -i #{File.expand_path(image)})
  system %(#{growlnotify} #{options} &)
end

watch( '(.+)(\.c|_test\.cpp|\.h)$' ) do |md|
  result = run("g++ #{md[1]}_test.cpp #{md[1]}.c -lgtest_main -lgtest -pthread -o #{md[1]}_test && ./#{md[1]}_test")
  growl result
  puts ("\n\n")
end


Запускается скрипт простой коммандой: watchr watchr.rb.
Tags:
Hubs:
+3
Comments5

Articles