То, что вы описали, - это низкоуровневая функциональность ввода / вывода. Поскольку fmemopen()
не является на 100% переносимым (от Linux, я думаю, что он скрипит), вам нужно предоставить себе что-то переносимое, что вы пишете достаточно близко, чтобы вы могли использовать свои суррогатные функции (только) при необходимости и использовать нативные функции, когда это возможно. Конечно, вы должны иметь возможность принудительно использовать свои функции даже в своей естественной среде обитания, чтобы вы могли протестировать свой код.
Этот код можно протестировать с известными данными, чтобы убедиться, что вы выбрали все символы во входных потоках и можете их верно вернуть. Если исходные данные находятся в определенном порядке байтов, вы можете убедиться, что ваши «большие» типы & mdash; гипотетически, такие функции, как stream_read-uint2()
, stream_read_uint4()
, stream_read_string()
и т. д. & mdash; все ведут себя соответственно. На этом этапе вам не нужны фактические данные; Вы можете изготовить данные, подходящие для себя и своих испытаний.
Как только вы это сделаете, вам также понадобится написать код для чтения данных с большими типами и обеспечения того, чтобы эти функции более высокого уровня действительно могли точно интерпретировать двоичные данные и вызывать соответствующие действия. Для этого вам, наконец, нужны примеры того, что предоставил формат; вплоть до этого этапа вы, вероятно, могли бы получить данные, которые вы изготовили. Но как только вы читаете реальные файлы, вам нужны примеры тех, с которыми можно работать. Или вам придётся изготовить их из вашего понимания и проверить как можно лучше. Насколько это легко, зависит от того, насколько четко задокументирован двоичный формат.
Одним из ключевых инструментов тестирования и отладки будут канонические функции «дампа», которые могут предоставить вам данные. Схема, которую я использую:
extern void dump_XyzType(FILE *fp, const char *tag, const XyzType *data);
Поток самоочевиден; обычно это stderr
, но, указав его в качестве аргумента, вы можете получить данные в любой открытый файл. tag
включен в напечатанную информацию; он должен быть уникальным, чтобы определить местоположение вызова. Последний аргумент - указатель на тип данных. Вы можете проанализировать и распечатать это. Вам следует воспользоваться возможностью, чтобы подтвердить все возможные проверки, чтобы избежать проблем.
Вы можете расширить интерфейс с помощью , const char *file, int line, const char *func
и договориться о добавлении __FILE__
, __LINE__
и __func__
к вызовам. Я никогда не нуждался в этом, но если бы я это сделал, я бы использовал:
#define DUMP_XyzType(fp, tag, data) \
dump_XyzType(fp, tag, data, __FILE__, __LINE__, __func__)
В качестве примера я имею дело с типом DATETIME, поэтому у меня есть функция
extern void dump_datetime(FILE *fp, const char *tag, const ifx_dtime_t *dp);
Один из тестов, которые я использовал на этой неделе, можно было убедить сбросить значение datetime, и он дал:
DATETIME: Input value -- address 0x7FFF2F27CAF0
Qualifier: 3594 -- type DATETIME YEAR TO SECOND
DECIMAL: +20120913212219 -- address 0x7FFF2F27CAF2
E: +7, S = 1 (+), N = 7, M = 20 12 09 13 21 22 19
Вы можете увидеть или не увидеть значение 2012-09-13 21:22:19
. Интересно, что эта функция сама вызывает другую функцию в семействе dump_decimal()
, чтобы распечатать десятичное значение. Однажды я обновлю печать квалификатора, включив в нее шестнадцатеричную версию, которую намного легче читать (3594 - 0x0E0A, что легко понять тем, кто знает, как 14 цифр (E), начиная с YEAR (второй От 0) до секунды (A), что, конечно, не так очевидно из десятичной версии. Конечно, информация находится в строке типа: DATETIME YEAR TO SECOND. (Десятичный формат несколько непонятен для постороннего, но довольно ясен инсайдеру, который знает, что есть показатель степени (E), знак (S), число (сотенных) цифр (N = 7) и фактические цифры (M = ...). Да, имя decimal
является строго неправильным, так как он использует представление base-100 или сотое.)
Тест не дает такого уровня детализации по умолчанию, но мне просто нужно было запустить его с достаточно высоким уровнем отладки (опцией командной строки). Я бы расценил это как еще одну ценную особенность.
Самый тихий способ выполнения тестов дает:
test.bigintcvasc.......PASS (phases: 4 of 4 run, 4 pass, 0 fail)(tests: 92 run, 89 pass, 3 fail, 3 expected failures)
test.deccvasc..........PASS (phases: 4 of 4 run, 4 pass, 0 fail)(tests: 60 run, 60 pass, 0 fail)
test.decround..........PASS (phases: 1 of 1 run, 1 pass, 0 fail)(tests: 89 run, 89 pass, 0 fail)
test.dtcvasc...........PASS (phases: 25 of 25 run, 25 pass, 0 fail)(tests: 97 run, 97 pass, 0 fail)
test.interval..........PASS (phases: 15 of 15 run, 15 pass, 0 fail)(tests: 178 run, 178 pass, 0 fail)
test.intofmtasc........PASS (phases: 2 of 2 run, 2 pass, 0 fail)(tests: 12 run, 8 pass, 4 fail, 4 expected failures)
test.rdtaddinv.........PASS (phases: 3 of 3 run, 3 pass, 0 fail)(tests: 69 run, 69 pass, 0 fail)
test.rdtimestr.........PASS (phases: 1 of 1 run, 1 pass, 0 fail)(tests: 16 run, 16 pass, 0 fail)
test.rdtsub............PASS (phases: 1 of 1 run, 1 pass, 0 fail)(tests: 19 run, 15 pass, 4 fail, 4 expected failures)
Каждая программа идентифицирует себя и свой статус (PASS или FAIL) и сводную статистику.Я занимался поиском и исправлением ошибок, отличных от тех, которые я обнаружил по совпадению, поэтому есть некоторые «ожидаемые ошибки».Это должно быть временное положение дел;это позволяет мне обоснованно утверждать, что все тесты пройдены.Если бы я хотел получить более подробную информацию, я мог бы выполнить любой из тестов на любом из этапов (поднаборы тестов, которые в некоторой степени связаны, хотя «что-то» на самом деле произвольно), и увидеть результаты полностью и т. Д.Как показано, выполнение этого набора тестов занимает менее секунды.
Я считаю это полезным, когда есть повторяющиеся вычисления - но мне пришлось рассчитывать или проверять правильный ответ для каждого из них.тесты в какой-то момент.