пятница, 15 ноября 2013 г.

Unit testing handmade (часть вторая)

С прошлого поста я сумел сделать более правильные параметризованные тесты, теперь в описании теста не нужно перечислять структуру тупла в списке параметров, да и помимо туплом можно использовать любой другой тип.

static const auto encrypt_params = {
    make_tuple(key01, 0xCCCCCCCCU, 0x33333333U, 0xF5FE5211U, 0x17E8D02EU),
    make_tuple(key01, 0x33333333U, 0xCCCCCCCCU, 0x6390ED97U, 0x3A962C89U),
    make_tuple(key02, 0xCCCCCCCCU, 0x33333333U, 0x2A78B7E0U, 0x800A0268U),
    ...
};

UP_PARAMETRIZED_TEST(encryptShouldBeCorrect, encrypt_params) {
    const auto key = get<0>(encrypt_params);
    const auto A = get<1>(encrypt_params);
    const auto B = get<2>(encrypt_params);
    const auto eA = get<3>(encrypt_params);
    const auto eB = get<4>(encrypt_params);

Но порассуждать хотел в основном на тему утверждений.

У меня ASSERT_EQUAL реализован на основе темплейтов. В бусте насколько знаю, тоже. Хотел посмотреть для верности, но что-то зарылся там, ну его нафиг. Учитывая, что пихнуть в утверждение могут все, что угодно (особенно в мое), проще всего параметризовать функцию сравнения одним типом:

template <typename T>
bool isEqual(const T &a, const T &b) const {

Это с одной стороны дает нам гарантию, что типы точно одинаковые. Но с другой стороны вызывает ряд сложностей. Самая большая сложность, которая мне не нравится - это то, что нельзя сравнивать знаковые и беззнаковые типы. Казалось бы, почему бы не написать просто:

UP_ASSERT_EQUAL(colection.size(), 5);

Но это не прокатит, потому, что размер коллекции меряется в size_t, который без знака... а 5 - это int. И все, шаблон не параметризуется, код не компилируется.

Вообще не люблю полагаться на неявные преобразования. Люблю все делать явно. Как вычитал в одной умной статье - неявные и сложные вещи повышают когнитивную нагрузку на мозг. Нужно стараться писать понятно, а свободный от нагрузки мозг не станет бездельничать - он найдет на какие еще мысли потратить свои ресурсы. Хотя в си все так привыкли к неявным преобразованиям, что всегда думают о них и боюсь что мое решение может оказаться не очень хорошо воспринятым. Но я всеравно сделаю это, потому, что считаю это правильным.

В сях -1 == UINT_MAX. Мне это совершенно не кажется правильным! Я даже не хочу задумываться что к чему приводится.

В моем фреймворке отрицательное число не равно беззнаковому. А знаковое число может быть равно беззнаковому только в общем для них числовом диапазоне. И это справедливо для всех целочисленных типов от сhar до long long... можно сравнивать  char и unsigned long long, но они будут сравнимы только в диапазоне от 0 до 127.

Вопрос конечно спорный и дискуссионный, но мне кажется что это математически правильно.

Другая интересная тема - непреднамеренное завершение теста. С исключениями - тут все просто. А вот ради перехвата сигналов пришлось поломать голову. Надо сказать, что я не очень дружу с сигналами. В жизни работа с ними требуется достаточно редко. И тупая попытка установить хендлер, который будет бросать исключения сработала, но только один раз. На втором тесте все благополучно свалилось в cегфолт. Пришлось лезть в буст и смотреть как это сделано там. Можно сказать, скопипастил.

Правда не обошлось без колебаний. С одной стороны приложения не должны падать. Любая ошибка такого рода - это фатально и требует немедленного исправления. Но с другой стороны - тестовый фреймворк должен показать полное состояние приложения, и будет как-то не честно при первом же делении на ноль тупо упасть, даже если при этот будет выведена вспомогательная информация. Тест по любому считается сбойным, но тестовый набор надо прогнать полностью.

Ввел я в свой фреймворк и чекпоинты. Надо сказать чекпоинты в бусте как-то мало помогали. Чекпоинт имеет смысл, когда тест непреднамеренно завершается. Но буст, в этом случае, всегда мне показывал координаты предыдущего теста. Учитывая что я запускаю тесты в случайном порядке - ценность этой информации нулевая. Мои чекпоинты стоят везде.

Наши кошки живут в гнезде,
Летают - везде.
Прилетели во двор, 
Завели разговор:
Кар-кар.
Вспомнилось, не знаю к чему...

В каждом тесте по умолчанию как минимум три чекпоинта. Перед инициализацией фикстуры, перед выполнением теста, перед финализацией фикстуры. Исключение из деструктора вылететь по идее не должно, но нарушение наверное может возникнуть? Черт, у меня нарушения транслируются в исключения - надо бы это потестить, а то может настать конец света... 

Кроме этого любое утверждение в коде является чекпоинтом. И конечно можно ставить свои, сколько надо. Не одно нарушение не останется незамеченным и нелокализованным!

На этой радостной ноте закругляюсь. Проект по прежнему живет на github.