Нужна мне была функция, которая бы при низкоуровневой отладочной печати умела распечатать строку, заменив непечатаемые коды (меньше 32 и больше 126) кодами. Все крайне тривиально.
Имеем:
1234\x04\x1fooo\xff
Печататься должно:
1234<04><1f>ooo<ff>
Рождается функция:
// This function converts all non-ASCII characters (with codes less than 32 // and greater than 126) into the <xx> form where "xx" is a hexadecimal code // of the character. void MakeAllPrintable(const char* from, char* to, int to_capacity) { const static char hex[] = "0123456789abcdef"; for (; *from && to_capacity > 1; ++from) { const unsigned char c = *from; if (c < 32 || c > 126) { if (to_capacity < 4 + 1) break; *to++ = '<'; *to++ = hex[(c >> 4) & 0xf]; *to++ = hex[c & 0xf]; *to++ = '>'; } else { *to++ = c; } } *to = '\0'; }
Увы, это С, а наша системы сборки частей на С, хоть и имеет cmockery, но добавление тестов всегда связано с ручным прописываением имен фунцкий, а это будет означать пересборку еще и makefile’ов, а это будет означать проверку этой пересборки на всех типах ОС, и в этоге на подготовку уйдет час или больше. А если потихоньку просто добавить функцию без теста, то 5 минут, так как изменится только один исходник. Мои мысли: “Ну я, типа, такой катаный колобок, врядли налажаю в столь простой функции. Так что можно без тестов.“. И, конечно, налажал. Код выше имеет конкретный косяк. Он бы, естественно, всплыл, может даже на code review, но в итоге я отогнал все ренегатские мысли, сделал всю рутину и сел за тесты, хотя по уму, я их должен быть писать до.
Очевидно, что вполне логичный (предпоследний) тест, там, где последний параметр 5, сразу показал, что я забыл проверку максимальной длины буфера (эдакая назадача!):
void test_MakeAllPrintable(void **state) { char buf[16 * 1024]; MakeAllPrintable("", buf, sizeof(buf)); assert_int_equal(strlen(buf), 0); MakeAllPrintable("abc", buf, sizeof(buf)); assert_string_equal(buf, "abc"); // This check verifies that the function always cleans the buffer at start. MakeAllPrintable("", buf, sizeof(buf)); assert_int_equal(strlen(buf), 0); // This check verifies that the function doesn't overwrite the given buffer // and always reserves the last byte for '\0'. MakeAllPrintable("12345678", buf, 5); assert_string_equal(buf, "1234");$ MakeAllPrintable("1\x01\r\n\t\x1f 2\x7e\x7f\x80\xff", buf, sizeof(buf)); assert_string_equal(buf, "1<01><0d><0a><09><1f> 2~<7f><80><ff>"); }
А вот как код должен был выглядеть:
// This function converts all non-ASCII characters (with codes less than 32 // and greater than 126) into the <xx> form where "xx" is a hexadecimal code // of the character. void MakeAllPrintable(const char* from, char* to, int to_capacity) { const static char hex[] = "0123456789abcdef"; for (; *from && to_capacity > 1; ++from) { const unsigned char c = *from; if (c < 32 || c > 126) { if (to_capacity < 4 + 1) break; *to++ = '<'; *to++ = hex[(c >> 4) & 0xf]; *to++ = hex[c & 0xf]; *to++ = '>'; to_capacity -= 4; // (!!!) } else { *to++ = c; --to_capacity; // (!!!) } } *to = '\0'; }
Пример, безусловно, примитивный, но очень показательный. Принцип простой: “нет тестов, нет кода”. Когда жизненный цикл проекта более нескольких дней, тесты обязаны быть частью кода, и по мне, не должно быть разделения на production code и test code по большому счету, так как первое без второго не существует.