Всегда считал, что это ортогональные вещи, но, тут товарищ landerhigh заявил, что использование дебагера — каменный век: L>Вот смотри. Сейчас ваш процесс выглядит примерно так. L>Тимлид: Нужно [исправить баг А|реализовать стори B|быстро что-то подправить в модуле C] L>Разраб: Yes, sir! L>Разработчк изменяет пару строк кода где-то очень глубоко в кишках исходников. И говорит "готово!". А тимлид "А ты проверил?". И разработчик идет курить бамбук, пока все пересобирается, чтобы потом еще полчаса вручную докликивать программу в состояние, в котором он может косвенно проверить часть функциональности внесенного изменения. Обнаруживает баг в багфиксе и все по-новому. Два часа рабочего времени жестоко убито. Мотивация утопилась в унитазе, а профессиональная гордость хлещет водяру из горла. L>А вот как выглядит процесс разработки здорового человека:
текст
L>Разраб: так, что у нас тут в беклоге вниманием обделено? О! Поддержка формата времени протокола (страшная аббривеатура) в сетевом модуле. L>Разраб: Ага, понятно, наконец-то мы будет показывать настоящее время, а не забивать его <unknown>. Так, где у нас спецификация... L>(5 минут) L>Разраб: Я хочу того же, что курили аффтфры спецификации протокола. Открывает Wireshark и выдергивает из сетевой сессии с прибором сообщения, содержащие оные типы. В случае отсутствия прибора или wireshark'a изобретает Hex-dump нужного PDU самостоятельно или заимствует его из спецификации, если аффтары озаботились примерами. В процессе придумывает забавные примеры невалидного элемента. Добавляет в протокола поддержку нового типа, а в проект юнит-тестов — проверку работы парсера, используюя награбленные или придуманные сырые данные. Не забывает тест, в котором на вход парсера подается мусор или специально инвалидированные данные. Щелкает "билд" на юнит-тест проекте. Оный собирает измененную либу парсера протокола, собирается сам и запускается. В консоли — 2 failed tests, которые разработчик нарочно зафейлил, чтобы проверить сам себя. Исправляет проваленные тесты, добавляет новые для граничных условий и обнаруженных серых пятен в спецификации, перезапускает билд. Через 5 минут — XXX tests passed.
L>В итоге — полностью реализовананя новая функциональность, покрытая тестами и даже в некоторых случаях прошедшая regression — ранее написанные тесты покажут, если новая функциональность вносит breaking change. Причем, за время, которого не хватило бы на полную сборку проекта и хотя бы один ручной тестовый прогон. Причем, проект автоматически прогоняет даже такие условия, которые во время ручного теста проверить невозможно.
На мой ответ: "извините, но вы здесь описали вариант, когда либо готовый код правится, либо к готовому коду добавляется еще один готовый функциональный блок, и проверяется их совместимость. я нигде не увидел собственного написания.
ну, простейший пример — вам сказал тим лид — нужно написать функцию, транспонирующую матрицу. как в этом случае будет выглядеть работа с тестами без дебагера?" последовали опять общие фразы.
Потому вопрос в зал, может кто-нибудь, кто придерживается мнения landerhigh, на примере задачи написания функции транспонирования матрицы показать, как тесты заменяют работу с дебагером?
UPD. L>Вот сходил ты в курилку и придумал, что назовешь свою функцию L>
L> Matrix transpose(const Matrix& original);
L>
L>Налил кофе и написал следующие три теста L>
L> TEST(transposeMatrix, transposeNegative)
L> {
L> // this is more a border case test
L> ASSERT_TRUE(transpose(emptyMatrix).empty()); // Also checks that we don't crash if matrix is empty
L> }
L> TEST(transposeMatrix, transposePositiveSymmetrical)
L> {
L> Matrix symmetricalMatrix = ...; // Initialize
L> Matrix expected = ...; // Manually transposed matrix
L> ASSERT_EQ(expected, transpose(symmetricalMatrix));
L> ASSERT_EQ(symmetricalMatrix, transpose(transpose(symmetricalMatrix)));
L> }
L> TEST(transposeMatrix, transposePositiveAsymmetrical)
L> {
L> Matrix asymmetricalMatrix = ...; // Initialize
L> Matrix expected = ...; // Manually transposed matrix
L> ASSERT_EQ(expected, transpose(asymmetricalMatrix ));
L> ASSERT_EQ(asymmetricalMatrix , transpose(transpose(asymmetricalMatrix )));
L> }
L>
L>Потом написал собственно код транспонирования. L>Запустил тест. На все-про-все 5 минут времени и у тебя гораздо более полное покрытие, нежели ты может добиться ручными проверками в отладчике.
L>Ты мне лучше скажи, зачем тут вообще отладчик может понадобиться?
ответ:
так а где здесь код-то самого транспонирования? ведь ошибки именно там кроются. и если вы увидите срабатывание ассертов, что делать дальше будете?
как, например, обнаружите глупейшую ошибку типа Matrix[i][j] = Matrix[j][i] для in-place трансполнирования квадратной матрицы?
Здравствуйте, _hum_, Вы писали:
__>Всегда считал, что это ортогональные вещи, но, тут товарищ landerhigh заявил, что использование дебагера — каменный век:
Не каменный век, а каменный цветок. Иногда выходит, а иногда нет.
Код, обложенный контрактами (в т.ч. тестами), конечно, гораздо легче отлаживать, чем ковыряться по-живому.
Во всяком случае, если что-то поломалось, то мы узнаем это на более ранней стадии, где проверили контракт (в ассерте, в тесте), а не тогда, когда неопределённое поведение уже устроило погром.
Но любое покрытие кода — хоть в виде тестов, хоть в виде ручного прохождения тех или иных сценариев работы с программой — требует известной выдумки.
(У нас С++, а не Эйфель, который принудительно покрывает всё, до чего может дотянуться).
Дебаг — это творческий процесс создания теста.
Делаем всё по шагам и вручную, смотрим, что там хорошо и что плохо, запоминаем, а потом насуём в код недостающие формальные контракты, ровно в нужном количестве.
Или, скажем, если какая-то проверка отвалилась, значит, неопределённое поведение возникло до неё. И потребуется отследить предысторию, хотя бы в объёме стека вызовов. А как ты напишешь новый нужный тест, если не проанализируешь стек?
__>ну, простейший пример — вам сказал тим лид — нужно написать функцию, транспонирующую матрицу. как в этом случае будет выглядеть работа с тестами без дебагера?" последовали опять общие фразы.
А зачем там дебаггер? Дебаггер позволяет за очень длительное время проверить ровно один тест кейс вручную. Вернее, ровно один тест-кейс с tracing'ом
L>>Ты мне лучше скажи, зачем тут вообще отладчик может понадобиться?
__>ответ: __>так а где здесь код-то самого транспонирования? ведь ошибки именно там кроются. и если вы увидите срабатывание ассертов, что делать дальше будете?
Править код, естественно
__>как, например, обнаружите глупейшую ошибку типа Matrix[i][j] = Matrix[j][i] для in-place трансполнирования квадратной матрицы?
Иногда, в редчайших случаях можно запустить дебаггер. В наиредчайших.
Тест может/должен выявить, что ошибка проявляется на inplace транспонировании квадратных матриц. Смотрим в код, находим ошибку
Здравствуйте, Кодт, Вы писали:
К>Дебаг — это творческий процесс создания теста. К>Делаем всё по шагам и вручную, смотрим, что там хорошо и что плохо, запоминаем, а потом насуём в код недостающие формальные контракты, ровно в нужном количестве.
Абсолютно согласен. Я обычно поначалу в дебагере несколько раз прохожу, и выясняю в процессе этого, какие варианты я не учел, и оперативно добавляю их в код. Если писать юнит тесты и краевые условия выявлять по ним, то чтобы нормально всё сделать вместо получаса уйдет рабочий день
К>Или, скажем, если какая-то проверка отвалилась, значит, неопределённое поведение возникло до неё. И потребуется отследить предысторию, хотя бы в объёме стека вызовов. А как ты напишешь новый нужный тест, если не проанализируешь стек?
Ну, можно каждую строчку алгоритма выделять например, в отдельную функцию, и обкладывать её тестами, но это на пару порядков больше работы.
Юнит тесты хороши для регрессионного тестирования, когда контракты кода уже зафиксированы и требуется чтобы ничего не поломалось при возможном последующем рефакторинге. Как их использовать при разработке, когда контракты не определены и уточняются, я
Было бы интересно посмотреть на какой-нибудь простой пример разработки через тесты, подробно описаный, если свидетели иеговыюнит-тестирования не сочтут за труд предоставить такой
Здравствуйте, Marty, Вы писали:
M>Это если без фанатизма. Но это не варианты landerhigh'а и __kot'а
Видишь ли... когда приходится писать на разных платформах, приходит понимание того, что отладчик — вещь платформеннозависимая. И навыки работы со студийным отладчиком никак не помогут в случае gdb, а если код вообще собирается для Соляриса или какого-нибудь AIX, то получается еще смешнее. Непереносимый навык, в общем. Это одна из причин, по которой я не пользуюсь отладчиком.
Но главная причина состоит в том, что отладчик как инструмент разработки контрпродуктивен. Для проведения вскрытия (разбора крешдампа) — вещь отличная, но использовать его при разработке?
Делать первый прогон под отладчиком, чтобы выяснить, какие варианты не учтены? Ну не знаю, мне для этого отладчик не нужен, и вообще звучит странно. Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
Здравствуйте, Marty, Вы писали:
M>Если писать юнит тесты и краевые условия выявлять по ним, то чтобы нормально всё сделать вместо получаса уйдет рабочий день
Краевые условия по тестам не выявляются. Они выявляются в процессе написания собственно кода, а юнит тесты пишутся для проверки правильности обработки этих краевых условий.
Про полчаса и рабочий день насмешил. Обычно наоборот, полдня в дебаггерре, потом еще полдня на кое-какерство в коде, ибо после нескольких часов в отладчике так и не приходит понимание, чего же там не так. Или приходит, и оказывается, что код нужно почти полностью переписать, но становится жалко усилий и начинается костылестроение.
При правильной организации работы полный набор тестов для нового тестируемого юнита пишется за очень короткое время. Практически на автомате. Этого времени не хватит даже на однократный запуск отладчика.
К>>Или, скажем, если какая-то проверка отвалилась, значит, неопределённое поведение возникло до неё. И потребуется отследить предысторию, хотя бы в объёме стека вызовов. А как ты напишешь новый нужный тест, если не проанализируешь стек?
M>Ну, можно каждую строчку алгоритма выделять например, в отдельную функцию, и обкладывать её тестами, но это на пару порядков больше работы.
Это смотря что ставить целью
M>Было бы интересно посмотреть на какой-нибудь простой пример разработки через тесты, подробно описаный, если свидетели иеговыюнит-тестирования не сочтут за труд предоставить такой
На пример (результат) смотреть бессмысленно. Смотреть нужно на процесс достижения этого результата. Как сделать это на форуме —
Здравствуйте, Кодт, Вы писали:
К>Или, скажем, если какая-то проверка отвалилась, значит, неопределённое поведение возникло до неё. И потребуется отследить предысторию, хотя бы в объёме стека вызовов. А как ты напишешь новый нужный тест, если не проанализируешь стек?
Здравствуйте, _hum_, Вы писали:
__>так а где здесь код-то самого транспонирования?
Он не нужен. Нужен код тестов.
__>ведь ошибки именно там кроются. и если вы увидите срабатывание ассертов, что делать дальше будете?
Покупать акции Газпрома, а что еще можно сделать?
Разбираться, конечно же, в причинах срабатывания ассертов.
__>как, например, обнаружите глупейшую ошибку типа Matrix[i][j] = Matrix[j][i] для in-place трансполнирования квадратной матрицы?
Вы не поверите...
Предположим, что у нас и правда завелась функция
void transposeInPlace(Matrix& mtx);
пишем тест для нее. Естественно, предполагается, что у нас есть оператор сравнения двух матриц.
подразумевает, что явного in-place транспонирования не предусмотрено. RVO тоже не сделает это транспонирование in-place, но мы все слышали про странные глюки в компиляторах, поэтому придумаем такое, чисто для очистки совести
Здравствуйте, landerhigh, Вы писали:
M>>Это если без фанатизма. Но это не варианты landerhigh'а и __kot'а
L>Видишь ли... когда приходится писать на разных платформах, приходит понимание того, что отладчик — вещь платформеннозависимая. И навыки работы со студийным отладчиком никак не помогут в случае gdb, а если код вообще собирается для Соляриса или какого-нибудь AIX, то получается еще смешнее. Непереносимый навык, в общем. Это одна из причин, по которой я не пользуюсь отладчиком.
Видишь ли, когда набираешься опыта, то начинаешь понимать, что windbg по удобству хоть и превосходит gdb, но не ушел от него далеко. Тут я спорить даже не буду, я согласен, что гораздо проще написать 100500 юнит-тестов, чем пытаться отлаживаться при помощи gdb. И я, по возможности, избегаю использования gdb. Просто я пишу кроссплатформенный код, который, как минимум, работает под виндой, линуксами и фряхами, и отлаживаю его в студии msvc, и мне этого хватает. А для отладки платформозависимых вещей мне хватает printf'ов.
Так что навык вполне переносимый — никто не мешает писать и отлаживать в студии переносимый код, который будет потом работать и под линуксом, и под аиксом
L>Но главная причина состоит в том, что отладчик как инструмент разработки контрпродуктивен. Для проведения вскрытия (разбора крешдампа) — вещь отличная, но использовать его при разработке?
Использование отладчика позволяет не писать 100500 тестов, которые после устаканивания контрактов интерфейсов будут выкинуты или переписаны, по мере изменения контраков
L>Делать первый прогон под отладчиком, чтобы выяснить, какие варианты не учтены? Ну не знаю, мне для этого отладчик не нужен, и вообще звучит странно.
Ну, ты молодец, если умеешь сразу всё предусмотреть в уме
L>Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
И? Ты пишешь тесты, которые проверяют контракты модуля. Нормально. Я тоже так делаю. Просто когда что-то не работает в твоем алгоритме в соответсвии с контрактом, ты пишешь 100500 тестов, а я прохожу эту функцию под отладчиком, и смотрю, на каком шаге и что пошло не так. Я за пол-часа справился, а ты уже вторую неделю пишешь юнит-тесты. Не, если тебя на твоей работе терпят с таким подходом — ок, я не хочу занять твоё место, сиди там и дальше, пиши юнит-тесты. Пока я пишу продукт.
Здравствуйте, landerhigh, Вы писали:
M>>Если писать юнит тесты и краевые условия выявлять по ним, то чтобы нормально всё сделать вместо получаса уйдет рабочий день
L>Краевые условия по тестам не выявляются. Они выявляются в процессе написания собственно кода, а юнит тесты пишутся для проверки правильности обработки этих краевых условий.
Интересно, как ты выявляшь краевые условия в процессе написания кода. Расскажи об этом поподробнее. Ты всё сразу продумываешь? И попадаешь в точку? Я так не умею. Я обычно подаю что-то на вход, ожидаю что-то на выходе. Если на входе не то, начинаю в отлпдчике по шпгпи проверять, где косяк
L>Про полчаса и рабочий день насмешил. Обычно наоборот, полдня в дебаггерре, потом еще полдня на кое-какерство в коде, ибо после нескольких часов в отладчике так и не приходит понимание, чего же там не так. Или приходит, и оказывается, что код нужно почти полностью переписать, но становится жалко усилий и начинается костылестроение.
При юнит-тестировании говнокод из эскизного концепт-проекта не надо переписывать?
L>При правильной организации работы полный набор тестов для нового тестируемого юнита пишется за очень короткое время. Практически на автомате. Этого времени не хватит даже на однократный запуск отладчика.
Ну, отладка у меня секунд за 5 запускается. Не пойму, какой набор тестов можно написать за это время. Или я просто не в курсе правильной организации?
К>>>Или, скажем, если какая-то проверка отвалилась, значит, неопределённое поведение возникло до неё. И потребуется отследить предысторию, хотя бы в объёме стека вызовов. А как ты напишешь новый нужный тест, если не проанализируешь стек?
M>>Ну, можно каждую строчку алгоритма выделять например, в отдельную функцию, и обкладывать её тестами, но это на пару порядков больше работы.
L>Это смотря что ставить целью
M>>Было бы интересно посмотреть на какой-нибудь простой пример разработки через тесты, подробно описаный, если свидетели иеговыюнит-тестирования не сочтут за труд предоставить такой
L>На пример (результат) смотреть бессмысленно. Смотреть нужно на процесс достижения этого результата. Как сделать это на форуме —
Результат как раз не пример. Нужно расписать процесс, все шаги от и до. На форуме это делается просто — берешь и рассказываешь. Последовательно, что и как делал, что на входе, каков результат.
Здравствуйте, Marty, Вы писали:
L>>Но главная причина состоит в том, что отладчик как инструмент разработки контрпродуктивен. Для проведения вскрытия (разбора крешдампа) — вещь отличная, но использовать его при разработке? M>Использование отладчика позволяет не писать 100500 тестов, которые после устаканивания контрактов интерфейсов будут выкинуты или переписаны, по мере изменения контраков
100500 тестов не требуется. На юнит обычно нужно три набора тестов (три теста). И написать их выйдет быстрее, чем ковырять их в отладчике. И они станут бесплатной регрессией, когда начнут изменяться какие-то юниты.
L>>Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
M>И? Ты пишешь тесты, которые проверяют контракты модуля. Нормально. Я тоже так делаю. Просто когда что-то не работает в твоем алгоритме в соответсвии с контрактом, ты пишешь 100500 тестов, а я прохожу эту функцию под отладчиком, и смотрю, на каком шаге и что пошло не так. Я за пол-часа справился, а ты уже вторую неделю пишешь юнит-тесты.
Нет, не так. Ты полчаса (скорее полдня, кого ты обманываешь?) ковырялся в отладчике, а мне этого делать вообще не пришлось, поскольку у меня покрытие сценариев для определенных участков кода стремится к 100% и вероятность случаев, когда что-то не работает в покрытом тестами алгоритме, стремятся к нулю. Это не значит, что все сразу магически работает, более высокоуровневые баги случаются. Только они, как правило, имеют определенную природу. И самое забавное, что их причина либо очевидна, либо выявляется банальной дедукцией, что проверяется написанием простого тестового сценария, который после исправления бага становится regression-тестом.
M>Не, если тебя на твоей работе терпят с таким подходом — ок, я не хочу занять твоё место, сиди там и дальше, пиши юнит-тесты. Пока я пишу продукт.
О, переход на личности во втором сообщении .
А еще нас обвиняют в фанатизме.
Здравствуйте, landerhigh, Вы писали:
L>100500 тестов не требуется. На юнит обычно нужно три набора тестов (три теста). И написать их выйдет быстрее, чем ковырять их в отладчике. И они станут бесплатной регрессией, когда начнут изменяться какие-то юниты.
Не одними юнитами.
А интеграционные тесты — или ты устроишь комбинаторный взрыв, покрывая все мыслимые сочетания, — и будешь гонять эту бомбу каждый раз при изменениях, и тогда узнаешь, что регрессия нифига не бесплатна; или перестанешь фанатеть и покроешь исключительно проблемные места; а какие места проблемные, выяснишь во время отладки.
L>>>Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
Интеграционный тест должен будет потратить столько же времени для проверки, наигрывается или нет тот самый баг. Если какой-то больной модуль ровно через год запускается.
Тебе придётся создавать специальную тестовую среду, джонни-сделай-монтаж, которую можно потом и сотрудникам-тестерам запускать.
Кроме того, тебе придётся создать инфраструктуру для эмуляции действий пользователя.
Например, реальный баг: после нажатия на кнопку [_] окошко не сворачивается, а захватывает мышь и тупит. Что это за хрень?
Ты пишешь тест: mainwindow()->minimize(). Тест проходит.
Пишешь mainwindow()->sendmessage(WM_COMMAND, ID_MINIMIZE). Проходит.
Понимаешь, что дело именно в клике мыши. Пишешь функцию point_of_x_button(), и функцию посылки клика mousedown, mouseup. Посылаешь окну. Тест, блин, проходит!
И т.д. и т.п., делаешь очень много работы, получаешь нахаляву кучу тестов для будущей регрессии, пока не докапываешься до правды: глюк был в обработчике WM_NCHITTEST, причём, не по всей площади кнопки, а только на её краю. И что самое смешное, это следствие кривых рекомендаций самого микрософта, который в 7, 8 и 10 продолжает экспериментировать с аэро и метро, и чьи программы тоже ведут себя странновато. (Ну, когда я знаю правду, я знаю, куда смотреть).
А может быть, проще было не выдумывать тесты, а запустить дебаггер и Spy++, и смотреть, как диспетчится мышь?
О да, у тебя теперь гора тестов для регрессии.
Хотя правильно будет выкинуть её, и оставить только покрытие багфикса. Ну, инфраструктуру-то заначить, ещё пригодится.
M>>И? Ты пишешь тесты, которые проверяют контракты модуля. Нормально. Я тоже так делаю. Просто когда что-то не работает в твоем алгоритме в соответсвии с контрактом, ты пишешь 100500 тестов, а я прохожу эту функцию под отладчиком, и смотрю, на каком шаге и что пошло не так. Я за пол-часа справился, а ты уже вторую неделю пишешь юнит-тесты.
L>Нет, не так. Ты полчаса (скорее полдня, кого ты обманываешь?) ковырялся в отладчике, а мне этого делать вообще не пришлось, поскольку у меня покрытие сценариев для определенных участков кода стремится к 100% и вероятность случаев, когда что-то не работает в покрытом тестами алгоритме, стремятся к нулю. Это не значит, что все сразу магически работает, более высокоуровневые баги случаются. Только они, как правило, имеют определенную природу. И самое забавное, что их причина либо очевидна, либо выявляется банальной дедукцией, что проверяется написанием простого тестового сценария, который после исправления бага становится regression-тестом.
Не обманывай себя. Тесты пишет человек. Вот насколько у тебя хватило фантазии покрыть предметную область, настолько ты и написал тесты.
Сделал, к примеру, транспонирование матрицы на месте, но она у тебя, скажем, стреляет по памяти при НОД(x,y)=17, а стрельба сходит с рук при x<100.
Да, ты написал тесты о том, что матрица транспонируется. Разумеется, только при маленьких размерах и выборочно (ну не перебирать же все x от 1 до 200 и y от 1 до 200).
А что ты сделал для проверки стрельбы?
Здравствуйте, landerhigh, Вы писали:
L>Но главная причина состоит в том, что отладчик как инструмент разработки контрпродуктивен. Для проведения вскрытия (разбора крешдампа) — вещь отличная, но использовать его при разработке?
Как минимум ещё случаи
1. Странности работы платформы/фреймворка/etc., не документированные, плохо описанные, нарушающие документацию, etc.
2. Сходно с первым, но другая вводная — оОбучение новым языкам/фреймворкам — когда надо прочувствовать происходящее в виде последовательности состояний и разницы между ними. Где что за что цепляется, где на каком шаге что успело измениться...
тесты на всё это ты не нарисуешь, потому что в принципе не представляешь, что проверять, или почему оно не работает, несмотря ни на что.
L>Делать первый прогон под отладчиком, чтобы выяснить, какие варианты не учтены? Ну не знаю, мне для этого отладчик не нужен, и вообще звучит странно. Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
Увы. И когда тогда оказывается, что одна из самых базовых архитектурных идей была нерабочей...
именно поэтому agile (поклон соседнему треду) хорош тем, что там возможность запустить "в составе этой самой программы" с самого начала делается как можно раньше, чтобы найти все подводные грабли.
Здравствуйте, netch80, Вы писали:
N>Как минимум ещё случаи N>1. Странности работы платформы/фреймворка/etc., не документированные, плохо описанные, нарушающие документацию, etc.
В большинстве случаев их тоже можно протестировать автоматическими тестами. Более того, бывают (я вообще подозреваю, что чаще всего встречаются) такие странности, которые под отладчиком в принципе не отлавливаются и не повторяются. Более того, зачастую иначе, как синтетическим тестом, многие странности даже и не повторить.
У меня был эпичный случай неведомого бага из-за недодокументирования, под отладчиком не повторяющийся, и он привел к фиксу в boost::context
N>2. Сходно с первым, но другая вводная — оОбучение новым языкам/фреймворкам — когда надо прочувствовать происходящее в виде последовательности состояний и разницы между ними. Где что за что цепляется, где на каком шаге что успело измениться...
Только отладчик при этом — это тот самый противогаз, который нужно одевать, когда стоя и в гамаке.
N>тесты на всё это ты не нарисуешь, потому что в принципе не представляешь, что проверять, или почему оно не работает, несмотря ни на что.
L>>Делать первый прогон под отладчиком, чтобы выяснить, какие варианты не учтены? Ну не знаю, мне для этого отладчик не нужен, и вообще звучит странно. Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год. N>Увы. И когда тогда оказывается, что одна из самых базовых архитектурных идей была нерабочей...
А кто-то покрывал тестами все модули с самого начала и никакого "оказалось" не случилось — косяк был выявлен на ранней стадии, а скорее всего даже и не случился, так как на неудачную архитектуру и тесты плохо ложатся.
N>именно поэтому agile (поклон соседнему треду) хорош тем, что там возможность запустить "в составе этой самой программы" с самого начала делается как можно раньше, чтобы найти все подводные грабли.
Да, это особенно помогает, когда пишется абсолютно новый коммуникационный уровень для поддержки нового устройства, которого в железе еще не существует. Не говоря уже о том, что ручное тестирование никогда не найдет все подводные грабли.
Здравствуйте, Кодт, Вы писали:
К>А интеграционные тесты — или ты устроишь комбинаторный взрыв, покрывая все мыслимые сочетания,
Вот для того, чтобы не устраивать комбинаторный взрыв в интеграционных тестах, и нужны юнит-тесты, чтобы все возможные варианты работы юнита тестировать в изоляции. Интеграционные тесты должны тестировать интеграцию, не более.
К>- и будешь гонять эту бомбу каждый раз при изменениях, и тогда узнаешь, что регрессия нифига не бесплатна; или перестанешь фанатеть и покроешь исключительно проблемные места; а какие места проблемные, выяснишь во время отладки.
Проблемные места имеют такое противное свойство, что могут возникать и после того, как их вроде бы как все "выяснили" во время отладки.
L>>>>Не говоря уже о том, что от написания какого-то кода, зарытого глубоко под капот системы, засунутой под капот подсистемы, засунутой в подмодуль модуля основной программы, до возможности его запустить в составе этой самой программы, может пройти полгода-год.
К>Интеграционный тест должен будет потратить столько же времени для проверки, наигрывается или нет тот самый баг. Если какой-то больной модуль ровно через год запускается.
Не должен. Это ответственность юнит-теста.
К>Например, реальный баг: после нажатия на кнопку [_] окошко не сворачивается, а захватывает мышь и тупит. Что это за хрень? К>Ты пишешь тест: mainwindow()->minimize(). Тест проходит.
Ничего не пишу. От слова совсем. Тесты уже есть, и тесты позволяют исключить 100500 юзкейсов из подозрения. В данном случае, тесты гарантируют, что если сообщение доходит до обработчика бизнес-логики, то оно обрабатывается в 100% случаев. Значит, проблема либо в том, что сообщение как-то неправильно обрабатывается в нашем обработчике, либо имеем пример недокументированного поведения операционной системы. То есть вариантов вообще практически нет. Изолируем, включаем лог в обработчике и смотрим, что приходит на вход. То есть вообще ничего делать не надо. 15 минут — и WFT найден.
К>А может быть, проще было не выдумывать тесты, а запустить дебаггер и Spy++, и смотреть, как диспетчится мышь?
Ничего выдумывать не нужно, все уже есть. Включаю лог и смотрю. Если уж совсем припрет, можно и отдалчик привлечь, как крайнюю меру.
К>Не обманывай себя. Тесты пишет человек.
Хмм, а в отладчик кто смотрит, супермен, что ли?
K>Вот насколько у тебя хватило фантазии покрыть предметную область, настолько ты и написал тесты.
Особой фантазии для хорошего покрытия не требуется. Требуется желание разбивать модули так, чтобы свести домены вариантов использования к минимуму и покрывать их в принципе стандартным способом.
К>Сделал, к примеру, транспонирование матрицы на месте, но она у тебя, скажем, стреляет по памяти при НОД(x,y)=17, а стрельба сходит с рук при x<100. К>Да, ты написал тесты о том, что матрица транспонируется. Разумеется, только при маленьких размерах и выборочно (ну не перебирать же все x от 1 до 200 и y от 1 до 200). К>А что ты сделал для проверки стрельбы?
Во-первых, don't write shit code. Моей фантазии не хватает на то, чтобы представить, как можно в коде транспонирования матрицы случайно такой косяк допустить.
Во-вторых, проверка стрельбы по памяти не входит в ответственность юнит-тестов. Я в принципе не уверен, что ее можно в общем случае обнаружить в момент собственно выстрела.
В-третьих, используя тот факт, что дважды транспонированная матрица равна самой себе, в случае подобных подозрений можно написать и автотест, который переберет все варианты и сделает это за O(x*y) время, что в 21 веке означает примерно "мгновенно".
Здравствуйте, Marty, Вы писали:
L>>Краевые условия по тестам не выявляются. Они выявляются в процессе написания собственно кода, а юнит тесты пишутся для проверки правильности обработки этих краевых условий.
M>Интересно, как ты выявляшь краевые условия в процессе написания кода. Расскажи об этом поподробнее. Ты всё сразу продумываешь? И попадаешь в точку? Я так не умею. Я обычно подаю что-то на вход, ожидаю что-то на выходе. Если на входе не то, начинаю в отлпдчике по шпгпи проверять, где косяк
Что значит "продумываешь"? Я пишу код. Я знаю, как он будет работать, я знаю, какие у него будут краевые условия. А можно как-то иначе писать код?
Ну ладно, код пишут человеки, которые все на свете знать не могут, и код этот часто вызывает другой код, который вообще является черным ящиком. Вот вызываешь ты из своего кода функцию A, но не уверен, что знаешь, как она себя ведет при подаче на вход определенных данных, которые твой код может выдать. Документация далека от исчерпывающей. Пишешь тест, который именно такие условия и проверяет, и вопрос решается раз и навсегда.
M>При юнит-тестировании говнокод из эскизного концепт-проекта не надо переписывать?
Не знаю. Практически все мои прототипы становились основой для production code, т.к. писать говнокод при использовании TDD и его аналогов получается плохо.
L>>При правильной организации работы полный набор тестов для нового тестируемого юнита пишется за очень короткое время. Практически на автомате. Этого времени не хватит даже на однократный запуск отладчика.
M>Ну, отладка у меня секунд за 5 запускается. Не пойму, какой набор тестов можно написать за это время. Или я просто не в курсе правильной организации?
Отладка чего? Что именно ты запускашь? Рабочую систему? Так ее после этих 5 секунд нужно еще полчаса вручную докликать до состояния, в котором можно что-то проверить. И что делать, если до самой возможности запуска рабочей системы должно пройти еще три человеко-года? Что делаешь, если нужно протестировать ситуацию, которая происходит лишь в определенных условиях, которые под отладчиком не воспроизводятся или требуют определенного, возможно, не совсем корректного поведения внешней системы?
Или ты запускаешь специально написанные тестовые программки, реализующие разные варианты работы?
M>>>Было бы интересно посмотреть на какой-нибудь простой пример разработки через тесты, подробно описаный, если свидетели иеговыюнит-тестирования не сочтут за труд предоставить такой
Ну так я в соседней ветки описывал, как разрабатывается транспонирование матрицы с использованием тестов. Маловато?
Здравствуйте, landerhigh, Вы писали:
N>>Как минимум ещё случаи N>>1. Странности работы платформы/фреймворка/etc., не документированные, плохо описанные, нарушающие документацию, etc. L>В большинстве случаев их тоже можно протестировать автоматическими тестами. Более того, бывают (я вообще подозреваю, что чаще всего встречаются) такие странности, которые под отладчиком в принципе не отлавливаются и не повторяются. Более того, зачастую иначе, как синтетическим тестом, многие странности даже и не повторить.
То в основном всякие темпоральные гейзенбаги. Я имел в виду случаи попроще. Есть цепочка вызовов API, где последние используют результат первых, что-то нарушено, единственный способ выяснить — всунуться между ними, а разносить в отдельные функции смысла нет аж совсем.
N>>2. Сходно с первым, но другая вводная — оОбучение новым языкам/фреймворкам — когда надо прочувствовать происходящее в виде последовательности состояний и разницы между ними. Где что за что цепляется, где на каком шаге что успело измениться... L>Только отладчик при этом — это тот самый противогаз, который нужно одевать, когда стоя и в гамаке.
?
N>>Увы. И когда тогда оказывается, что одна из самых базовых архитектурных идей была нерабочей... L>А кто-то покрывал тестами все модули с самого начала и никакого "оказалось" не случилось — косяк был выявлен на ранней стадии, а скорее всего даже и не случился, так как на неудачную архитектуру и тесты плохо ложатся.
В одном проекте изначальное решение понадеяться на саморегулирование нагрузочной способности под постоянным потоком данных оказалось самой большой ошибкой и вылечилось, фактически, переходом на поллинг вместо пуша. Такое тестами не выловится при любом покрытии "с самого начала".
N>>именно поэтому agile (поклон соседнему треду) хорош тем, что там возможность запустить "в составе этой самой программы" с самого начала делается как можно раньше, чтобы найти все подводные грабли. L>Да, это особенно помогает, когда пишется абсолютно новый коммуникационный уровень для поддержки нового устройства, которого в железе еще не существует. Не говоря уже о том, что ручное тестирование никогда не найдет все подводные грабли.