Поделитесь мыслями о способе тестирования который часто незаслуженно обходят вниманием.
Речь идет о тестировании программы во время выполнения, чем то пересекается с использлованием assert в С++ и Design Contracts.
Например использую в программе пространственное KD дерево. Вместо написания теста на отдельный функционал и сценарии использования , можно проверять все инварианты дерева при модификации данных и запускать проверку целостности всей структуры в ключевых местах сразу по месту выполнения.
кто использует похожее и какие преимущества недостатки вы видите ?
Здравствуйте, minorlogic, Вы писали:
M> Например использую в программе пространственное KD дерево. Вместо написания теста на отдельный функционал и сценарии использования , можно проверять все инварианты дерева при модификации данных и запускать проверку целостности всей структуры в ключевых местах сразу по месту выполнения. M> кто использует похожее и какие преимущества недостатки вы видите ?
Эээ, а как вообще можно тестировать программу не во время ее исполнения?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, minorlogic, Вы писали:
M>> Например использую в программе пространственное KD дерево. Вместо написания теста на отдельный функционал и сценарии использования , можно проверять все инварианты дерева при модификации данных и запускать проверку целостности всей структуры в ключевых местах сразу по месту выполнения. M>> кто использует похожее и какие преимущества недостатки вы видите ?
ВВ>Эээ, а как вообще можно тестировать программу не во время ее исполнения?
Реализацию KD дерева вполне могу протестировать юнит тестом и т.п.
Здравствуйте, minorlogic, Вы писали:
M>Поделитесь мыслями о способе тестирования который часто незаслуженно обходят вниманием. M>Речь идет о тестировании программы во время выполнения, чем то пересекается с использлованием assert в С++ и Design Contracts.
У нас это сплошь и рядом, но у нас сильная специфика. Грубо говоря, в потоках данных кроме реальных существует примесь тестовых для фиктивных объектов. Входные данные для фиктивных объектов изменяются согласно какому-то сценарию (он может быть и случайным) и проверяется соответствие выходных реакций этим данным в течение заданного времени.
Но это нормально годится только там, где непрерывные процессы с достаточно плотными потоками, и вообще есть понятие потока данных.
M> Например использую в программе пространственное KD дерево. Вместо написания теста на отдельный функционал и сценарии использования , можно проверять все инварианты дерева при модификации данных и запускать проверку целостности всей структуры в ключевых местах сразу по месту выполнения.
Слишком дорого, jIMHO. Хотя можно, например, напускать на каждое 100е изменение...
Здравствуйте, minorlogic, Вы писали: M> кто использует похожее и какие преимущества недостатки вы видите ?
Активно использую, эффективность по сравнению с юнит-тестами — небо и земля.
Нам всё равно приходится проверять аргументы в методах — зачем заводить два разных фреймворка?
Ассерты элементарно превращаются в условные брякпойнты — вместо того, чтобы следить за успешностью тестов, наш код просто останавливает выполнение при нарушения условия, давая возможность исправить ошибку "вживую".
При некоторой усидчивости, такие ассерты можно интегрировать с MS CodeContracts и практически нахаляву получить статическую верификацию и информацию о контрактах в тултипах/документации к коду. Увы, CodeContracts сыроваты и страдают от крайне своеобразного позиционирования — но это проблема их менеджмента, а не технологии как таковой.
Контракты не требуют раскрытия внутренностей кода для проверки тестами, не требуют mock-ов да и юнит-тест-фреймворков вообще. Кроме того, проверки пишутся прямо по месту, когда весь контекст держится в голове. В результате код проще писать/читать/сопровождать.
Ассерты рулят для отслеживания внутреннего состояния, круче них — только [InvariantMethod] в CodeContracts, но это уже тяжёлая артиллерия. Главное — соблюдать 2 правила:
1. Пишешь код, рассчитывая на побочный эффект (ну не может список быть пустым) — добавь проверку.
2. Использовать для таких проверок методы, помеченные [ConditionalBuild("DEBUG")].
Наконец (но это крайне субъективный довод), мне просто приятнее работать с ассертами, чем с юнит-тестами. На последние уходит раза в 2-3 больше времени, плюс на одну-две ошибки в тестируемом коде я регулярно умудряюсь накосячить раз 10 в самом тесте
Здравствуйте, minorlogic, Вы писали:
ВВ>>Эээ, а как вообще можно тестировать программу не во время ее исполнения? M>Реализацию KD дерева вполне могу протестировать юнит тестом и т.п.
Хорошо. Тогда скажите, чем описанный вами способ отличается от юнит-теста?
Здравствуйте, minorlogic, Вы писали:
M>Попробую уточнить. Я про тесты как и примитивные (в вашем примере) так и про более тяжелые M>например
Классный пример
Просто для примитивного кода сложных проверок не напишешь, а сложный — сюда не запостишь
Да, такое тоже пишу, но (как правило) оно компилируется только для дебаг-сборок.
Здравствуйте, minorlogic, Вы писали:
ВВ>>Хорошо. Тогда скажите, чем описанный вами способ отличается от юнит-теста? M>Я уже описал. Такой подход не требует создания дополнительного окружения для теста.
Ну так все равно не ясно, в чем это отличия для теста.
1) Положим, у нас есть юнит-тест, создающий некий объект в хранилище и запускающий валидацию хранилище.
2) Есть другой тест, который запускает валидацию после "штатного" сохранения. Он "включен", когда приложение развернуто в тесте и какой-нибудь тестер вводит данные в формочку и нажимает сохранить.
Зачем вам нужен второй вариант? Что он дает? Чем он отличается от 1? Тестироваться там будет ровно то же самое что и в варианте 1. Причем вариант 1 я могу запускать чуть ли не на каждом коммите для регрессион тестирования, а с 2 как быть?
Здравствуйте, minorlogic, Вы писали:
M>Здравствуйте, netch80, Вы писали:
N>>Слишком дорого, jIMHO. Хотя можно, например, напускать на каждое 100е изменение...
M>Я подразумевал конечно , что сборки можно делать с разной условной компиляцией и выключать тесты по требованию.
Как раз делать мало смысла зависимость от условной компиляции: программа будет или постоянно непроверяемой, или значительно более медленной.
А вот сделать, например, параметр настройки — через сколько действий напускается расширенная проверка — на практике видится значительно полезнее. А если несколько параметров с разным назначением — тем более. Так можно регулировать долю побочных контрольных действий согласно цели и нагрузке.
Рассчитать несколько типичных значений: для среднего юзера, для разработчика, для тестера. Первое брать по умолчанию, если не переопределено.
Здравствуйте, Sinix, Вы писали:
M>> кто использует похожее и какие преимущества недостатки вы видите ? S>Активно использую, эффективность по сравнению с юнит-тестами — небо и земля.
Мнэээ... я бы не был так категоричным. Проверки в рантайме полезны тем, что проверяют на реальных процессах, это так. Но у них колоссальный недостаток: они проверяют именно то, что происходит при типичных реальных процессах, и не затрагивают редкие, хоть и важные, случаи. Избавляясь от юнит-тестов в пользу таких тестов на ходу, вы устраните самые частые баги, но результат останется падать редко и непредсказуемо (и, что самое печальное, очень плохо диагностируемо). Далее, автоматизированное тестирование в таких условиях требует отработки сценариев общения с программой, начиная от простых до предельно диких, а их рисовать значительно сложнее, чем юнит-тесты или даже простые функциональные тесты на отдельных компонентах. Наконец, проверять всё в реальных условиях означает многократное увеличение затрат процессора (а иногда и памяти). В общем, никак не панацея...
S> [TestMethod] S> public static void TestBinarySizeToString() S> { S> BinarySize binarySize = new BinarySize( S> 10 * BinarySize.Megabyte S> + 123 * BinarySize.Kilobyte S> + 22);
S> Code.Equal(binarySize.ToString(), "binarySize", "10,12 MB"); S> Code.Equal(binarySize.ToString(BinarySizeUnit.Megabyte), "binarySize", "10,12 MB"); S> Code.Equal(binarySize.ToString(BinarySizeUnit.Kilobyte), "binarySize", "10 363,02 KB"); S> }
Не сказано главное — когда и почему запускается эта функция. Кстати, пример совершенно невнятный: проверяется весьма простой кусок кода с простейшей функциональностью, и единственное, что в этой проверке относится ко времени исполнения — это создание объекта, то есть фактически проверяется работа слоя рантайма по выполнению оператора new и другим необходимым действиям вроде приписки типа к объекту, а также выделения памяти в ОС и рантайме. Всё остальное тривиально проверяется в юниттесте — что будет дешевле по последствиям и проще хотя бы потому, что будет отловлено в среде разработчика или тестера, но никак не у пользователя. И нафига? (tm)
S> Ассерты элементарно превращаются в условные брякпойнты — вместо того, чтобы следить за успешностью тестов, наш код просто останавливает выполнение при нарушения условия, давая возможность исправить ошибку "вживую".
Ты выдал апологетику обычных assert'ов, как бы они в конкретном случае ни именовались. Это, конечно, замечательно, но совершенно не ново и как-то мало имеют связи с обсуждаемым вопросом, насколько я его понял: автор треда начинал разговор совсем не про обычные assert'ы, а какие-то явно запускаемые проверки функциональности.
И, кстати, что мешает сделать условными breakpoint'ами ассерты в случае тестовой сборки под юнит-тесты и функциональные тесты?
S> Контракты не требуют раскрытия внутренностей кода для проверки тестами,
Юнит-тесты чёрного ящика тоже этого не требуют, они работают по спецификации.
S> не требуют mock-ов да и юнит-тест-фреймворков вообще. Кроме того, проверки пишутся прямо по месту, когда весь контекст держится в голове. В результате код проще писать/читать/сопровождать.
И сложность работы с этим в результате переваливается на пользователя, который плюётся и выбрасывает программу — потому что такие тесты в рантайме покрывают ничтожную часть реальных случаев и сценариев.
S>Наконец (но это крайне субъективный довод), мне просто приятнее работать с ассертами, чем с юнит-тестами. На последние уходит раза в 2-3 больше времени, плюс на одну-две ошибки в тестируемом коде я регулярно умудряюсь накосячить раз 10 в самом тесте:(
Сочувствую, но это всего лишь лень и отсутствие привычки.
Здравствуйте, netch80, Вы писали:
N>Мнэээ... я бы не был так категоричным.
Не-не-не, тут я конечно перегнул палку — без тестов никак и никуда.
Вся разница — когда код нашпигован ассертами, мелкие проверки в тестах не нужны и они фактически превращаются в интеграционные тесты и в проверки основных сценариев использования.
N>Не сказано главное — когда и почему запускается эта функция. Кстати, пример совершенно невнятный...
Угу, полный тест в несколько раз больше
N>проверяется весьма простой кусок кода с простейшей функциональностью
Не, BinarySize — моё творение. Внутри ToString() — немного магии с автоматическим определением размерности, поддержка пользовательских строк форматирования, локализация ч/з ресурсы. Плюс только недавно пришлось заменить дубовый код аля
public long Megabytes
{
get { return (sizeInBytes % Gigabyte) / Megabyte; }
}
на (пишу по памяти, в реальном коде magic numbers вынесены в константы)
public long Megabytes
{
get { return (int)((sizeInBytes & (1023 << 20)) >> 20); }
}
Ну и поскольку ToString() покрывает больше всего кода в классе — совершенно естественно тестировать в первую очередь именно его
N>Ты выдал апологетику обычных assert'ов, как бы они в конкретном случае ни именовались. N>И, кстати, что мешает сделать условными breakpoint'ами ассерты в случае тестовой сборки под юнит-тесты и функциональные тесты?
Ничего Просто удобно когда один и тот же механизм работает и в тестах, и в обычных проверках.
S>> Контракты не требуют раскрытия внутренностей кода для проверки тестами, N>Юнит-тесты чёрного ящика тоже этого не требуют, они работают по спецификации.
Да, но они как раз не позволяют проверять внутреннее состояние.
N>И сложность работы с этим в результате переваливается на пользователя, который плюётся и выбрасывает программу — потому что такие тесты в рантайме покрывают ничтожную часть реальных случаев и сценариев.
Кажется, мы говорим про разные вещи Все проверки компилируются только в дебаг-сборки, а они используются в основном в тестах и при разработке. В релиз-сборках остаётся только базовая валидация аргументов/состояния, тот же if-then-throw, только записанный в 1 вызов метода. По производительности — в понедельник могу прогнать тесты, афайр в релизе проверки занимают проценты от общего времени.
S>>Наконец (но это крайне субъективный довод), мне просто приятнее работать с ассертами, чем с юнит-тестами. На последние уходит раза в 2-3 больше времени, плюс на одну-две ошибки в тестируемом коде я регулярно умудряюсь накосячить раз 10 в самом тесте N>Сочувствую, но это всего лишь лень и отсутствие привычки.
Неа, это всего лишь лень и присутствие здравого смысла: нет никаких причин вкладывать в одноразовый код столько же внимания, сколько в код, который идёт в продакшн.
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, Sinix, Вы писали:
M>>> кто использует похожее и какие преимущества недостатки вы видите ? S>>Активно использую, эффективность по сравнению с юнит-тестами — небо и земля.
N>Мнэээ... я бы не был так категоричным. Проверки в рантайме полезны тем, что проверяют на реальных процессах, это так. Но у них колоссальный недостаток: они проверяют именно то, что происходит при типичных реальных процессах, и не затрагивают редкие, хоть и важные, случаи.
Почему же это недостаток, они покрывают именно тот набор данных которые используются в реальной программе.
N> Избавляясь от юнит-тестов в пользу таких тестов на ходу, вы устраните самые частые баги, но результат останется падать редко и непредсказуемо (и, что самое печальное, очень плохо диагностируемо). Далее, автоматизированное тестирование в таких условиях требует отработки сценариев общения с программой, начиная от простых до предельно диких, а их рисовать значительно сложнее, чем юнит-тесты или даже простые функциональные тесты на отдельных компонентах.
Это не всегда так. И конечно вполне структуируется. Например разрабатывая фильтр для фотошопа , вам не надо проверять функциональность всего фотошопа, а только малую часть связанную с вашим фильтром и входными данными.
N> Наконец, проверять всё в реальных условиях означает многократное увеличение затрат процессора (а иногда и памяти). В общем, никак не панацея...
Я и не видел никакого призыва отказываться от юнит тестов и использовать панацею. Мы же изначально хотели выяснить недостатки и достоинства этого подхода.
Я вот вижу что значительно больше недостатков , если у разработчика нет возможности использовать реальную инфраструктуру где код будет работать. Кстати для меня это что то из ряда вон выходящее.
N>Не сказано главное — когда и почему запускается эта функция. Кстати, пример совершенно невнятный: проверяется весьма простой кусок кода с простейшей функциональностью, и единственное, что в этой проверке относится ко времени исполнения — это создание объекта, то есть фактически проверяется работа слоя рантайма по выполнению оператора new и другим необходимым действиям вроде приписки типа к объекту, а также выделения памяти в ОС и рантайме. Всё остальное тривиально проверяется в юниттесте — что будет дешевле по последствиям и проще хотя бы потому, что будет отловлено в среде разработчика или тестера, но никак не у пользователя. И нафига? (tm)
Преимущество тут , в том что не надо писать юнит тест. А юнит тест это симуляция окружения , генерация данных и т.п.
S>> Ассерты элементарно превращаются в условные брякпойнты — вместо того, чтобы следить за успешностью тестов, наш код просто останавливает выполнение при нарушения условия, давая возможность исправить ошибку "вживую".
N>Ты выдал апологетику обычных assert'ов, как бы они в конкретном случае ни именовались. Это, конечно, замечательно, но совершенно не ново и как-то мало имеют связи с обсуждаемым вопросом, насколько я его понял: автор треда начинал разговор совсем не про обычные assert'ы, а какие-то явно запускаемые проверки функциональности.
Вы верно подметили , я говорил скорее о тяжелой верификации.
N>И, кстати, что мешает сделать условными breakpoint'ами ассерты в случае тестовой сборки под юнит-тесты и функциональные тесты?
S>> Контракты не требуют раскрытия внутренностей кода для проверки тестами,
N>Юнит-тесты чёрного ящика тоже этого не требуют, они работают по спецификации.
S>> не требуют mock-ов да и юнит-тест-фреймворков вообще. Кроме того, проверки пишутся прямо по месту, когда весь контекст держится в голове. В результате код проще писать/читать/сопровождать.
N>И сложность работы с этим в результате переваливается на пользователя, который плюётся и выбрасывает программу — потому что такие тесты в рантайме покрывают ничтожную часть реальных случаев и сценариев.
Не думаю что польхователю должна идти сборка с тестами. Ну или в крайнем случае можно вести лог и т.п.
S>>Наконец (но это крайне субъективный довод), мне просто приятнее работать с ассертами, чем с юнит-тестами. На последние уходит раза в 2-3 больше времени, плюс на одну-две ошибки в тестируемом коде я регулярно умудряюсь накосячить раз 10 в самом тесте
N>Сочувствую, но это всего лишь лень и отсутствие привычки.
Лень это замечательно , если избавляет от лишней работы.
Здравствуйте, minorlogic, Вы писали:
M> Например использую в программе пространственное KD дерево. Вместо написания теста на отдельный функционал и сценарии использования , можно проверять все инварианты дерева при модификации данных и запускать проверку целостности всей структуры в ключевых местах сразу по месту выполнения.
M> кто использует похожее и какие преимущества недостатки вы видите ?
Если вас устраивает получающаяся скорость, зачем вам такие сложные структуры данных, как дерево? Упрощайте свои структуры данных (за счет скорости), и ошибок станет меньше.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, minorlogic, Вы писали:
ВВ>>>Хорошо. Тогда скажите, чем описанный вами способ отличается от юнит-теста? M>>Я уже описал. Такой подход не требует создания дополнительного окружения для теста.
ВВ>Ну так все равно не ясно, в чем это отличия для теста. ВВ>1) Положим, у нас есть юнит-тест, создающий некий объект в хранилище и запускающий валидацию хранилище. ВВ>2) Есть другой тест, который запускает валидацию после "штатного" сохранения. Он "включен", когда приложение развернуто в тесте и какой-нибудь тестер вводит данные в формочку и нажимает сохранить.
ВВ>Зачем вам нужен второй вариант? Что он дает? Чем он отличается от 1?
Отличается тем, что работает на реальных данных в реальном окружении. Не ребует дополнительной инфраструктуры для тестирования, не тратит ресурсы на на создание дополнительной инфраструктуры для тестирования. Сценарий работы можно автоматизировать.
ВВ> Тестироваться там будет ровно то же самое что и в варианте 1.
Не совсем. Автоматически будут потдерживаться изменения данных и сценарием использования. Покрытие данными может быть шире (а можеи уже).
ВВ> Причем вариант 1 я могу запускать чуть ли не на каждом коммите для регрессион тестирования, а с 2 как быть?
Инвертирование спарсовой матрицы, заранее фиксированной структуры.
Алгоритм нетривиальный, а вот про верить результат инвертирования можно используя денсовый алгоритм инвертирования. В условиях жетской нехватке времени пишем в коде рантаймовую проверку и сравниваем инвертированные матрицы.
Здравствуйте, minorlogic, Вы писали:
M>Пример номер 2.
M>Инвертирование спарсовой матрицы, заранее фиксированной структуры. M>Алгоритм нетривиальный, а вот про верить результат инвертирования можно используя денсовый алгоритм инвертирования. В условиях жетской нехватке времени пишем в коде рантаймовую проверку и сравниваем инвертированные матрицы.
А почему не взять какие-то легкопроверяемые характеристики? По идее, даже подсчитать и сравнить детерминанты будет значительно дешевле (хотя тут подзабыл теорию)