Известно, что макросы в 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 пишут сразу ручками.
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 пишут сразу ручками.
Комментариев нет:
Отправить комментарий