Re[11]: дебагинг vs unit-тесты vs ассерты
От: landerhigh Пират  
Дата: 04.05.16 15:47
Оценка: 16 (1) +1
Здравствуйте, Кодт, Вы писали:


К>Нет, пока что я вижу, куда ты тянешь дискуссию: "поскольку юнит-тесты — это круто, давайте выкинем отладчик".


Нет, не так. Попытка заменять тесты отладчиком приводят именно что к фигак-фикаг продакшену.

К>Проект хромиума покрыт тестами сверху донизу, но их настолько недостаточно, что приходится иногда даже трахаться с gdb в командной строке.


Есть мнение, что без тестов он вообще бы не взлетел.

К>Э, ты тут спрыгиваешь с темы.

К>Дано: написал кривой код, который на редких условиях зримо глючит. Обкладывай его тестами для поиска бага.

Эээ, тесты не для поиска багов. А для написания верифицируемого в контролируемых условиях кода. Почувствуйте разницу (с).

К>Можешь даже отрефакторить наивный алгоритм, исходно обложенный тестами. Собственно, так оно и происходит: взяли наивную реализацию, начали переписывать на замороченную, затейливо накосячили (а старые тесты при этом выполнились).


Еще раз — когда в код вносят новые граничные условия, для этих условий пишут новые тесты. Это часть процесса разработки. Если ему не следовать, то, как говорится, щасливой атладки!

К>>>Если будешь рожать "с листа", то риск накосячить с адресной арифметикой достаточно велик.

L>>Ключевое слово "риск". Привыкший работать по TDD моментально распознает эти риски. И задает себе вопрос — как я напишу тест, который проверит, накосячил ли я в данном месте? Это не всегда можно, но чаще всего — как раз можно.

К>До какой степени детализации ты будешь обкладывать код тестами?


До приемлимой. Наша задача — написать код с предсказуемым поведением в определенных условиях и верифицировать это поведение в пределах этих самых условий.

К>
К>void go_around_zero(int n) { for(int i = -n*10; i <= n*10; ++i) { do_smth(); } }
К>// TODO отрефакторить, чтобы покрыть тестами
К>


Эээ, тесты по определению тестируют наблюдаемое поведение. Что можно явно наблюсть у данного куска кода? Факт зацикливания? Это неявно. do_smth() явно не зависит ни от n, ни от i. Можно нужно переписать цикл, убрав чертовщину с -n*10.
Но ладно, в принципе, пример годный. Допустим, что исходный код имеет некий пока непонятный мне смысл. Сделаем неявное явным

template <class F>
void go_around_zero_v2(int n, F func) { for(int i = -n*10; i <= n*10; ++i) { func(); } }


TEST(go_around_zero_v2, testNegativeN)
{
    int n = 10;
    unsigned int expected = n*10*2+1;    // Ну или 100500 от балды, чтобы просто отловить бесконечный цикл
    unsigned int actual = 0;
    go_around_zero(-10, [&]() {
        ASSERT_LT(expected, actual++) << "Possible infinite loop";    // Враг не пройдет
        });
    ASSERT_EQ(expected, actual);        // Исходный код до этого момента не дойдет
}


Получаем провал теста.
получаем провал и начинаем думать — кто виноват и что делать. В смысле, должен ли go_around_zero выполнять проверку входных данных или по условию задачи они проверяются уровнем выше и тут их проверять смысла нет. Если последний вариант — добавляем огромный WARNING, нет, лучше ACHTUNG в описание функции. Если первый, то выясняем, что именно нужно проверить — только на отрицательное значение n или же на переполнение и т.п. Или же в ходе дискусии выяснится, что этот go_around_zero — остаток говнокода из каменного века, и т.к. do_something от индекса не зависит, то код можно переписать с условием цикла здорового человека, убрав возможности для неявного переполнения.
На больших проектах, где задействовано много людей, бывает так, что концов не найти и все отмахиваются, но при этом настаивают на сохранении go_around_zero. Для очистки совести меняю оригинальный код на
template <class F>
void go_around_zero_v2(int n, F func) { 
    if (math::abs(n) > MAX_UINT/20 -1)
    {
        throw std::runtime_error("Loop will go bananas!");
    }
    for(int i = -n*10; i <= n*10; ++i) { 
        func(); 
    } 
}

void go_around_zero(int n)
{
    go_around_zero_v2(n, do_smth);
}


Примерно все.

К>А в это время в Виллабаджо уже делают фигак-фигак-продакшен.




Через 3 месяца тестеры пишут баг-репорт "падает". Смотришь в лог, видишь bananas. Пинаешь того, кто вызывал твой код.
www.blinnov.com
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.