Здравствуйте, netch80, Вы писали:
N>То в основном всякие темпоральные гейзенбаги.
Ооо, да. Крови попили изрядно.
N>Я имел в виду случаи попроще. Есть цепочка вызовов API, где последние используют результат первых, что-то нарушено, единственный способ выяснить — всунуться между ними, а разносить в отдельные функции смысла нет аж совсем.
Что-то не совсем понял, в чем тут проблема?
N>>>2. Сходно с первым, но другая вводная — оОбучение новым языкам/фреймворкам — когда надо прочувствовать происходящее в виде последовательности состояний и разницы между ними. Где что за что цепляется, где на каком шаге что успело измениться... L>>Только отладчик при этом — это тот самый противогаз, который нужно одевать, когда стоя и в гамаке. N>?
Ну неудобно!
N>В одном проекте изначальное решение понадеяться на саморегулирование нагрузочной способности под постоянным потоком данных оказалось самой большой ошибкой и вылечилось, фактически, переходом на поллинг вместо пуша. Такое тестами не выловится при любом покрытии "с самого начала".
Хех. Мы в подобных случаях ставили подобному риску самую высокую оценку и планировали нагрузочное тестирование на самый первый спринт, в котором это было бы возможно. То есть необходимо было обеспечить минимальный уровень бизнес-логики с кучей моков и стабов, но позволяющий протестировать бутылочные горлышки. Но это не задача юнит-тестов.
N>>>именно поэтому agile (поклон соседнему треду) хорош тем, что там возможность запустить "в составе этой самой программы" с самого начала делается как можно раньше, чтобы найти все подводные грабли. L>>Да, это особенно помогает, когда пишется абсолютно новый коммуникационный уровень для поддержки нового устройства, которого в железе еще не существует. Не говоря уже о том, что ручное тестирование никогда не найдет все подводные грабли. N>Я и не предлагал отменять юнит-тестирование
Здравствуйте, landerhigh, Вы писали:
N>>Я имел в виду случаи попроще. Есть цепочка вызовов API, где последние используют результат первых, что-то нарушено, единственный способ выяснить — всунуться между ними, а разносить в отдельные функции смысла нет аж совсем.
L>Что-то не совсем понял, в чем тут проблема?
Проблемы нет — тогда, когда ты влезаешь между вызовами и смотришь, что же там реально передаётся между ними. А это или отладчик, или пересборка с логгингом.
Отладчик обычно проще, по крайней мере на один раз.
N>>>>2. Сходно с первым, но другая вводная — оОбучение новым языкам/фреймворкам — когда надо прочувствовать происходящее в виде последовательности состояний и разницы между ними. Где что за что цепляется, где на каком шаге что успело измениться... L>>>Только отладчик при этом — это тот самый противогаз, который нужно одевать, когда стоя и в гамаке. N>>? L>Ну неудобно!
Для данных целей — не мешает.
N>>В одном проекте изначальное решение понадеяться на саморегулирование нагрузочной способности под постоянным потоком данных оказалось самой большой ошибкой и вылечилось, фактически, переходом на поллинг вместо пуша. Такое тестами не выловится при любом покрытии "с самого начала". L>Хех. Мы в подобных случаях ставили подобному риску самую высокую оценку и планировали нагрузочное тестирование на самый первый спринт, в котором это было бы возможно. То есть необходимо было обеспечить минимальный уровень бизнес-логики с кучей моков и стабов, но позволяющий протестировать бутылочные горлышки. Но это не задача юнит-тестов.
Ну вот потому я и говорю про то, что надо собрать как можно раньше.
И слово "спринт", которое ты тут применил, из мира agile.
А так — в этой части у нас различие позиций только в акцентах.
L>>Что-то не совсем понял, в чем тут проблема? N>Проблемы нет — тогда, когда ты влезаешь между вызовами и смотришь, что же там реально передаётся между ними. А это или отладчик, или пересборка с логгингом. N>Отладчик обычно проще, по крайней мере на один раз.
В смысле, зачем смотреть, что передается между вызовами стороннего API?
L>>Ну неудобно! N>Для данных целей — не мешает.
Да и не помогает особо.
L>>Хех. Мы в подобных случаях ставили подобному риску самую высокую оценку и планировали нагрузочное тестирование на самый первый спринт, в котором это было бы возможно. То есть необходимо было обеспечить минимальный уровень бизнес-логики с кучей моков и стабов, но позволяющий протестировать бутылочные горлышки. Но это не задача юнит-тестов.
N>Ну вот потому я и говорю про то, что надо собрать как можно раньше.
Вовсе не обязательно собирать аппликуху. Вполне можно собрать функциональный тест на основе юнит-тест фреймворка.
N>И слово "спринт", которое ты тут применил, из мира agile.
Это просто способ отслеживать прогресс и упорядочивать хаос, в переводе на человеческий язык звучит как "сделать первым делом".
Здравствуйте, landerhigh, Вы писали: L>Предположим, что у нас и правда завелась функция L>пишем тест для нее. Естественно, предполагается, что у нас есть оператор сравнения двух матриц.
...
L>
Я думал, что у тебя есть секретная комба - ломик в рукаве, поэтому у тебя получается, а у меня — ни фига.
А оказывается, я делаю всё то же самое.
Ты вот говоришь, что код, мол, неправильный.
SVZ>Не всегда просто и нифига не быстро.
Если не просто и не быстро, то вы либо не понимаете свой собственный код, либо алгоритм, либо предметную область. Значит, нужно заполнять пробелы.
SVZ>В моей практике это сложно и геморройно. И еще объемно.
Хороший индиактор того, что тестируемый код пора дробить на части.
А я думаю, что проблема не в коде, а в прикладной области и в решаемых задачах.
Есть алгоритмы, у которых на вход подается крошечный набор данных и легко проверить результат.
В качестве примера — Poco (недавно ковырял). Там, действительно все тесты по 5-10 строк. Такие тесты можно клепать по дюжине в час.
Вот если взять твой пример с матрицей и спроецировать его, скажем, на построение адаптивной сетки (adaptive mesh refinement — можно погуглить картинки), то что у нас получается.
Вот эта инициализация: L> Matrix mtx = ...; // Тут инициализация квадратной матрицы L> Matrix expected = ...; // Вручную инициализированная транспонированная матрица
Может занимать до сотни строк.
И еще столько же — проверка результатов.
Ну и тестов надо написать хренову тучу.
Что придется тестировать:
Случай 1. Сетка из одной ячейки.
Подготовка данных занимает одну строчку кода, затем надо проверить, что все 6 граней инцидентны "воздуху".
Теперь ячейку надо рассечь.
Варианты сечения: по осям X, Y, Z, X+Y, X+Z, Y+Z, X+Y+Z. Итого 7 вариантов сечения. В результате получается от 2 до 8 ячеек.
Теперь нужно найти положение ячеек относительно соседей (не одна строчка). Затем проверить, что на периферии ячейки по-прежнему инцидентны воздуху, а внутри соединены между собой.
Случай 2. Добавляем соседей. Строим однослойную структуру 3х3х1. Выбираем центральную ячейку.
Тут одной строчкой кода уже не обойдешься. И снова прогоняем все варианты сечения, и проверяем результаты. Но тут результаты уже совсем другие, поэтому и проверку результатов надо писать совсем другую.
Случай 3. Сверху и снизу добавляем ячеек — получаем "кубик" 3х3х3 ячеек. Снова ищем центральную ячейку и все по новой. Сложность проверки возрастает, т.к. любое (почти любое) сечение затрагивает соседей в нескольких слоях.
Случай 4. Тут самое интересное. Т.к. каждая ячейка своей гранью может соприкасаться только с 0, 1, 2 и 4 ячейками, то при определенных сечениях возникает конфликт, чтобы его разрешить необходимо подразбить соседа — начинается лавинообразный процесс. Как тут писать тест — у меня фантазии уже нет.
Итого. Чтобы запрограммировать алгоритм потребуется 1-2 недели и еще пара недель, как минимум, чтобы обложить его тестами, от которых будет хоть какой-то прок.
Что интересно, когда код будет отлажен никто в него не полезет. А если полезет, то это означает, что предстоят очень серьезные изменения, которые могут перекроить все структуры данных и тогда написанные тесты пойдут псу под хвост.
Как в таких условиях извлечь из юнит-тестов какую-то пользу — я пока не представляю.
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Я думал, что у тебя есть секретная комба - ломик в рукаве, поэтому у тебя получается, а у меня — ни фига. SVZ>А оказывается, я делаю всё то же самое.
Секретный ломик состоит в том, чтобы понять, что тест должен быть крайне простым. Из этого отрастают весьма любопытные вещи.
SVZ>Может занимать до сотни строк. SVZ>И еще столько же — проверка результатов.
Так бывает, когда неверно выбран базис, к которому применяется тест.
SVZ>Ну и тестов надо написать хренову тучу.
Показатель того, что что-то делается неправильно.
Ведь это также означает, что для ручной верификации придется сидеть в отладчике годами.
SVZ>Что интересно, когда код будет отлажен никто в него не полезет. А если полезет, то это означает, что предстоят очень серьезные изменения, которые могут перекроить все структуры данных и тогда написанные тесты пойдут псу под хвост.
Это лишь означает, что и тесты и код были изначально написаны неправильно. Хорошие тесты даже при серьезных изменениях кода либо не меняются вообще, либо незначительно, так как они тестируют контракты, то есть видимое поведение.
В области 3D графики я чилийский лох, и у меня нет времени на то, чтобы в ней разбираться лишь для того, чтобы предоставить пример правильного подхода к использованию тестов в разработки кода в этой области. Но если там используются стабильные алгоритмы, то нет никаких причин, по которым там не получится эффективно использовать юнит тесты.
SVZ>Как в таких условиях извлечь из юнит-тестов какую-то пользу — я пока не представляю.
Для начала нужно определиться, какую пользу хочется извлечь. В нашей секте юнит-тесты
1. Помогают разработать наиболее эффективный и удобный контракт кода. Если что-то неудобно тестировать, то и использовать это тоже будет неудобно
2. Помогают проводить раннюю валидацию алгоритмов. Ни железа, ни системы еще нет, а бизнес-логика у нас уже работает согласно спецификациям.
3. Абсолютно незаменимы при итеративной разработке, так как выявляют breaking changes. Тут без комментариев.
4. Помогают в рефакторинге по той же причине.
Все вместе это приводит к парадоксальным результатам — кода в итоге пишется меньше, и времени на его написание тоже третбуется меньше.
И еще заметил такую вещь — если разработчик не понимает, как он будет тестировать свой собственный код, это означает, что он не понимает, что делает.
Здравствуйте, landerhigh, Вы писали:
L>Здравствуйте, Marty, Вы писали:
M>>Это если без фанатизма. Но это не варианты landerhigh'а и __kot'а L>Видишь ли... когда приходится писать на разных платформах, приходит понимание того, что отладчик — вещь платформеннозависимая.
строго говоря, нет. на определенном уровне абстракции различия исчезают.
L> И навыки работы со студийным отладчиком никак не помогут в случае gdb,
еще скажите что навыки написания кода в студии никак не могут, когда под рукой только x-code и text mate.
L> Непереносимый навык, в общем.
научившись отлаживать резидентов в дос я могу отлаживать что угодно и чем угодно. даже если это NEC V850 и микроконтроллер для автомобиля.
L> Но главная причина состоит в том, что отладчик как инструмент разработки контрпродуктивен.
с этим согласен. отберите у человека отладчик и код сразу станет лучше. а если еще и ide отобрать, то вообще супер (без сарказма)
интерактивный отладчик скорее зло, чем добро.
americans fought a war for a freedom. another one to end slavery. so, what do some of them choose to do with their freedom? become slaves.
Здравствуйте, landerhigh, Вы писали:
L>Здравствуйте, Marty, Вы писали:
L> покрытие сценариев для определенных участков кода стремится к 100% и вероятность случаев,
покрытие сценаривев или покрытие кода? на одном из собеседований предложили прикинуть сколько нужно тестов для функции сортировки. вышло 100500 тестов и все они очень нетривиальные.
убедиться, что в отсортированных данных есть все исходные данные и нет никаких левых. убедиться что сортировка (не)устройчивая. убедиться, что данные действительно отсортированы в нужном порядке. убедиться, что если повторно сортируются уже отсортированные данные мы не получаем тормозов, выходящих за рамки требований к функции сортировки.
дальше больше. надо же как-то убедиться, что мы не пишем за пределы массива (если сортируется массив) и что нет утечек памяти (если выделяется память). плюс еще тонкости реализации чтобы не получилось так, что на 64 бит платформе при сортировке массива из двух+ миллиардов записей мы не получали кашу.
короче, получилось, что функция сортировки пишется минут за пол-часа и еще пол-года к ней пишутся тесты, причем часть тестов требуют либо низкоуровнего взаимодействия с системой (а как иначе отслеживать "промахи" по обращению мимо массива), либо спциального софта и оборудования (нужен 64-бит компилятор и система на которой можно выделить столько памяти, чтобы обнаружить, что указатели у нас 128 бит, а размер типа индекса 32 бита).
кстати, последний тест сломает кучу реализаций самых разных алгосов. потому что тип индекса обычно выбирается бездумно. типа раз у меня 32 бит машина и у меня все работает, то так и надо. ну или у меня 64 бита машина, но мне не нужно сортировать больших массивов и все работает. до поры до времени.
так что интересно как вы достигаете 100% покрытия для алгоритмов чуть-чуть сложнее чем разворот списка.
ЗЫ. разумеется, дебаггер тут не помощник, но мне странно слышать о легкости написания тестов. зачастую тесты пишет отдельный коллектив разработчиков и пишет их на фулл-тайм и по времени они занимают ну никак не меньше чем сам проект. если заглянуть в тесты того же огнелиса, то можно увидеть, что они неполные и покрывают лишь малую часть. в результате чего там постоянно находят баги и дыры.
americans fought a war for a freedom. another one to end slavery. so, what do some of them choose to do with their freedom? become slaves.
Здравствуйте, landerhigh, Вы писали:
L>>>Что-то не совсем понял, в чем тут проблема? N>>Проблемы нет — тогда, когда ты влезаешь между вызовами и смотришь, что же там реально передаётся между ними. А это или отладчик, или пересборка с логгингом. N>>Отладчик обычно проще, по крайней мере на один раз. L>В смысле, зачем смотреть, что передается между вызовами стороннего API?
Мнэээ... ну я не знаю, наверно, чтобы решить проблему?
Если оно что-то не то делает, наверно, надо или заставить его работать, или заменить на другое, но замена может оказаться слишком дорогой.
Ну или расскажи, как бы ты это сделал именно в такой ситуации.
L>>>Ну неудобно! N>>Для данных целей — не мешает. L>Да и не помогает особо.
Именно что помогает. Breakpoint, посмотрели на переменные, ужаснулись, начали рисовать обходы.
N>>Ну вот потому я и говорю про то, что надо собрать как можно раньше. L>Вовсе не обязательно собирать аппликуху. Вполне можно собрать функциональный тест на основе юнит-тест фреймворка.
Увы, при ретроспективном анализе я прихожу всё время к одному и тому же выводу — воспроизвести реальную проблему можно было только на коде, очень близком к финальному. До этого она не воспроизводилась.
N>>И слово "спринт", которое ты тут применил, из мира agile. L>Это просто способ отслеживать прогресс и упорядочивать хаос, в переводе на человеческий язык звучит как "сделать первым делом".
Не не не дэвид блейн кролик упорхнул терминология ушла в массы.
Здравствуйте, мыщъх, Вы писали:
М>покрытие сценаривев или покрытие кода? на одном из собеседований предложили прикинуть сколько нужно тестов для функции сортировки. вышло 100500 тестов и все они очень нетривиальные.
Подход в принципе из серии натянуть сову на глобус. Для сортировки надо сделать _верификацию_, а не тестирование, алгоритма, и тестирование применённых механизмов, вроде swap(). Какие-то тесты всего алгоритма, конечно, нужны, но они больше для того, чтобы убедиться, что не сломано само окружение, и как минимум часть из них будет проверкой этого окружения, в виде sizeof(int)>=4.
В соседнем треде обсуждают ATS, там верификация встраивается в сам код. Говорят, выглядит многообещающе.
А ещё подобные штуки тестируются случайными данными, то есть при каждом прогоне выбирается случайный массив входных данных, сортируется, и результат или сравнивается с тупым алгоритмом, или проверяется на базовые инварианты типа возрастания ключа. Главное — чтобы при обломе теста входные данные были чётко отражены в отчёте, а не "тут поломалось, я не знаю, почему".
Я слышал, как такое рандомизированное тестирование находило проблемы через много лет после вроде бы стабилизации кода.
М>убедиться, что в отсортированных данных есть все исходные данные и нет никаких левых. убедиться что сортировка (не)устройчивая.
Убедиться, что сортировка неустойчивая, как и с любым другим подобным случаем непредсказуемого невыполнения свойства, которое не обещалось, можно только статистически. На одиночном вызове — нет, оно чисто случайно может соблюсти устойчивость.
М> убедиться, что данные действительно отсортированы в нужном порядке. убедиться, что если повторно сортируются уже отсортированные данные мы не получаем тормозов, выходящих за рамки требований к функции сортировки.
Это частная проблема только для qsort без защиты по глубине рекурсии. Может быть проще проверить сам переход к более дорогим предсказуемым средствам (heapsort, медиана медиан), чем мерять всё вместе. Хотя такая проверка по времени должна быть сделана на верхнем уровне чисто для отмазки для проверяющих (включая себя).
М>дальше больше. надо же как-то убедиться, что мы не пишем за пределы массива (если сортируется массив)
Верификация. Указатель за пределами массива просто не может быть создан.
На qsort опасные места — только при формировании субпартиций — можно явно вставить проверки в рантайме, это будет дёшево, и/или опять же верифицировать.
М> и что нет утечек памяти (если выделяется память).
Может, тут лучше не проверять внешними средствами, а нарисовать простой менеджер одноразовой арены.
М> плюс еще тонкости реализации чтобы не получилось так, что на 64 бит платформе при сортировке массива из двух+ миллиардов записей мы не получали кашу.
У меня на второе был другой любимый пример — функция подсчёта utf8 codepoints
в строке, собирающая результат в однобайтном счётчике.
Против такого писать тесты — это надо ну очень постараться и вообще предположить, что кто-то переполнил эту границу. А это уже анализ кода.
М>так что интересно как вы достигаете 100% покрытия для алгоритмов чуть-чуть сложнее чем разворот списка.
Именно алгоритмы сложной логики как раз покрыть очень сложно.
Хотя у меня пара показательных примеров есть — вот, например, группировка элементов списка для одного ключа:
Что тут тестировать? Очевидно, именно качество сочетания соседних элементов. С одинаковым ключом — собирать, с разными — нет ну и частные случаи входе пустого списка на входе. Получаем:
Честно говоря, мне кажется, что я покрыл все возможные варианты
Хочу увидеть контрпример на хотя бы подозрительную ситуацию.
М>ЗЫ. разумеется, дебаггер тут не помощник, но мне странно слышать о легкости написания тестов. зачастую тесты пишет отдельный коллектив разработчиков и пишет их на фулл-тайм и по времени они занимают ну никак не меньше чем сам проект. если заглянуть в тесты того же огнелиса, то можно увидеть, что они неполные и покрывают лишь малую часть. в результате чего там постоянно находят баги и дыры.
Это результат того, что у них moving target настолько быстрый, что при test-first они не успеют никуда
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Вот если взять твой пример с матрицей и спроецировать его, скажем, на построение адаптивной сетки (adaptive mesh refinement — можно погуглить картинки), то что у нас получается.
Интересный пример.
SVZ>Что придется тестировать: SVZ>Случай 1. Сетка из одной ячейки. SVZ>Подготовка данных занимает одну строчку кода, затем надо проверить, что все 6 граней инцидентны "воздуху".
Да, хорошая база. Собственно, эта проверка отвечает концепции BDD — если у нас есть понятные и легко формулируемые требования уровня ТЗ к реализации чего-то, их в первую очередь надо оформить в виде тестов верхнего уровня.
При любых изменениях внутренней логики именно нарушение этих тестов будет показывать какой-то крупный факап.
SVZ>Теперь ячейку надо рассечь. SVZ>Варианты сечения: по осям X, Y, Z, X+Y, X+Z, Y+Z, X+Y+Z. Итого 7 вариантов сечения. В результате получается от 2 до 8 ячеек. SVZ>Теперь нужно найти положение ячеек относительно соседей (не одна строчка). Затем проверить, что на периферии ячейки по-прежнему инцидентны воздуху, а внутри соединены между собой.
Примерно то же самое. Можно делать, если логика симметрична, тест не на все сразу, а на самый сложный вариант плюс по одному простому (типа, из X+Y, X+Z, Y+Z вначале взять только X+Z). Остальные сложить в низкоприоритетный TODO.
Но, по-моему, сечения по косым граням — это перебор. Что, параллелепипеда вам недостаточно? А почему бы тогда сразу не переходить к какой-нибудь трёхмерно-сотовой структуре?
[...]
SVZ>Случай 4. Тут самое интересное. Т.к. каждая ячейка своей гранью может соприкасаться только с 0, 1, 2 и 4 ячейками, то при определенных сечениях возникает конфликт, чтобы его разрешить необходимо подразбить соседа — начинается лавинообразный процесс. Как тут писать тест — у меня фантазии уже нет.
Вот если лавинообразный процесс — значит, он формулируется в терминах: шаг процесса (взять одну ячейку, если она ещё не пройдена на этом уровне (для данного шага и более крупного), изучить воздействие на соседей, добавить соседей в список обработки). Это вполне может быть одной функцией, для которой наготовить тестовых данных для типичных ситуаций, далее обеспечить покрытие всех веток логики (проверить каким-то автоматизированным средством). Ключ к качеству проверки — именно полное покрытие.
Далее сформулировать логику цикла по списку ячеек (вариант общеизвестного поиска в ширину) и на моках убедиться в её работе на произвольных данных нескольких случаев (пустой список, 2-3 варианта перекрытия распространения ячеек на одну и ту же). Тонкостью будет отдельный учёт одной ячейки на более мелкой сетке как новой сущности, но это решается в зависимости от варианта алгоритма (например, если шаг сетки кратен степени двойки, то учёт вообще банален).
SVZ>Итого. Чтобы запрограммировать алгоритм потребуется 1-2 недели и еще пара недель, как минимум, чтобы обложить его тестами, от которых будет хоть какой-то прок. SVZ>Что интересно, когда код будет отлажен никто в него не полезет. А если полезет, то это означает, что предстоят очень серьезные изменения, которые могут перекроить все структуры данных и тогда написанные тесты пойдут псу под хвост.
SVZ>Как в таких условиях извлечь из юнит-тестов какую-то пользу — я пока не представляю.
См. выше. Тут чётко видно различие двух уровней логики — чисто программирования (представление ячеек само по себе, списки поиска в ширину для лавинного распространения...) и математического.
Если вынести оба в отдельные функции, их можно тестировать. А компилятор пусть потом инлайнит
Здравствуйте, landerhigh, Вы писали:
К>>Сделал, к примеру, транспонирование матрицы на месте, но она у тебя, скажем, стреляет по памяти при НОД(x,y)=17, а стрельба сходит с рук при x<100. К>>Да, ты написал тесты о том, что матрица транспонируется. Разумеется, только при маленьких размерах и выборочно (ну не перебирать же все x от 1 до 200 и y от 1 до 200). К>>А что ты сделал для проверки стрельбы?
L>Во-первых, don't write shit code. Моей фантазии не хватает на то, чтобы представить, как можно в коде транспонирования матрицы случайно такой косяк допустить.
Для 17 — да, для 16 — уже запросто — при попытке ускорения через SSE/AVX/etc.
L>Во-вторых, проверка стрельбы по памяти не входит в ответственность юнит-тестов. Я в принципе не уверен, что ее можно в общем случае обнаружить в момент собственно выстрела.
В общем случае — нет. В достаточно простых — например, порча соседних участков из-за вылезания за границы на плюс-минус константу — есть хорошо проработанные методы с guard zone.
Здравствуйте, netch80, Вы писали:
SVZ>>Варианты сечения: по осям X, Y, Z, X+Y, X+Z, Y+Z, X+Y+Z. Итого 7 вариантов сечения. В результате получается от 2 до 8 ячеек. SVZ>>Теперь нужно найти положение ячеек относительно соседей (не одна строчка). Затем проверить, что на периферии ячейки по-прежнему инцидентны воздуху, а внутри соединены между собой.
N>Примерно то же самое. Можно делать, если логика симметрична, тест не на все сразу, а на самый сложный вариант плюс по одному простому (типа, из X+Y, X+Z, Y+Z вначале взять только X+Z). Остальные сложить в низкоприоритетный TODO.
Ну разве что. Будет, хотя бы, надежда, что удастся изловить самый вопиющий случай. Конечно это лучше, чем ничего, но жаба постоянно стоит у горла — очень соблазнительно писать новый функционал вместо теста. Пока соблазны побеждают.
N>Но, по-моему, сечения по косым граням — это перебор. Что, параллелепипеда вам недостаточно? А почему бы тогда сразу не переходить к какой-нибудь трёхмерно-сотовой структуре?
Пока обходимся параллелепипедами, сечение происходит вертикальными (X,Y) и горизонтальной (Z) плоскостями.
Но дальше, видимо, таки придется делать косые сечения (либо наклонять/поворачивать вертикальные грани) — можно здорово повысить точность моделирования без увеличения размерности задачи.
Триангуляция нам не подошла из-за особенностей солверов (солверы делаю не я, поэтому подробнее не смогу объяснить).
Готовые движки для построения сетки тоже не годятся. Работаем с печатными платами, а это явно выраженная слоистая структура — горизонтальные грани должны выравниваться на границу слоев (металл, диэлектрик, финишное покрытие и т.д.).
Вот и приходится самим всё делать.
SVZ>>Случай 4. Тут самое интересное. Т.к. каждая ячейка своей гранью может соприкасаться только с 0, 1, 2 и 4 ячейками, то при определенных сечениях возникает конфликт, чтобы его разрешить необходимо подразбить соседа — начинается лавинообразный процесс. Как тут писать тест — у меня фантазии уже нет.
N>Вот если лавинообразный процесс — значит, он формулируется в терминах: шаг процесса (взять одну ячейку, если она ещё не пройдена на этом уровне (для данного шага и более крупного), изучить воздействие на соседей, добавить соседей в список обработки). Это вполне может быть одной функцией, для которой наготовить тестовых данных для типичных ситуаций, далее обеспечить покрытие всех веток логики (проверить каким-то автоматизированным средством). Ключ к качеству проверки — именно полное покрытие. N>Далее сформулировать логику цикла по списку ячеек (вариант общеизвестного поиска в ширину) и на моках убедиться в её работе на произвольных данных нескольких случаев (пустой список, 2-3 варианта перекрытия распространения ячеек на одну и ту же). Тонкостью будет отдельный учёт одной ячейки на более мелкой сетке как новой сущности, но это решается в зависимости от варианта алгоритма (например, если шаг сетки кратен степени двойки, то учёт вообще банален).
Ну это получается, что надо лезть внутрь алгоритма, чего очень не хотелось делать. Но, другого варианта на горизонте нет. Скоро предстоит опять ковырять генератор сетки.
N.B. В кои-то веки дискуссия про тестирование получилась продуктивной.
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, netch80, Вы писали:
N>Если оно что-то не то делает, наверно, надо или заставить его работать, или заменить на другое, но замена может оказаться слишком дорогой. N>Ну или расскажи, как бы ты это сделал именно в такой ситуации.
Я подозреваю, что несколько не догоняю, что ты имеешь в виду. Пример можно?
Юнит-тесты, как правило, тестируют поведение своего собственного кода, изолируя особенности внешнего API. Зачастую можно сразу сказать, где косяк — внутри или снаружи.
L>>Да и не помогает особо.
N>Именно что помогает. Breakpoint, посмотрели на переменные, ужаснулись, начали рисовать обходы.
Или посмотрели на переменные, не ужаснулись, ибо не воспроизвелось — воспроизводится только при определенных таймингах, которые отладчик рушит
L>>Вовсе не обязательно собирать аппликуху. Вполне можно собрать функциональный тест на основе юнит-тест фреймворка.
N>Увы, при ретроспективном анализе я прихожу всё время к одному и тому же выводу — воспроизвести реальную проблему можно было только на коде, очень близком к финальному. До этого она не воспроизводилась.
Вот в этом-то и прикол — юнит тесты тестируют юниты в сценариях использования, полностью идентичным финальным.
Ну и еще это может быть просто показатель того, что при планировании архитектуры концентрируются не на том. Бывает, что с первого дня начинают задаваться цветом кнопок в UI, и полностью игнорируют тот факт, что без достаточной производительности никому этот UI не нужен будет.
N>>>И слово "спринт", которое ты тут применил, из мира agile. L>>Это просто способ отслеживать прогресс и упорядочивать хаос, в переводе на человеческий язык звучит как "сделать первым делом". N>Не не не дэвид блейн кролик упорхнул терминология ушла в массы.
Здравствуйте, netch80, Вы писали:
L>>Во-первых, don't write shit code. Моей фантазии не хватает на то, чтобы представить, как можно в коде транспонирования матрицы случайно такой косяк допустить. N>Для 17 — да, для 16 — уже запросто — при попытке ускорения через SSE/AVX/etc.
О, хороший пример, как раз показывающий полезность юнит-тестирования.
Подобные подкапотные оптимизации не изменяют наблюдаемого поведения. Но это не случайное изменение, и разработчк, реализующий подобную оптипизацию, понимает, что он вносит еще один граничный случай в код (если он не понимает, то это уже нужно обсужать в форуме "о работе"). И знает, что ему нужно написать тест, который протестирует работу юнита именно в этих условиях. И он этот тест пишет, и не ждет, когда придет баг репорт из продакшена.
L>>Во-вторых, проверка стрельбы по памяти не входит в ответственность юнит-тестов. Я в принципе не уверен, что ее можно в общем случае обнаружить в момент собственно выстрела. N>В общем случае — нет. В достаточно простых — например, порча соседних участков из-за вылезания за границы на плюс-минус константу — есть хорошо проработанные методы с guard zone.
Вот и я про то. Кроме того, налицо двойные стандарты — методом ручного лома код на отсутствие привычки стрелять по памяти не проверить от слова никак. А вот от юнит-тестов начинают внезапно требовать еще и это, и многое другое.
Здравствуйте, мыщъх, Вы писали:
L>> И навыки работы со студийным отладчиком никак не помогут в случае gdb, М>еще скажите что навыки написания кода в студии никак не могут, когда под рукой только x-code и text mate.
По правде говоря, мне жаль разработчика, у которого отобрали студию и вручили vim, который он видит в первый раз в жизни.
Но это оффтопик.
Здравствуйте, мыщъх, Вы писали:
L>> покрытие сценариев для определенных участков кода стремится к 100% и вероятность случаев, М>покрытие сценаривев или покрытие кода? на одном из собеседований предложили прикинуть сколько нужно тестов для функции сортировки. вышло 100500 тестов и все они очень нетривиальные.
Покрытие сценариев. Обычно три набора тестов — для верификации поведения на валидных данных, на граничных и на невалидных (проверка корректности негативного сценария).
Верификация собственно сортировки тривиальна, хотя и O(n)
М>убедиться, что если повторно сортируются уже отсортированные данные мы не получаем тормозов, выходящих за рамки требований к функции сортировки.
Это тоже можно и даже в некотором смысле элементарно, но это не совсем юнит-тест.
М>дальше больше. надо же как-то убедиться, что мы не пишем за пределы массива (если сортируется массив) и что нет утечек памяти (если выделяется память).
это в задачи юнит-тестов не входит. И, кстати, юнит-тесты для функции сортировки сразу показывают, что затея выделять память внтури оных функций — так себе идея.
М>плюс еще тонкости реализации чтобы не получилось так, что на 64 бит платформе при сортировке массива из двух+ миллиардов записей мы не получали кашу.
Тонкости реализации есть граничные случаи, которые юнит-тесты обязаны проверять, но конкретно данный случай проверять обычно смысла нет.
М>короче, получилось, что функция сортировки пишется минут за пол-часа и еще пол-года к ней пишутся тесты,
Получилось как в анекдоте про стеклянный бакен.
М>так что интересно как вы достигаете 100% покрытия для алгоритмов чуть-чуть сложнее чем разворот списка.
Достигаем обычно. Это необходимость.
М>ЗЫ. разумеется, дебаггер тут не помощник, но мне странно слышать о легкости написания тестов. зачастую тесты пишет отдельный коллектив разработчиков
Юнит-тесты всегда пишет тот же самый человек, что и тестируемый код. Без исключений.
Здравствуйте, landerhigh, Вы писали:
N>>Если оно что-то не то делает, наверно, надо или заставить его работать, или заменить на другое, но замена может оказаться слишком дорогой. N>>Ну или расскажи, как бы ты это сделал именно в такой ситуации. L>Я подозреваю, что несколько не догоняю, что ты имеешь в виду. Пример можно? L>Юнит-тесты, как правило, тестируют поведение своего собственного кода, изолируя особенности внешнего API. Зачастую можно сразу сказать, где косяк — внутри или снаружи.
Ну представь себе... что бы такое попроще... разборку входной строки циклом вокруг strtok().
Если эта функция будет лажаться на специфических данных, ты этого не заподозришь, пока не уткнёшься носом. А юнит-тесты таки строятся вокруг стандартной библиотеки, потому что делать тестирование вообще без библиотеки или невозможно (она неявно вызывается), или бессмысленно.
Не хочешь явных вызовов? Сложи две строки на чём угодно, кроме C — попал в стандартную библиотеку
L>>>Да и не помогает особо. N>>Именно что помогает. Breakpoint, посмотрели на переменные, ужаснулись, начали рисовать обходы. L>Или посмотрели на переменные, не ужаснулись, ибо не воспроизвелось — воспроизводится только при определенных таймингах, которые отладчик рушит
Ну я не про гейзенбаги. Это другой случай, по-своему серьёзный, но другой.
L>>>Вовсе не обязательно собирать аппликуху. Вполне можно собрать функциональный тест на основе юнит-тест фреймворка. N>>Увы, при ретроспективном анализе я прихожу всё время к одному и тому же выводу — воспроизвести реальную проблему можно было только на коде, очень близком к финальному. До этого она не воспроизводилась. L>Вот в этом-то и прикол — юнит тесты тестируют юниты в сценариях использования, полностью идентичным финальным.
В сценариях, не имеющих абсолютно ничего общего с финальными в таких вопросах, как отношение к нагрузке, суммарное использование памяти и тому подобное.
В этих вопросах юнит-тестирование это даже не игры в песочнице, это хуже — это игры на макете песочницы, рисованном кривыми линиями на рваном листике в полосочку.
L>Ну и еще это может быть просто показатель того, что при планировании архитектуры концентрируются не на том. Бывает, что с первого дня начинают задаваться цветом кнопок в UI, и полностью игнорируют тот факт, что без достаточной производительности никому этот UI не нужен будет.
Голой производительности хватало. Не учли несколько неустранимых мест, дающих O(N^2) от уровней перекосов нагрузки. Именно в этом "гладко было на бумаге, да забыли про овраги". Ну, не знали, но от этого не легче.
N>>>>И слово "спринт", которое ты тут применил, из мира agile. L>>>Это просто способ отслеживать прогресс и упорядочивать хаос, в переводе на человеческий язык звучит как "сделать первым делом". N>>Не не не дэвид блейн кролик упорхнул терминология ушла в массы. L>А она удобная — всем сразу все понятно
Здравствуйте, _hum_, Вы писали:
__>Всегда считал, что это ортогональные вещи,
Именно. Сравнивать юнит-тесты и отладчик — это как сравнивать тёплое с мягким. Алтернативой отладчику могут быть логи и то с натяжкой. При чём тут юнит-тесты вообще не понятно
__>но, тут товарищ landerhigh заявил, что использование дебагера — каменный век:
Так говорят обычно те, кто не понимает что такое отладчик.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Именно. Сравнивать юнит-тесты и отладчик — это как сравнивать тёплое с мягким. Алтернативой отладчику могут быть логи и то с натяжкой. При чём тут юнит-тесты вообще не понятно
При том, что тема выделена из другой, где т.с. на своем опыте столкнулся с тем, что попытка заменять тесты ручной отладкой приводит к тому, что процесс разработки встает намертво.
__>>но, тут товарищ landerhigh заявил, что использование дебагера — каменный век: IT>Так говорят обычно те, кто не понимает что такое отладчик.
Скорее те, кто слишком хорошо знает, что таке отладчик. Кстати, по-английски он называется debugger, и называется так ИМХО вовсе не случайно.
Здравствуйте, _hum_, Вы писали:
__>Всегда считал, что это ортогональные вещи, но, тут товарищ landerhigh заявил, что использование дебагера — каменный век:
Я бы уточнил так: использование отладчика без тестов — или каменный век, или любая разновидность подхода, не ориентированного на целевое долговременное использование кода, или редкие особые случаи, когда автоматизация тестирования невозможна.
Во вторую ветку входят, совершенно без всякой отрицательной коннотации, учебные цели. Школьники/студенты пишут задачу, чинят отладчиком, сдают на сайтах типа e-olymp.com и забывают. Это нормально. Точно так же во вторую входит "одноразовый код" по Спольски, и это тоже нормально.
Ненормально, когда код должен жить хотя бы полгода, и ему не фиксируют тесты, но находят время гонять отладчик. Во времена, когда каждому доступны VCS, облака для сохранения не только кода, но и тестов, и тестовые фреймворки для всех языков, это действительно каменный век по уровню использования средств.
Но если хочется получить ценность результата, то программист в первую очередь ценит свои усилия. Запуск отладчика и вызов в нём чего-то вручную — это неповторяемые действия, требующие умственных затрат на их повторение. Автоматизация их повторения — это уже тестовый фреймворк. Но такие фреймворки качественного уровня, которые запускают готовую программу, правильно и вовремя вводя/выводя данные и иначе взаимодействуя с ней, сложны, дороги и в общем случае принципиально невозможны (все эти Selenium решают важные частные случаи, но не общую задачу). Зато оформление функций и модулей в понятно управляемые песочницы — реализуемы в подавляющем большинстве случаев.
Неиспользование отладчика должно быть не намеренным действием, а следствием усилий, приложенных к автоматизации тестирования. "Каменный век" активного использования отладчика — это интегральная статистическая характеристика всего процесса разработки, а не одного действия.
В третьей ветке (неавтоматизируемое) заметное место занимают, например, скелет глубокосистемных вещей типа управлений режимами процессора, работа с аппаратными прерываниями и т.п.; после того, как они хоть как-то ожили, уже можно применять менее ручные средства.
__>На мой ответ: "извините, но вы здесь описали вариант, когда либо готовый код правится, либо к готовому коду добавляется еще один готовый функциональный блок, и проверяется их совместимость. я нигде не увидел собственного написания. __>ну, простейший пример — вам сказал тим лид — нужно написать функцию, транспонирующую матрицу. как в этом случае будет выглядеть работа с тестами без дебагера?" последовали опять общие фразы.
__>Потому вопрос в зал, может кто-нибудь, кто придерживается мнения landerhigh, на примере задачи написания функции транспонирования матрицы показать, как тесты заменяют работу с дебагером?
Транспонирование матриц вообще-то очень удачный пример именно для написания тестов (исключая уже упомянутые тут случаи всяких вылезаний за память, которые и отладчиком плохо ловятся, если намеренно не искать) и очень неудачный для отладчика. Очень простая и рутинная функциональность.
Пример от landerhigh для набора тестов вполне разумный и покрывает базу возможного, а то и полностью, если алгоритм реализован предельно тупо и прямолинейно через два индекса.
L>>Ты мне лучше скажи, зачем тут вообще отладчик может понадобиться? __>ответ: __>так а где здесь код-то самого транспонирования? ведь ошибки именно там кроются.
А зачем он тут нужен? Описанные тесты покрывают необходимое поведение по ТЗ, а не рассмотрение с учётом особенностей кода.
__> и если вы увидите срабатывание ассертов, что делать дальше будете?
Искать ошибку. Есть другие варианты?
__>как, например, обнаружите глупейшую ошибку типа Matrix[i][j] = Matrix[j][i] для in-place трансполнирования квадратной матрицы?
Простите, а что именно тут названо глупейшей ошибкой? Вообще-то транспонирование на месте именно так и делается.
Если Вы имели в виду, что циклы для этого должны быть организованы так, что 1<=i<=n, но 1<=j<i, а ошибкой будет 1<=j<=n, то именно это и надо явно говорить.
Но эта ошибка проверяется транспонированием диагонально несимметричной матрицы любого размера, хоть 2*2.