19 апр. 2007 г.

CppUnit для ленивых

Приспичило мне однажды овладеть юнит-тестированием кода C++. Так приспичило, что сил никаких нет - ни есть не могу, ни спать. Прошвырнувшись по ближайшим форумам, я понял, что не один такой, и что страждущих немало, но все они в каких-то непонятках. Казалось бы - чего тут сложного? Ан нет, каждый свое защищает: линуксоиды все как один говорят, что "у них все есть, но виндузятникам этого не понять", виндузятники раскидывают по сторонам слюни, описывая некую приблуду к Visual Studio, а все остальные тихо сидят и посмеиваются. В общем, черт их разберет, кто прав. Единственное название, упоминающееся более-менее часто - это CppUnit.
Вспомнив, как классно было работать с NUnit, я глядел на прогресс-бар скачивания дистрибутива, счастливо улыбаясь и предвкушая легкую жизнь. Но суровая реальность оказалась жестокой - ничего напоминающего мои прошлые опыты с инструментами тестирования я там не нашел.
Началось все с того, что меня заставили компилировать библиотеки самостоятельно. Но то ли авторы не удосужились проверить свое творение на последних версиях Студии, то ли Студия мне попалась кривая, но компиляция у меня шла, спотыкаясь на каждом шагу и ругаясь на чем свет стоит. В результате я все же смог получить нужные либы, правда, только для режима отладки.
Следующим шагом было прочтение readme.txt, который по совместительству является этаким сборником рецептов. Следуя инструкциям, я пытался заставить свои тесты запускаться с помощью GUI-утилиты TestPlugInRunnerd.exe, но потратив на это несколько часов, убедил себя, что GUI - это для маленьких, а настоящие дядьки работают в консоли, на чем и успокоился.
Запустить тесты в консоли оказалось не в пример проще. Ниже приведу последовательность действий, актуальную для Visual Studio 8 (2005).
1) Добавляем каталог include установленного CppUnit в стандартные пути (Tools - Options - Projects and Solutions - VC++ Directories).
2) Добавляем в проект ссылку на cppunitd.lib (в свойствах проекта: Configuration Properties - Linker - Input).
3) Пишем тест примерно такого вида:
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>

class MyTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(MyTest);
CPPUNIT_TEST(myTest1);
CPPUNIT_TEST(myTest2);
CPPUNIT_TEST(myTest3);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
// инициализация
}
void tearDown() {
// деинициализация
}
void myTest1() {
CPPUNIT_ASSERT(true);
}
void myTest2() {
CPPUNIT_ASSERT(true);
}
void myTest3() {
CPPUNIT_ASSERT(false &&amp; true);
}
};

4) Пишем функцию main() примерно так:
#include <cppunit/ui/text/TestRunner.h>
#include "MyTest.h"

CPPUNIT_TEST_SUITE_REGISTRATION(MyTest);

int main()
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
runner.run();
return 0;
}

5) Запускаем и радуемся. Если появляются новые тесты, то для каждого из них (я имею в виду для каждого Test Suite) нужно добавить строку вида CPPUNIT_TEST_SUITE_REGISTRATION(...) в файл с функцией main(). После запуска программа выдаст на консоль информацию о выполненных тестах, и если какие-то из них не прошли, укажет причину ошибок и соответствующие номера строк в исходных файлах.

Вот так, дешево и сердито. Если кто знает, как делать то же самое, но с помощью GUI-утилиты, буду рад у вас сонно поучиться.

14 комментариев:

Анонимный комментирует...

Сранно что этот блог никто не читает. Очень даже понравилоль, и помогло с CppUnit. Спасиб...

Flegmatic комментирует...

На здоровье! Постараюсь почаще сюда писать.

Анонимный комментирует...

Ошибочка !!!

int main()
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
runner.run();
return 0;
}

Что должно быть?

Flegmatic комментирует...

Какая ошибочка-то?

Анонимный комментирует...

®istry Что должно быть

Flegmatic комментирует...

Хм, должно быть ®istry, как в статье и написано. Что-то я не понимаю, в чем проблема, уж извините меня...

Анонимный комментирует...

®istry

Первый символ переменной стоит ® код в юникоде U+00AE (буква R в кружочке)
Я написал registry выдал тучу ошибок( vc6.0)
msvcprtd.lib(MSVCP60D.dll) : error LNK2005: "public: __thiscall std::basic_string......(void)" (??1?$basic_strin
g@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ) already defined in test1.obj
видимо чтото чтото DLL

Flegmatic комментирует...

Это ошибка линкера, имя переменной тут не при чем. Вообще, не уверен, что использование в программе символов с кодами больше 127 - хорошая мысль.
Я по-прежнему с трудом понимаю. Вы в своей программе назвали переменную ®istry, и программа перестала работать? Или же Вы в моей статье видите ®istry как ®istry?

Анонимный комментирует...

Я вижу в статье букву R в кружочке ®istry как ®istry.

Flegmatic комментирует...

Вот теперь понял. В статье вместо ®istry на самом деле написан символ амперсанда & и слово registry. Я это вижу именно так (Firefox 2.0.0.6).
Статью исправил, теперь должно отображаться нормально.

Анонимный комментирует...

А это что?
CPPUNIT_ASSERT(false && true);

Анонимный комментирует...

как использовать cppunit для написания модульных тестов в VS2012?

Анонимный комментирует...

"Если появляются новые тесты, то для каждого из них (я имею в виду для каждого Test Suite) нужно добавить строку вида CPPUNIT_TEST_SUITE_REGISTRATION(...) в файл с функцией main()"
Можно делать и так, но если проще, то макрос CPPUNIT_TEST_SUITE_REGISTRATION(MyTest) можно подключить в MyTest.cpp перед определением класса.
И делать так каждый раз при добавлении новых тестов. Таким образом можно избавиться от необходимости каждый раз дописывать main.
Так же отпадает необходимость инклудить новый тест в main файле.

Анонимный комментирует...

g++ -o test -I/usr/include -L/usr/lib/x86_64-linux-gnu/ -lcppunit testCppUnit.cpp
testCppUnit.cpp: In function ‘int main()’:
testCppUnit.cpp:17:23: error: no matching function for call to ‘CppUnit::TextTestRunner::addTest(int*)’
runner.addTest(&test);
^
testCppUnit.cpp:17:23: note: candidate is:
In file included from /usr/include/cppunit/ui/text/TextTestRunner.h:7:0,
from testCppUnit.cpp:2:
/usr/include/cppunit/TestRunner.h:85:16: note: virtual void CppUnit::TestRunner::addTest(CppUnit::Test*)
virtual void addTest( Test *test );
^
/usr/include/cppunit/TestRunner.h:85:16: note: no known conversion for argument 1 from ‘int*’ to ‘CppUnit::Test*’