Здравствуйте, netch80, Вы писали:
N>Я бы уточнил так: использование отладчика без тестов — или каменный век, или любая разновидность подхода, не ориентированного на целевое долговременное использование кода, или редкие особые случаи, когда автоматизация тестирования невозможна.
+100500
__>>так а где здесь код-то самого транспонирования? ведь ошибки именно там кроются. N>А зачем он тут нужен? Описанные тесты покрывают необходимое поведение по ТЗ, а не рассмотрение с учётом особенностей кода.
Плюс в сферическом идеальном мире тесты пишутся до того, как будет написан собственно код. Так, правда, почти никогда не бывает.
__>> и если вы увидите срабатывание ассертов, что делать дальше будете? N>Искать ошибку. Есть другие варианты? __>>как, например, обнаружите глупейшую ошибку типа Matrix[i][j] = Matrix[j][i] для in-place трансполнирования квадратной матрицы? N>Простите, а что именно тут названо глупейшей ошибкой? Вообще-то транспонирование на месте именно так и делается.
Хех. Именно что глупейшая, которую легко допустить на автомате, ибо задача элементарная и моск был занят чем-то другим в момент написания кода. Но такая ошибки отлично отлавливается на счет "раз" юнит-тестами. Тут значение элемента [i][j] теряется. Вместо этого кода должно быть swap(Matrix[i][j],Matrix[j][i])
Здравствуйте, _hum_, Вы писали: __>Потому вопрос в зал, может кто-нибудь, кто придерживается мнения landerhigh, на примере задачи написания функции транспонирования матрицы показать, как тесты заменяют работу с дебагером?
понадобятся 4 теста
[] -> [] // если упадет этот тест, то у вас не проверяются, понятно, пустные данные
1 -> 1 // если этот тест - выход за пределы матрицы при итерировании по ней
12 -> 1 // тут - ошибка в коде изменения размера матрицы или забыли итерироваться по столбцам
2
12 -> 13 // если ошибка тут - то та самая ошибка с индексами или забыли итерироваться по строкам
34 24
если кажется, что этих тестов недостаточно, то приведите пример любого более-менее разумного кода транспонирования, который бы проходил эти тесты, но содержал бы опечатку или ошибку и не работал в каких-то случаях
Здравствуйте, landerhigh, Вы писали:
L>Вот для того, чтобы не устраивать комбинаторный взрыв в интеграционных тестах, и нужны юнит-тесты, чтобы все возможные варианты работы юнита тестировать в изоляции. Интеграционные тесты должны тестировать интеграцию, не более.
Твоими бы тестами, да медку бы хапнуть!
Все эти "должны" — это прекрасно, а кто конкретно эти долги отдаёт?
К>>- и будешь гонять эту бомбу каждый раз при изменениях, и тогда узнаешь, что регрессия нифига не бесплатна; или перестанешь фанатеть и покроешь исключительно проблемные места; а какие места проблемные, выяснишь во время отладки.
L>Проблемные места имеют такое противное свойство, что могут возникать и после того, как их вроде бы как все "выяснили" во время отладки.
L>>>>>Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
К>>Интеграционный тест должен будет потратить столько же времени для проверки, наигрывается или нет тот самый баг. Если какой-то больной модуль ровно через год запускается.
L>Не должен. Это ответственность юнит-теста.
Только в том случае, если юнит-тестами покрыто абсолютно всё поведение модуля.
В том числе, нарушение контракта извне. То есть, у тебя есть мок, который наловчился диагностировать абсолютно все нарушения контракта, проходящие под категорией "неопределённое поведение".
Подчеркну: абсолютно все.
К>>Например, реальный баг: после нажатия на кнопку [_] окошко не сворачивается, а захватывает мышь и тупит. Что это за хрень? К>>Ты пишешь тест: mainwindow()->minimize(). Тест проходит.
L>Ничего не пишу. От слова совсем. Тесты уже есть, и тесты позволяют исключить 100500 юзкейсов из подозрения. В данном случае, тесты гарантируют, что если сообщение доходит до обработчика бизнес-логики, то оно обрабатывается в 100% случаев. Значит, проблема либо в том, что сообщение как-то неправильно обрабатывается в нашем обработчике, либо имеем пример недокументированного поведения операционной системы. То есть вариантов вообще практически нет. Изолируем, включаем лог в обработчике и смотрим, что приходит на вход. То есть вообще ничего делать не надо. 15 минут — и WFT найден.
Да щас! "Тесты уже есть".
Тесты — это лишь ещё одна проекция предметной области на твоё понимание предметной области. (Первая проекция — это рабочий код).
Если ты чего-то недопонял, то тупо не догадаешься написать правильные тесты.
К>>А может быть, проще было не выдумывать тесты, а запустить дебаггер и Spy++, и смотреть, как диспетчится мышь?
L>Ничего выдумывать не нужно, все уже есть. Включаю лог и смотрю. Если уж совсем припрет, можно и отдалчик привлечь, как крайнюю меру.
К>>Не обманывай себя. Тесты пишет человек. L>Хмм, а в отладчик кто смотрит, супермен, что ли?
И в отладчик смотрит человек. А человеку свойственно ошибаться, и поэтому надеяться только на тесты — это заранее согласиться на ложноотрицательную диагностику.
Как, впрочем, и одну лишь отладку...
К>>Сделал, к примеру, транспонирование матрицы на месте, но она у тебя, скажем, стреляет по памяти при НОД(x,y)=17, а стрельба сходит с рук при x<100. К>>Да, ты написал тесты о том, что матрица транспонируется. Разумеется, только при маленьких размерах и выборочно (ну не перебирать же все x от 1 до 200 и y от 1 до 200). К>>А что ты сделал для проверки стрельбы?
L>Во-первых, don't write shit code. Моей фантазии не хватает на то, чтобы представить, как можно в коде транспонирования матрицы случайно такой косяк допустить.
Или ладно, не транспонирования, а быстрого умножения. Где ради борьбы с кешмиссами делается переупорядочивание по Мортону, в зависимости от архитектуры процессора ещё. Таких проблем, для которых есть дорогой наивный алгоритм без ошибок — тьма. Я так, навскидку про транспонирование вспомнил.
Если будешь рожать "с листа", то риск накосячить с адресной арифметикой достаточно велик. L>Во-вторых, проверка стрельбы по памяти не входит в ответственность юнит-тестов. Я в принципе не уверен, что ее можно в общем случае обнаружить в момент собственно выстрела.
ВотЪ! Не всемогущи твои юнит-тесты. Настоящий, труъ баг они не распознают.
L>В-третьих, используя тот факт, что дважды транспонированная матрица равна самой себе, в случае подобных подозрений можно написать и автотест, который переберет все варианты и сделает это за O(x*y) время, что в 21 веке означает примерно "мгновенно".
Сколько пар (x,y) ты хочешь перебрать? В диапазоне [1..N] ты уже отхватишь O(N^4). Мгновенно...
Здравствуйте, Кодт, Вы писали:
К>Твоими бы тестами, да медку бы хапнуть!
Я, кажется, знаю, куда эта дискуссия ведет — поскольку юнит-тесты не отлавливают расстрел памяти в сферическом случае в в вакууме, то и писать их не будем. Я такое видел 100500 раз.
К>Все эти "должны" — это прекрасно, а кто конкретно эти долги отдаёт?
По опыту, долги приходится отдавать в основном тогда, когда на тестах "сэкономили".
К>Только в том случае, если юнит-тестами покрыто абсолютно всё поведение модуля. К>В том числе, нарушение контракта извне. То есть, у тебя есть мок, который наловчился диагностировать абсолютно все нарушения контракта, проходящие под категорией "неопределённое поведение".
"Нарушение контракта извне" aka выдача невалидных данных на вход — один из трех сценариев использования юнита. Обязательно тестируется.
К>Подчеркну: абсолютно все.
Что значит "абсолютно все"? Юнит таков, что ему можно 100500 способами нарушить контракт? Так нужно его резать на съедобные части, только и всего.
К>Да щас! "Тесты уже есть". К>Тесты — это лишь ещё одна проекция предметной области на твоё понимание предметной области. (Первая проекция — это рабочий код).
Не предметной области, а логики работы юнита.
К>Если ты чего-то недопонял, то тупо не догадаешься написать правильные тесты.
Когда допонял — дописываешь пропущенные. А то ведь так бывает, что допонял предметную часть и понял, что все, что накодил, нужно полностью переписывать.
К>И в отладчик смотрит человек. А человеку свойственно ошибаться, и поэтому надеяться только на тесты — это заранее согласиться на ложноотрицательную диагностику. К>Как, впрочем, и одну лишь отладку...
Вот именно, вот именно.
К>Ну давай, покажи простой и эффективный код транспонирования матрицы на месте.
Э, нет, я тут про тесты говорю, а не матирцы.
К>Если будешь рожать "с листа", то риск накосячить с адресной арифметикой достаточно велик.
Ключевое слово "риск". Привыкший работать по TDD моментально распознает эти риски. И задает себе вопрос — как я напишу тест, который проверит, накосячил ли я в данном месте? Это не всегда можно, но чаще всего — как раз можно.
L>>Во-вторых, проверка стрельбы по памяти не входит в ответственность юнит-тестов. Я в принципе не уверен, что ее можно в общем случае обнаружить в момент собственно выстрела. К>ВотЪ! Не всемогущи твои юнит-тесты. Настоящий, труъ баг они не распознают.
По нынешним временам стрельба по памяти — это уже не труъ багъ, а "скорее кто пустил студентов в репозиторий".
Тем не менее, гоняя юнит-тесты при каждой сборке, у тебя больше шансов узнать, что имеет место быть "упс", еще до выхода в тестирование.
L>>В-третьих, используя тот факт, что дважды транспонированная матрица равна самой себе, в случае подобных подозрений можно написать и автотест, который переберет все варианты и сделает это за O(x*y) время, что в 21 веке означает примерно "мгновенно". К>Сколько пар (x,y) ты хочешь перебрать? В диапазоне [1..N] ты уже отхватишь O(N^4). Мгновенно...
Все же, наверное, квадрат. Вполне приемлимо для функционального теста.
Опять же, никто так не делает. Анализируют риски ошибок в реализации алгоритма, и пишут тесты, нацеленные на срабатывание именно этих рисков.
Здравствуйте, landerhigh, Вы писали:
L>Я, кажется, знаю, куда эта дискуссия ведет — поскольку юнит-тесты не отлавливают расстрел памяти в сферическом случае в в вакууме, то и писать их не будем. Я такое видел 100500 раз.
Нет, пока что я вижу, куда ты тянешь дискуссию: "поскольку юнит-тесты — это круто, давайте выкинем отладчик".
Проект хромиума покрыт тестами сверху донизу, но их настолько недостаточно, что приходится иногда даже трахаться с gdb в командной строке.
К>>Ну давай, покажи простой и эффективный код транспонирования матрицы на месте. L>Э, нет, я тут про тесты говорю, а не матирцы.
Э, ты тут спрыгиваешь с темы.
Дано: написал кривой код, который на редких условиях зримо глючит. Обкладывай его тестами для поиска бага.
Для тренировки: напиши исходно безглючный код для замороченного алгоритма.
Можешь даже отрефакторить наивный алгоритм, исходно обложенный тестами. Собственно, так оно и происходит: взяли наивную реализацию, начали переписывать на замороченную, затейливо накосячили (а старые тесты при этом выполнились).
К>>Если будешь рожать "с листа", то риск накосячить с адресной арифметикой достаточно велик. L>Ключевое слово "риск". Привыкший работать по TDD моментально распознает эти риски. И задает себе вопрос — как я напишу тест, который проверит, накосячил ли я в данном месте? Это не всегда можно, но чаще всего — как раз можно.
До какой степени детализации ты будешь обкладывать код тестами?
void go_around_zero(int n) { for(int i = -n*10; i <= n*10; ++i) { do_smth(); } }
// TODO отрефакторить, чтобы покрыть тестами
ну поехали, что ли!
int ten_times(int x) { return x*10; }
// TODO покрыть тестами умножение на 10void go_around_zero(int n) { for(int i = ten_times(-n), e = ten_times(n); i <= e; ++i) { do_smth(); } }
// TODO тесты для go_around_zero
начнём...
// названия макросов тестов вымышлены, по мотивам GTest.
TEST(TenTimes, ZeroIsZero) { EXPECT_EQ( 0, ten_times( 0); }
TEST(TenTimes, PositiveIsPositive) { EXPECT_EQ(+10, ten_times(+1); }
TEST(TenTimes, NegativeIsNegative) { EXPECT_EQ(-10, ten_times(-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. Пинаешь того, кто вызывал твой код.
Здравствуйте, landerhigh, Вы писали:
К>>Нет, пока что я вижу, куда ты тянешь дискуссию: "поскольку юнит-тесты — это круто, давайте выкинем отладчик". L>Нет, не так. Попытка заменять тесты отладчиком приводят именно что к фигак-фикаг продакшену.
Кто говорил "заменять"? Это ты всячески хочешь заменить отладчик тестами, а не я тесты отладчиком.
L>Еще раз — когда в код вносят новые граничные условия, для этих условий пишут новые тесты. Это часть процесса разработки. Если ему не следовать, то, как говорится, щасливой атладки!
Видишь ли, вся арифметика — это сплошные граничные условия.
И ты либо пишешь на идрисе или каких-то тому подобных языках, с нумералами Пеано и прочами ужасами.
Либо соглашаешься на то, чтоб какую-то совсем уж откровенную мелочёвку принять на веру. (А компилятор, в случае чего, тебя не простит и отомстит).
Вот в этом месте, как только ты где-то написал *10 или даже +1, — ты уже попал на граничные условия. В совершенно новом коде, в котором, казалось бы, нечему ломаться.
L>Эээ, тесты по определению тестируют наблюдаемое поведение. Что можно явно наблюсть у данного куска кода? Факт зацикливания? Это неявно. do_smth() явно не зависит ни от n, ни от i. Можно нужно переписать цикл, убрав чертовщину с -n*10.
Почему факт зацикливания? Умный гусь или шланг в -O3 способен творить чудеса, и на нашем форуме уже приводили примеры — как он и бесконечный, и пустой цикл создавал, и через условия перепрыгивал. Потому что Undefined даёт широкие возможности. Например, отладочный код будет работать правильно (тесты-то компилятся с -O0 либо -Ox -DDEBUG), а релизный хаотично, и даже диск форматнуть может при известной ловкости рук.
L>Но ладно, в принципе, пример годный. Допустим, что исходный код имеет некий пока непонятный мне смысл. Сделаем неявное явным L>
L>template <class F>
L>void go_around_zero_v2(int n, F func) {
L> if (math::abs(n) > MAX_UINT/20 -1)
L> {
L> throw std::runtime_error("Loop will go bananas!");
L> }
L> for(int i = -n*10; i <= n*10; ++i) {
L> func();
L> }
L>}
L>void go_around_zero(int n)
L>{
L> go_around_zero_v2(n, do_smth);
L>}
L>
L>Примерно все.
Моя мина-ловушка всё-таки сработала! abs(INT_MIN) даёт неопределённое поведение, если INT_MIN < -INT_MAX.
Для комплементарной арифметики abs(INT_MIN) == INT_MIN, и условие становится всегда-истинным.
После чего вылезает оптимизатор (который тоже посчитал границы цикла с неопределённым поведением) и делает там что-то очень странное. Но в тесты это не попадёт, потому что тестовое окружение задавило оптимизацию.
К>>А в это время в Виллабаджо уже делают фигак-фигак-продакшен. L>Через 3 месяца тестеры пишут баг-репорт "падает". Смотришь в лог, видишь bananas. Пинаешь того, кто вызывал твой код.
Через три месяца Виллабаджо захватила свою долю рынка бета-версией продукта, дерзкой и как пуля резкой, а Вилларибо продолжает обкладывать арифметику тестами и/или выпускать гигантского ленивца (каждая проверка имеет свою цену в байтах и наносекундах).
Здравствуйте, Кодт, Вы писали: К>Кто говорил "заменять"? Это ты всячески хочешь заменить отладчик тестами, а не я тесты отладчиком.
Нет, я предлагаю вылезти из каменного века и перестать точить кремниевые топоры гранитными камнями. К>Видишь ли, вся арифметика — это сплошные граничные условия. К>И ты либо пишешь на идрисе или каких-то тому подобных языках, с нумералами Пеано и прочами ужасами. К>Либо соглашаешься на то, чтоб какую-то совсем уж откровенную мелочёвку принять на веру. (А компилятор, в случае чего, тебя не простит и отомстит).
Что-то всегда нужно принять на веру, а то так можно договориться до того, что начинать нужно с юнит-тестов для операций процессора. К>Почему факт зацикливания? Умный гусь или шланг в -O3 способен творить чудеса, и на нашем форуме уже приводили примеры — как он и бесконечный, и пустой цикл создавал, и через условия перепрыгивал. Потому что Undefined даёт широкие возможности. Например, отладочный код будет работать правильно (тесты-то компилятся с -O0 либо -Ox -DDEBUG), а релизный хаотично, и даже диск форматнуть может при известной ловкости рук.
Именно поэтому у нас проект сразу собирается в релизе и тесты гоняются тоже в релизе. Мы можем себе это позволить, поскольку при разработке отладчик нам использовать незачем. И это нас уже спасло от кучи именно таких приколов.
cut
L>>Но ладно, в принципе, пример годный. Допустим, что исходный код имеет некий пока непонятный мне смысл. Сделаем неявное явным L>>
L>>template <class F>
L>>void go_around_zero_v2(int n, F func) {
L>> if (math::abs(n) > MAX_UINT/20 -1)
L>> {
L>> throw std::runtime_error("Loop will go bananas!");
L>> }
L>> for(int i = -n*10; i <= n*10; ++i) {
L>> func();
L>> }
L>>}
L>>void go_around_zero(int n)
L>>{
L>> go_around_zero_v2(n, do_smth);
L>>}
L>>
L>>Примерно все. К>Моя мина-ловушка всё-таки сработала! abs(INT_MIN) даёт неопределённое поведение, если INT_MIN < -INT_MAX.
Не сработала. Эта мини-ловушка не пройдет тест для граничных условий. Я просто в очередной раз наступаю своей же песне на хвост — тест с INT_MIN я напишу вторым, после INT_MAX. Мне это очевидно, тем, кто тесты писать не привык, нет. Я же говорю — тесты пишутся для граничных условиях на автомате за считанные секунды. Иногда приходится задуматься, как в случае с INT_MIN, чтобы понять, сколько же итераций в данном случае ожидается.
(А потом аффтар кода вспомнит, что писать код под гриппом с температурой — это еще хуже, чем под веществами (наверное) и больше этого делать не будет) К>После чего вылезает оптимизатор (который тоже посчитал границы цикла с неопределённым поведением) и делает там что-то очень странное. Но в тесты это не попадёт, потому что тестовое окружение задавило оптимизацию.
Не делает — тест гоняются в релизе. И волосы остаются мягкими и шелковистыми. К>Через три месяца Виллабаджо захватила свою долю рынка бета-версией продукта, дерзкой и как пуля резкой, а Вилларибо продолжает обкладывать арифметику тестами и/или выпускать гигантского ленивца (каждая проверка имеет свою цену в байтах и наносекундах).
Нет, не так. Через два месяца Виллариба выпустила стабильный первый релиз продукта, захвативший весь рынок. Через еще два — второй стабильный.
А виллабаджо никак из нестабильной беты вылезти не может.
Слушай, не надо только меряться. Ну не увеличивает использование тестов время разработки. Уменьшает. Часто — очень значительно уменьшает. А уж разница после передачи проекта в QA... но тут нужно один раз увидеть, чтобы поверить.
Здравствуйте, landerhigh, Вы писали:
К>>Дано: написал кривой код, который на редких условиях зримо глючит. Обкладывай его тестами для поиска бага. L>Эээ, тесты не для поиска багов. А для написания верифицируемого в контролируемых условиях кода. Почувствуйте разницу (с).
Вначале я думал этому мягко возразить, но, подумав, скорее соглашусь. Формулировка слегка коварная, но правильная. (За неё плюс и баллы, как бы я ни относился к остатку письма.)
Верификацию в нашей типичной реальности проводит в основном человек, с небольшой помощью компилятора. В некоторых случаях, как в соседнем треде про ATS, чуть больше чем небольшой, но всё равно участие человека основное.
И чтобы эта верификация проходила успешно, человеку надо помочь.
Более того, даже при машинной верификации тестирование всё равно необходимо — чтобы повысить шансы, что условия, включённые в аксиоматику верификатора, действительно истинны.
L>TEST(go_around_zero_v2, testNegativeN)
Вот тут уже начинаются нелады. Основная проблема в том, что ты поддался на откровенную провокацию Кодта. Провокация эта не в INT_MIN или в чём-то подобном; провокация в том, что ты согласился рассматривать именно его постановку задачи в виде этой функции и его разбиение кода этой функции на структурные части. А это тут не нужно. Проверка кода в таком виде или вообще не проверяется тестами, или тесты станут повторять последствия математической верификации кода; но тогда им нужно проверять не выходной скомпилированный результат, а промежуточный отчёт компилятора (gcc, например, на каком-то этапе выдаёт возможные границы значений переменных) или вообще исходный код! Это работа статического анализатора, который должен был сказать "эгегей, если у тебя n>214748364, то у тебя проблемы с UB, а если n<0, то вообще непонятно, чего ты тут хотел добиться". То есть всё равно верификатор.
Итого, лучше всего было просто отказаться рассматривать в таком виде.
L> unsigned int expected = n*10*2+1; // Ну или 100500 от балды, чтобы просто отловить бесконечный цикл
Так не отловишь же — оно может захотеть вообще ничего не делать. Да и в реальности бесконечного цикла не будет при любом из возможных переполнений.
Пока что, если ты пишешь тест, уже зная про потенциальную возможность UB, тебе нет смысла писать тест именно на UB именно потому, что UB непредсказуемо и поэтому заведомо за рамками тестирования.
(Разве что апостериорного статистического, но это совсем другой разговор.)
Вот если бы речь шла про C# и checked context, или аналогичное средство, где переполнение гарантируется, можно было бы его явно проверить только зачем? И без нас есть кому проверить компилятор
К>>А в это время в Виллабаджо уже делают фигак-фигак-продакшен. L>) L>Через 3 месяца тестеры пишут баг-репорт "падает". Смотришь в лог, видишь bananas. Пинаешь того, кто вызывал твой код.
Всё-таки в контрактах это уместнее.
L>До приемлимой.
Здравствуйте, Кодт, Вы писали:
К>До какой степени детализации ты будешь обкладывать код тестами? К>
К>void go_around_zero(int n) { for(int i = -n*10; i <= n*10; ++i) { do_smth(); } }
К>// TODO отрефакторить, чтобы покрыть тестами
К>
Уже писал рядом, но повторюсь: это вообще отвратительная провокация, и суть в том, что этот код, за счёт UB на переполнение и странной сущности внутри, не подлежит в таком виде ни тестированию, ни изучению отладчиком. Он подлежит статическому анализу и действиям по результату этого анализа.
Если гарантируется, что n<=INT_MAX/10, то проблемы в нём нет и тестировать, из-за простоты ситуации, нечего. Ну разве что вызвать с n=2 и убедиться в 41 запуске mock'а — этого будет достаточно для формальной отмазки. Можно добавить рантайм-проверку на предусловие.
Если же такой гарантии нет, то надо или обеспечить 20n+1 запусков вложенной функции (если она не зависит от i) вложенными циклами (лучше внутренний строго на 20 итераций, а внешний на n, чтобы компилятор развернул по вкусу), или (если i внутри значит) перейти на int64_t. Ну ладно, на int37_t, если такое есть или double. И тестировать только заведомо крайние случаи, которые на самом деле станут тестами гарантии окружения (что нам под видом int64_t не подсунули int32_t, и double под видом float). Всё остальное — не наше дело.
К>TEST(TenTimes, PositiveOverflow) { ASSERT_FAIL(ten_times(+INT_MAX/10 + 1)); } К>TEST(TenTimes, NegativeOverflow) { ASSERT_FAIL(ten_times(-INT_MAX/10 — 1)); }
В случае C++ без -fwrapv или аналога сами эти тесты не имеют права выполняться как что-то полезное.
К>А в это время в Виллабаджо уже делают фигак-фигак-продакшен.
И удивляются, когда кто-то таки подаёт этому коду n=2e9.
Здравствуйте, netch80, Вы писали:
N>Более того, даже при машинной верификации тестирование всё равно необходимо — чтобы повысить шансы, что условия, включённые в аксиоматику верификатора, действительно истинны.
L>>TEST(go_around_zero_v2, testNegativeN)
N>Вот тут уже начинаются нелады. Основная проблема в том, что ты поддался на откровенную провокацию Кодта.
Я знаю
На деле, что-то подобное всегда всплывает в разговорах о юнит-тестировании. Либо вот из таких сферических примеров в вакууме делается вывод, что юнит-тесты писать бесполезно, либо идут по другому пути — всем писать тесты с покрытием кода 100%, а кто не все, того накажем.
L>> unsigned int expected = n*10*2+1; // Ну или 100500 от балды, чтобы просто отловить бесконечный цикл
N>Так не отловишь же — оно может захотеть вообще ничего не делать. Да и в реальности бесконечного цикла не будет при любом из возможных переполнений.
Ну, не бесконечный, но явная чертовщина отловится. На самом деле приходилось подобные чудеса из кода вычленять через серии последовательно приближения юнит-тестами к пониманию, чего же хотелись добиться.
L>>До приемлимой. N>На всякий случай — "приемлемой".
Здравствуйте, netch80, Вы писали:
N>Уже писал рядом, но повторюсь: это вообще отвратительная провокация, и суть в том, что этот код, за счёт UB на переполнение и странной сущности внутри, не подлежит в таком виде ни тестированию, ни изучению отладчиком. Он подлежит статическому анализу и действиям по результату этого анализа.
Это было доведение до абсурда.
Конечно, программирование (как начисто, так и отладка) должно обращаться и к статическому анализу (часть которого делает компилятор), и к автоматическому тестированию, и к верификации "сверху" — доказательству корректности алгоритма, и "снизу" — доказательству соответствия реализации задумке. Но и к запуску под дебаггером, если предыдущие рубежи обороны оказались прорваны.
А то, что дорогостоящие рубежи обороны можно строить впустую, это во Второй Мировой показала линия Мажино, а в сях — моя провокация
Что касается компилятора, то он находится в состоянии неопределённости.
То ли программист знает что-то особенное за пределами возможностей компилятора, и мысленно поклялся, что UB не случится (предельный случай — write-only code).
То ли программист сам ещё не догадывается об ограничениях. (Или догадывается, но ошибается).
То ли догадывается, но хочет эксплуатировать.
Ну самое невинное:
for (int i = -n*10; i <= n*10; ++i) {...}
может быть неявно if(n >= 0), а может — так же неявно — assert(n >= 0).
В первом случае отрицательный аргумент окажется фичей, во втором — багом.
Хорошо тем, кто с самого начала выбрал писать на языке, придирчивом до контрактов. Эйфель там, не знаю, Ада какая-нибудь.
Если забудешь написать assert, или напишешь, но компилятор докажет, что реальные ограничения более жёсткие, чем заявленные, — он тебе надаёт по пальцам.
(Кстати, тот же гусь вполне мог бы при добавочном ключике --fimplicitly-add-magic-asserts-please навтыкать этих проверок в код. А может, есть уже такой ключик?)
А для всех остальных — огромная проблема легаси, во-первых (все те наработки, которые УЖЕ ещё не покрыты ассертами и тестами), и охоты на воробьёв, во-вторых (когда за горой проверок теряется замысел).
И не забываем, что никакой компилятор не всесилен. Там, где у него закончится горизонт понимания, он перестанет и за покрытием кода следить, и за диапазонами, хотя, с другой стороны, не активирует сценарии UB "инициативный дурак хуже услужливого".
И там, если человек облажался с ассертами (потому что и у человека есть горизонт, да и errare humanum est), придётся воевать со львами и драконами старым добрым дедовским отладчиком, логами и крешдампами.
К>>А в это время в Виллабаджо уже делают фигак-фигак-продакшен. N>И удивляются, когда кто-то таки подаёт этому коду n=2e9.
Но это случается только у одного пользователя из миллиона, из серии "а вы на шкаф залезать не пробовали?"
А остальные 999999 несут в Виллабаджо денежку.
Собственно, это уже за рамками программирования, а в ведении бизнесменов. Пациентов терака чертовки жалко, марсоход очень досадно, а за дыры для джейлбрейка даже и спасибо.
Возвращаясь к провокации.
По-хорошему, надо было не клевать себе мозг, а написать
void go_around_zero(int n) {
assert(n >= 0); // если кому-то нужно работать с вывернутым интервалом, подумайте о направлении итераций.
assert(n < 1000); // какому психу понадобилась такая здоровая окрестность, пусть и чинит.for (int i = -n*10; i <= +n*10; ++i)
do_smth(i);
// P.S. если ты тот самый псих, не забудь последить за целочисленным переполнением здесь, и за корректностью do_smth там.
}
Доказательства корректности в рамках этих волюнтаритских рамок делаются в уме.
А какими тестами обложить этот код, чтобы это было не "отвяжись, тимлид", — пусть landerhigh поделится
Здравствуйте, Кодт, Вы писали:
К>А то, что дорогостоящие рубежи обороны можно строить впустую, это во Второй Мировой показала линия Мажино, а в сях — моя провокация
Никто не пишет тесты для случаев "угадай, что я имел в виду".
К>>>А в это время в Виллабаджо уже делают фигак-фигак-продакшен. N>>И удивляются, когда кто-то таки подаёт этому коду n=2e9. К>Но это случается только у одного пользователя из миллиона, из серии "а вы на шкаф залезать не пробовали?" К>А остальные 999999 несут в Виллабаджо денежку.
это если этот самый не попадет за 100500 денежек и не раструбит об этом на 100500 углах.
К>Возвращаясь к провокации. К>По-хорошему, надо было не клевать себе мозг, а написать К>
К>void go_around_zero(int n) {
К> assert(n >= 0); // если кому-то нужно работать с вывернутым интервалом, подумайте о направлении итераций.
К> assert(n < 1000); // какому психу понадобилась такая здоровая окрестность, пусть и чинит.
К> for (int i = -n*10; i <= +n*10; ++i)
К> do_smth(i);
К> // P.S. если ты тот самый псих, не забудь последить за целочисленным переполнением здесь, и за корректностью do_smth там.
К>}
К>
Внимание, вопрос — это отладочный ассерт или эмуляция контракта, существующая и в релизе?
К>Доказательства корректности в рамках этих волюнтаритских рамок делаются в уме. К>А какими тестами обложить этот код, чтобы это было не "отвяжись, тимлид", — пусть landerhigh поделится
Сначала неплохо было бы описать, что же такое полезное этот do_smth делает, чтобы можно было наблюдаемое поведение наблюсть.
Здравствуйте, landerhigh, Вы писали:
L>Никто не пишет тесты для случаев "угадай, что я имел в виду".
Как отличить "тотальное покрытие" от "догадайся мол сама"?
К>>Но это случается только у одного пользователя из миллиона, из серии "а вы на шкаф залезать не пробовали?" К>>А остальные 999999 несут в Виллабаджо денежку. L>это если этот самый не попадет за 100500 денежек и не раструбит об этом на 100500 углах.
Управление рисками — для космолёта и радиохирурга одно, для свистелки под андроид другое. Тут ведь тоже "угадай, что скажут продажники".
К>>
К>> assert(n >= 0); // если кому-то нужно работать с вывернутым интервалом, подумайте о направлении итераций.
К>> assert(n < 1000); // какому психу понадобилась такая здоровая окрестность, пусть и чинит.
К>> // P.S. если ты тот самый псих, не забудь последить за целочисленным переполнением здесь, и за корректностью do_smth там.
К>>
L>Внимание, вопрос — это отладочный ассерт или эмуляция контракта, существующая и в релизе?
Контракт, конечно. Слово "отладочный" мы не будем рассматривать, а то ведь и до gdb доберёмся.
По-хорошему, надо бы заставить следить за исполнением контракта компилятор, а не окружение контроля качества (включая и скомпилированные тесты, и штат сотрудников, запускающих программу и читающих логи и крешдампы).
Например, вытащить эти ассерты в тип аргумента. В аде это делается почти бесплатно, в плюсах — немножко секса на шаблонах, в идрисе — просто нельзя не сделать.
Причём, ассерты могут быть разными
— чисто для -DDEBUG, "мамой клянус, мы всё проверили, если в релизе наиграется — нэ судьба"
— панические, — "мамой клянус" и одновременно "врагу не сдаётся Варяг" — немедленно сдохнуть в релизе, пока UB не добралось до винчестера
— с известными отступлениями (fallback) в пользу живучести (robustness) — исключение там кинуть, по дефолту что-нибудь вернуть...
Но это уже зависит от общей картины. В конце концов, живучесть приложения целиком посредством легко дохнущих дочерних процессов — стратегия let them crash — тоже имеет место, хоть в Эрланге, хоть в Хромиуме.
К>>Доказательства корректности в рамках этих волюнтаритских рамок делаются в уме. К>>А какими тестами обложить этот код, чтобы это было не "отвяжись, тимлид", — пусть landerhigh поделится
L>Сначала неплохо было бы описать, что же такое полезное этот do_smth делает, чтобы можно было наблюдаемое поведение наблюсть.
Ну это само собой. Вопрос был в том, нужно ли (и как) обвешать тестами аргумент n, если мы уже прописали контракт с помощью ассертов.
Здравствуйте, Кодт, Вы писали:
L>>Никто не пишет тесты для случаев "угадай, что я имел в виду". К>Как отличить "тотальное покрытие" от "догадайся мол сама"?
Эээ, ну аффтар-то кода должен хотя бы понимать, что делает? L>>это если этот самый не попадет за 100500 денежек и не раструбит об этом на 100500 углах.
К>Управление рисками — для космолёта и радиохирурга одно, для свистелки под андроид другое. Тут ведь тоже "угадай, что скажут продажники".
Космолет и радиохирург есть детский лепет. Возьми систему управления фабрикой, программный косяк в которой может привести к открытию не того вентиля не туда, в результате чего жители ближайшего городка выблюют собственные кишки.
L>>Внимание, вопрос — это отладочный ассерт или эмуляция контракта, существующая и в релизе? К>Контракт, конечно. Слово "отладочный" мы не будем рассматривать, а то ведь и до gdb доберёмся.
Контракт в том или ином виде существует в любом коде. е это делается почти бесплатно, в плюсах — немножко секса на шаблонах, в идрисе — просто нельзя не сделать.
К>Причём, ассерты могут быть разными К>- чисто для -DDEBUG, "мамой клянус, мы всё проверили, если в релизе наиграется — нэ судьба"
И что, в тестирование передавать отладочную сборку? Мочить в сортире.
К>- панические, — "мамой клянус" и одновременно "врагу не сдаётся Варяг" — немедленно сдохнуть в релизе, пока UB не добралось до винчестера К>- с известными отступлениями (fallback) в пользу живучести (robustness) — исключение там кинуть, по дефолту что-нибудь вернуть...
А это уже как раз то, для чего хороши юнит-тесты. Вот есть юнит, в который передаются данные извне программы. От юзера, из датчиков и т.п. И вот сидит такой погромизд красивый, написал тест для случая корректных данных. А для случая некорректных — не может, так как непонятно, что делать-то. Думать надо, и не надеяться, что сойдет и так.
L>>Сначала неплохо было бы описать, что же такое полезное этот do_smth делает, чтобы можно было наблюдаемое поведение наблюсть. К>Ну это само собой. Вопрос был в том, нужно ли (и как) обвешать тестами аргумент n, если мы уже прописали контракт с помощью ассертов.
А вот не всегда контракт с помощью ассертов применим. Вот есть протокол, в ем слой. В слое оном — сообщение. В сообщении — элемент. В элементе — длина оного. И якобы длина элемента не должна превышать длину посылки. Только вот очень иногда почему-то превышает, контракт нарушается. Что делать будем?
Здравствуйте, netch80, Вы писали:
N>Я слышал, как такое рандомизированное тестирование находило проблемы через много лет после вроде бы стабилизации кода.
Здравствуйте, _hum_, Вы писали:
__>Всегда считал, что это ортогональные вещи, но, тут товарищ landerhigh заявил, что использование дебагера — каменный век:
Использование юнит-тестов это каменный век, а вовсе не использование дебага.
Классический юнит-тест по ТДД (это когда ты сначала пишешь тест, а потом реализуешь код) — крайне редко встречается в природе. На практике, тест пишется вместе с кодом, ты вначале можешь плохо представлять как в конечном итоге будет выглядеть интерфейс и как модуль будет использоваться. Юнит-тест это такая лаборатория, которая пишется вместе с кодом и которая позволяет начать запускать этот код в процессе ранней разработки (в том числе под отладчиком). До интеграции в продукт еще далеко, поэтому это единственный способ поработать с разрабатываемым модулем в живую (REPL-а то нет). Помимо этого, юнит-тест нужен для отладки билда, т.к. в системе сборки будут видны все зависимости этого модуля еще до того как он включится в основной продукт.
Юнит-тесты почти не находят ошибки. Они помогают разрабатывать код, это строительные леса, после стабилизации кода они практически не находят регрессии. Их запускают на CI сервере в основном для того, чтобы успокоить разработчиков, которые эти тесты написали.
Другое дело — функциональные тесты. На проектах, где я работал, большая часть регрессий находилась с помощью таких тестов, а юнит-тесты, написанные программистами, находили какие-то слезы. Но вот тут уже без отладчика сложно что-то сделать, т.к. тебе дают либо крэш-дамп, либо входной файл/базу данных. Здесь нужны именно классические навыки отладки + знание инструментов, таких как valgrind, asan и tsan. Та же история с фаззингом. Он находит много всего, больше всех ваших юнит-тестов вместе взятых, но его без отладчика опять же сложно использовать, а под asan-ом american fuzzy lop находит еще больше говна.
Вот например facebook недавно зарелизил ZStandard — новый комрессор (https://github.com/facebook/zstd). Вы там не найдете юнит-тестов вообще, но там есть fuzz-тесты и бенчмарки. Наверняка они еще и функциональные тесты используют, просто скрипты и датасеты для них в другом репозитории хранят. В общем, классические юнит-тесты сильно переоценены, а вот навыки отладки как раз наоборот — недооценены и редко встречаются. Тестировать код правильно тоже никто почти не умеет.
Здравствуйте, chaotic-kotik, Вы писали:
CK>Использование юнит-тестов это каменный век, а вовсе не использование дебага.
Наверное, поэтому технику юнит-тестирования до сих пор освоили считанные единицы.
CK>Классический юнит-тест по ТДД (это когда ты сначала пишешь тест, а потом реализуешь код) — крайне редко встречается в природе.
Это утопия. Хотя иногда встречается.
CK>На практике, тест пишется вместе с кодом, ты вначале можешь плохо представлять как в конечном итоге будет выглядеть интерфейс и как модуль будет использоваться. Юнит-тест это такая лаборатория, которая пишется вместе с кодом и которая позволяет начать запускать этот код в процессе ранней разработки (в том числе под отладчиком). До интеграции в продукт еще далеко, поэтому это единственный способ поработать с разрабатываемым модулем в живую (REPL-а то нет).
И уже одно это доказывает, что коду без юнит-тестов место в помоечъке (с).
Как минимум при разработке чего-то серьезнее CD-ejector
CK>Юнит-тесты почти не находят ошибки. Они помогают разрабатывать код, это строительные леса,
Юнит-тесты, внимание, это не инструмент для поиска ошибок. Это инструмент, помогающий эту ошибку не допустить. Он не гарантирует отсутствие ошибок, но при правильном пользовании позволяет свести вероятность их появления к минимуму
CK>после стабилизации кода они практически не находят регрессии.
Здрасте. Правильные юнит-тесты — это первая и зачастую самая главная линия регрессии. Они обязаны обнаруживать сломанную функциональность просто по определению.
CK>Другое дело — функциональные тесты. На проектах, где я работал, большая часть регрессий находилась с помощью таких тестов, а юнит-тесты, написанные программистами, находили какие-то слезы.
Бывает. Когда юнит-тесты пишут без понимания их сути или просто из-под палки.
CK>Но вот тут уже без отладчика сложно что-то сделать, т.к. тебе дают либо крэш-дамп, либо входной файл/базу данных. Здесь нужны именно классические навыки отладки + знание инструментов, таких как valgrind, asan и tsan.
С моего последнего проекта, переданного в тестирование, ни одного креш-дампа не пришло. Так и ушел в продакшен, не предоставив мне возможности отточить классические навыки отладки.
CK>Та же история с фаззингом. Он находит много всего, больше всех ваших юнит-тестов вместе взятых, но его без отладчика опять же сложно использовать
Ой. Фаззинг — это просто модное словечко, которым называют один из вариантов написания автотеста. Только вот верить в то, что он "много находит"... ну не знаю. У вас еще все впереди
CK>Вот например facebook недавно зарелизил ZStandard — новый комрессор (https://github.com/facebook/zstd). Вы там не найдете юнит-тестов вообще, но там есть fuzz-тесты и бенчмарки.
Нашел кучу говнотестов. Хорошая иллюстрация на тему "как делать не надо". Впрочем, под пиво да для кода на выброс, поддерживать который не собираются, пойдет.
Здравствуйте, landerhigh, Вы писали:
L>Здравствуйте, chaotic-kotik, Вы писали:
CK>>Использование юнит-тестов это каменный век, а вовсе не использование дебага. L>Наверное, поэтому технику юнит-тестирования до сих пор освоили считанные единицы.
Я не знаю где ты работаешь, но по моему опыту это не так. Практикуют все кому не лень.
CK>>На практике, тест пишется вместе с кодом, ты вначале можешь плохо представлять как в конечном итоге будет выглядеть интерфейс и как модуль будет использоваться. Юнит-тест это такая лаборатория, которая пишется вместе с кодом и которая позволяет начать запускать этот код в процессе ранней разработки (в том числе под отладчиком). До интеграции в продукт еще далеко, поэтому это единственный способ поработать с разрабатываемым модулем в живую (REPL-а то нет).
L>И уже одно это доказывает, что коду без юнит-тестов место в помоечъке (с). L>Как минимум при разработке чего-то серьезнее CD-ejector
Можно с тем же успехом написать консольное приложение, которое будет как-то использовать код. Для этих целей не обязательно использовать именно юнит-тесты.
L>Юнит-тесты, внимание, это не инструмент для поиска ошибок. Это инструмент, помогающий эту ошибку не допустить. Он не гарантирует отсутствие ошибок, но при правильном пользовании позволяет свести вероятность их появления к минимуму
С последней фразой не согласен. До стабилизации — да, юнит-тесты помогают искать ошибки, код часто меняется и запускать функциональные тесты на каждый чих — дорого и сложно. А вот юнит-тесты — в самый раз. Поэтому разработчики их и любят, пока пишешь код — видишь эффект от их использования, ну а когда код стабилизирован — не видишь, но и с этим кодом ты не работаешь. А в нем продолжают находиться ошибки, но уже другими средствами.
Основные проблемы юнит-тестов в том что они а) изолированны б) пишутся разработчиками. Изолированность, означает что тест не увидит регрессию, которая появилась из-за изменений внешней среды (изменился код, использующий твой код, по отдельности все тесты проходят, но приложение падает). Bias разработчика в том, что его код всегда будет использоваться корректно и так как он(она) предполагал. В общем, ты про эмерджентные свойства системы слышал наверное, когда в системе появляются новые свойства, которых нет у компонентов системы. Вот это баги чаще всего.
CK>>после стабилизации кода они практически не находят регрессии. L>Здрасте. Правильные юнит-тесты — это первая и зачастую самая главная линия регрессии. Они обязаны обнаруживать сломанную функциональность просто по определению.
Потому что старый код редко меняется, пишется новый. Новый код неправильно конфигурирует твой компонент, который проходит все юнит-тесты. В тестах нового кода твой компонент замокан. Баг в стелс режиме едет на прод.
CK>>Другое дело — функциональные тесты. На проектах, где я работал, большая часть регрессий находилась с помощью таких тестов, а юнит-тесты, написанные программистами, находили какие-то слезы. L>Бывает. Когда юнит-тесты пишут без понимания их сути или просто из-под палки.
Либо код покрытый юнит-тестами хреново тестируют дальше, поэтому большая часть багов после них не находится. Хорошую систему функциональных тестов сделать куда сложнее. Существуют классы ошибок, которые модульным тестирование невозможно обнаружить в принципе. Ну и еще раз повторю — юнит-тесты не находят ошибки взаимодействия компонентов.
L>С моего последнего проекта, переданного в тестирование, ни одного креш-дампа не пришло. Так и ушел в продакшен, не предоставив мне возможности отточить классические навыки отладки.
У меня тоже такое было. Все зависит от проекта. Когда я один делал проект (сравнительно мало SLOC) такое было. Когда я работал на больших проектах — такого не было.
L>Ой. Фаззинг — это просто модное словечко, которым называют один из вариантов написания автотеста. Только вот верить в то, что он "много находит"... ну не знаю. У вас еще все впереди
А ты использовал когда-нибудь фаззинг?
CK>>Вот например facebook недавно зарелизил ZStandard — новый комрессор (https://github.com/facebook/zstd). Вы там не найдете юнит-тестов вообще, но там есть fuzz-тесты и бенчмарки. L>Нашел кучу говнотестов. Хорошая иллюстрация на тему "как делать не надо". Впрочем, под пиво да для кода на выброс, поддерживать который не собираются, пойдет.
Это не код на выборс, а большой инфраструктурный проект для fb и модульных тестов там нет (что за говнотест ты там нашел?). Можешь посмотреть на код google brotli или LZ4, там та же песня. Причина в том, что ты не напишешь нормальный юнит-тест для компрессора. Что там тестировать можно модульно? Как ты себе это представляешь? Ну вот есть компонент генерирующих хаффмановский словарь — как ты узнаешь, правильно ли он сгенерирован? Захардкодишь веса или сделаешь параллельную реализацию?
Компрессоры тестируют с помощью round-trip тестов (есть и в zstd и в brotli и в lz4). Это когда берут кучу файлов и каждый из них сначала сжимают, а потом разжимают и сравнивают с оригиналом. Фаззинг тут еще неплохо работает.