воскресенье, 29 сентября 2013 г.

Аккуратней с макросами

Известно, что макросы в C++ беспощадны к программистам. В силу их природы они не прощают оплошностей. А при самом неудачном стечении обстоятельств макросы могут просто мастерски скрыть баг. Но сегодня не тот день.
Сегодня мы снова убедимся, что, если ты имеешь дело с макросами, да и еще поставляешь их как API, то нужно держать ухо востро.
Вот например. В проекте для тестов используется CppUnit. Библиотека позволяет писать тесты непосредственно с использованием поставляемых типов, но есть и более простой вариант. Макросы могут значительно упростить использование CppUnit.
Итак есть тест, который проверяет какое-то условие. Конкретно, нас интересует строка с самой проверкой. Выглядит примерно так:

CPPUNIT_ASSERT_MESSAGE(_T("Assert failed!"), Check());

Чтобы было понятней, во что эта запись развернется, обратите внимание на определение макроса

#define CPPUNIT_ASSERT_MESSAGE(message,condition) \
    ( CPPUNIT_NS::Asserter::failIf( !(condition), \
          CPPUNIT_NS::Message( "assertion failed", \
                               "Expression: " #condition, message ), \ CPPUNIT_SOURCELINE() ) )

Именно с этой проверкой все будет нормально, но я столкнулся с другим случаем

CPPUNIT_ASSERT_MESSAGE(GetError(), Check());

В тестируемом функционале предусмотрена специальная процедура для получения сообщения ошибки. К сожалению, при падении теста, сообщение об ошибке выведено не будет. Всему виной использование этой процедуры и порядок вычисления параметров в failif вызове. Дело в том, что при развороте кода вызов GetError в порядке передачи параметров оказывается до вычисления проверки. Почему так - первое, что приходит на ум, во всем виновато соглашение о вызове. __stdcall и __cdecl используют передачу параметров справа налево. Похоже, CppUnit был спроектирован для использования в C++ Builder :-). Вернемся к проблеме. Решение тривиальное - вычисление условия до вызова failif

#define CPPUNIT_ASSERT_MESSAGE(message,condition) \
{ bool bCppUnit_dummy_condition_check_inverse = !(condition); \
( CPPUNIT_NS::Asserter::failIf( bCppUnit_dummy_condition_check_inverse, \
                               CPPUNIT_NS::Message( "assertion failed", \
                                                    "Expression: " \
                                                    #condition, \
                                                    message ), \
           CPPUNIT_SOURCELINE() ) );}


Прибавился еще один плюс - в конце использования макроса можно не ставить точку с запятой.В сухом остатке обозначим несколько открытых вопросов:
1. Зависит ли порядок вычисления параметров от порядка его передачи? К сожалению, в стандарт не смотрел. Не дотянулся :-)
2. Правильно ли использовать библиотеку, предназначенную для модульного тестирования, для тестирования API? Ведь подобных проблем я почему-то в интернете не обнаружил. Все по всей видимости error message пишут сразу ручками.

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

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