Pull to refresh

Comments 31

Если взять все же чуть более сложный пример, чем просто возведение в степень — то замены особо то и нету.

У нас в компании есть бот который проходит игру — т.е. чистое автоматическое тестирование, по тем или иным тригерам собирается билд, заливается на девайс и включается бот встроенный игру на прохождение игры — и бот присылает отчет в результате.

В том числе, бот начинает играть не только с начала, а с определенных сохраненных сейвов. В том числе часть (несколько сотен) этих сейвов взяты с реальных сейвов игроков которые выбраны случайным образом. Ибо игрок может загнать игру в такую комбинацию, никакой бот не загонет.

Альтернативы вот этой случаности я не вижу. Генерить собственные сейвы очень сложная задача. Можно оставлять сейвы с предыдушего прогона бота — но основные баги как раз находятся из-за доигрывания сейвов игроков.
Да, на мой взгляд, вы все правильно делаете. Определить на глаз граничные условия в такой сложной штуке как игра — практически невозможно. Поэтому просто возьмем какую-то кучу сейвов и сделаем из них кучу тестов. Ну а дальше вступает в ход соображение и производительности: не можем мы каждый раз запускать всю кучу.
Мне кажется правильнее так: вместо одного теста со случайными величинами лучше сделать пару десятков тестов с фиксированными значениями и правильно их назвать. Например: БотСрезваетУгол, БотИдетВБрод, БотНаступаетНаБанан и тд. и тогда по картине падения тестов, даже не заглядывая в них вы можете догадаться где ошибка. А тест со случайными значениями еще надо разобрать, понять почему он упал, а это время и деньги.
последовательным надо быть — сперва фиксированные тесты, а потом случайные
Ибо игрок может загнать игру в такую комбинацию, никакой бот не загонет.


Не в этом ли проблема?

Если очень захотеть и потратить достаточное кол-во времени — можно свести игру к набору действий которые может выполнить игрок — и куда его это может привести. Конечно в результате мы получим полный перебор вариантов развития событий — который даже бот не сможет прогнать за обозримый отрезок времени.

Судя по сообщению сейвы вы выбирали случайным образом, а значит это не даёт вам никакой уверенности в результате. Вы только периодически сокращаете множество ошибок которые могут возникнуть.

Альтернатива полному перебору и случайности есть. Она называется парное тестирование (Pairwise testing). Правда в вашем случае скорее всего набор тест кейсов полученый парным тестированием будет очень велик, но подход всё же есть.

Коротко о парном тестировании — сперва вы определяете набор действий и значений которые может выполнять игрок(бот), далее вы определяете стандартные значения для каждого из действий/значений.
После этого — вы начинаете попарно изменять значения в каждом тесте.
В конкретно вашем случае — тестом будет скорее не прохождение игры от начала до конца, а совершение одного хода игры (если игра пошаговая), с контролем ожидаемого результата на каждом шагу.
Мухи отдельно, котлеты отдельно. Для автотестов выбрать определённые сейвы, можно случайным образом. И зафиксировать навечно. Выбрать достаточно много, чтобы хотя бы code coverage было хорошим.

А случайностью тестировать — это не автотесты — это стресстестирование. Можно гонять сервер с тестированием хоть сутки на пролёт, если найдётся вариант, выдающий баг, который не ловится обычными тестами, исправить баг, добавить руками этот вариант в тест.

Собственно, у вас, судя по описанию нет автотестов, а именно одно только стресс-тестирование. Т.е. нет понятие code coverage и т.д. В настоящих тестах можно было бы внести баг в код (закомментировать одну строчку), написать тест, который её ловит.

Не вижу чем ваш вариант отличается от истории «Мы написали конкурент mysql. Пользователь может может выполнять кучу разных SQL запросов в произвольном порядке! Сам сервер работает в N потоков и в любом месте может быть дедлок или какое-либо системное race condition! Нам просто это не протестировать, мы берём реальные SQL запросы пользователей и прогоняем их на разном железе»
Самый важный момент, который почти все забывают. Тест должен не только показывать, что ошибка есть, но и помогать её обнаружить, то есть быть составной частью цикла дебага.
Почемуже это самый важный момент?
Сам факт наличия бага нисколько не мешает и не пугает. Зная о баге — его можно найти, разными способами и затратами по времени, но все же. В худшем случае предупредить клиентов/временно отключить фичу/сделать костыль.
А вот не знание о существовании бага — это прямые потери и беды.
Так что самый важный момент — это по прежнему определение факта наличия бага, а не поиск причины.
Репорт «приложение иногда падает» — бесполезен. Необходим максимум информации о том при каких условиях и с каким состоянием.
Нет не бесполезен. Он позволяет узнать о проблеме. Если мое приложение падает — я буду искать почему падает.
Если я не знаю о том, что падает — искать не буду.
Все же очень просто.
И чтобы найти где и что падает таки придётся написать тест, который не маскирует проблемное место, который не отправляет входные параметры в /dev/null, который не портит стектрейс и который не мешает дебаггеру останавливаться в месте возникновения исключительной ситуации, который не рандомизирует входные параметры. Почему вы считаете, что тест, который ничего из перечисленного не делает, является значительно более сложным?
Потому, что если бы баги ловились автотестами — мы бы жили совершенно в другом мире. :)
А вообще, то что вы описали — это не «помогает искать баги», а не «не мешает искать».
Конечно, тест не должен портить стэк и не должен мешать дебагеру. Но именно что не должен мешать, а не должен помогать.
Зачастую лучшая помощь — это не мешать.
Дополню:
Нет никакого смысла писать изначально тест заточенный на поиск проблемы, т.к. проблема может никогда не появится, а время на усложнение теста будет потрачено.
Писать надо простой тест, который обнаружит факт наличия проблемы. И уже в случае возникновения проблемы надо усложнять тест, чтобы он более детально обрабатывал ситуацию. Или же вообще к другим инструментам прибегать.
Статья — капитантство. «Вы не должны использовать, но вот иногда можно».

Случайность — инструмент. Как и любой инструмент, применять его нужно с умом, в нужном месте.
На любой вопрос можно получить диаметрально противоположные ответы, а по любому поводу услышать взаимоисключающие мнения. Даже точки зрения уважаемых и признанных специалистов могут существенно разниться.

Такое можно сказать практически о любой сфере деятельности. ООП — нужно/не нужно, TDD — нужно/умерло. Кроме вас, вашу задачу никто не решит. Поэтому решает каждый для себя сам.

Компилятор, PDF Viewer, браузер, — куда вы уедете при создании таких инструментов без автоматического тестирования? Google на своих кластерах гоняет свои продукты на fuzzing-тестах непрерывно, выкашивая баги сотнями.
В то же время, да, если вы делаете тесты с элементом случайности, при этом не проверяя граничные значения или не сохраняя seed упавшего теста — грош цена такому тестированию.

Рекомендую курс Udacity по тестированию, по случайности там отдельная глава есть. Читает John Regehr, отличный специалист в своей области. К слову, в другом курсе рассказывается про язык хардварной верификации, в котором все переменные по умолчанию имеют случайные значения. Вот жуть-то, не правда ли?
А можете написать топик-ответ? Я думаю Вам есть что высказать
То о чем вы говорите является базовыми знаниями в теории функционального тестирования.
По теме — Анализ граничных значений (Boundary Value Analysis) и Эквивалентное разбиение (Equivalence Partitioning).

Использование же случайных значений — так же рассматривается как проблема функционального тестирования, а именно как путь борьбы с «эффектом пестицида». При этом случайные значения беруться только в пределах какого-то из полученных разбиений, а не на всём множестве значений.

В итоге чтобы сделать хорошее автоматизированное тестирование — человеку неплохо бы подтянуть базу по функциональному тестированию.
Проблема с граничными значениями часто в том, что программист не представляет себе реально граничных значений. Если бы представлял, то сразу бы написал без багов, а так и баг допустит, и тест не напишет. Несколько реальных примеров:
1. Функция бинарного поиска в сортированном массиве в куче языков программирования была сломана десятки лет для массивов длиной более 2^30 записей. Никому не приходило в голову, что (a+b)/2 может выдать отрицательное число для 32-битных знаковых целых.
2. Функция преобразования строки в число с плавающей точкой более десяти лет зацикливалась на некоторых входных строках. Миллионы людей пользовались, но никто не написал автотест на этот граничный случай.
3. Некто написал (на Java) код вроде table[Math.abs(str.hashCode())%size]. Многим ли придёт в голову написать тест на этот код, подав на вход строку «polygenelubricants» и одновременно size, не являющийся степенью двойки? Тот, кому бы пришло в голову написать такой тест, никогда бы не написал такой код.

Так что будет здорово, если рэндом случайно выстрелит в тот граничный случай, о котором вы не подумали. Конечно, зависит от ситуации: иногда граничный случай проявляется исключительно редко. Но ведь цель не написать безглючную программу, а написать программу, глюки которой не увидят клиенты.
Написание тестов не отменяет наличие багов в коде и человеческого фактора. Пример с бинарным поискам не релевантен вообще, т.к. для него нужны огромные данные, чтобы было переполнение.

Ничего вы не добьётесь тем, что будете добавлять случайность в тесты. Когда тот самый «2.2250738585072012e-308» вам попадётся (а именно, раз в 10 000 лет), в скажите «а, опять хрупкий тест», потому что, на самом деле он у вас падает пару раз в неделю по другим причинам, которые сложно найти из-за того, что их сложно воспроизвести из-за случайных данных.
То есть вы в своих тестах не выводите необходимую для воспроизведения информацию в отчёт? Это же несложно, а для случайных тестов вдвойне обязательно. Если же тест зациклился, тут ещё проще: вы приходите утром, убеждаетесь, что ночная сборка тестов почему-то всё ещё выполняется, снимаете дамп памяти и смотрите, что там происходит. Либо какой-нибудь хадсон по таймауту сам уже снял вам дамп и прибил сборку.
В вашем идеальном мире, отладночной информации всегда достаточно чтобы исправить баг. Т.е. есть баг, есль вывод какой-то информации. Программист его чинит, ни разу не запуская тест. Вообще ни разу.
Даже когда он написал код, он его не запускает и не проверяет, а сразу коммитит и заканчивает работу, даже не пытаясь посмотреть на результат.

Я же больше говорю про «воспроизведение бага» т.е. реальные запуски теста с тем чтобы баг повторился (и желательно чтобы это было не трудоёмко).
Именно потому и нужно разделять программиста и тестировщика и нельзя возлагать на разработчика ответственность за тестирование своих же поделок. Никто не пишет кода с багами, каждый разработчик думает что то что он написал работает правильно. И исходит из этого. Плюс к тому — умения для тестирования нужны не те же что для разработки. Это просто разные поля деятельности для которых нужны отличные образы мышления и подготовка.

Вот тестировщик сможет определить правильные граничные значения. Работа у него такая, он этим с утра до вечера занимается.
Следует различать тесты, направленные на обнаружение ошибок в новой функциональности, и тесты, направленные на закрепление существующей. Первые должен писать тестировщик, вторые — программист.
Возникает невольный вопрос: а почему?
Потому что программист знает (ну, в идеале :), что он написал, а тестировщик знает (ну, в идеале :), что программист должен был написать. По сути тест, написанный программистом, является отчётом о работе по заданию, а при методологиях типа TDD/BDD одновременно (и прежде всего) и заданием. Не задача программиста искать ошибки в понимании им задания — это не эффективно. Но и не задача тестировщика искать ошибки в реализации правильно понятого задания — это тоже не эффективно.
Вроде бы всё написано правильно, но непонятно как это подкрепляет точку зрения о том, что разработчик должен писать тесты. На мой взгляд единственные тесты, которые может и, возможно, должен писать разработчик — это юнит тесты, вещи для контроля что его код работает именно так как он задумал. Но это абсолютно никак не коррелирует с заданием, кроме как через голову этого самого разработчика. Задача таких тестов только помочь разработчику удостовериться что то что он сделал работает так как он хочет. Именно как он сам задумал, а не как написано в задании.

А любые тесты со стороны функционала продукта являются тестами, которые проверяют в том числе работу головы разработчика, а не только правильность реализации разработчиком чего-либо. Поэтому те же регрессионные тесты, которые не являются тестами новой функциональности, а как раз закреплением существующей, тоже должны писать тестировщики — они должны взять задание, то как должен работать функционал для пользователя и написать тесты на все случаи жизни, которые удостоверят что всё работает именно так.

Разработчик этого делать не будет потому что ему это скучно и не сможет сделать, потому что его мозг не заточен на поиск каких-нибудь ошибок. Он может искать причину существующей ошибки, если на неё пожаловались, но не искать целенаправленно какую-то абстрактную ошибку, которой может и не быть. Разработчик это создатель, ему сама мысль о том что функционал, описанный в ТЗ может работать как-то не так противна и чужда как минимум до уровня миддла :)
Мне казалось, что именно для того чтобы совместить предсказуемость со случайностью и придуманы разные тестовые фреймворки с генераторами (aka property based testing), такие как QuickCheck и его многочисленные потомки.
Все это можно отнести к категории «я ищу ошибку». Вероятно, мне стоило подчеркнуть, что речь идет в первую очередь о регресионных тестах, которые программист использует при рарзработке.
Отнюдь. Тест на сгенерированных значениях точно также поможет найти регрессию при очередном рефакторинге или оптимизации как и любой другой тест. Проблема с этими фреймворками скорее в том, что генераторы частенько оказываются достаточно нетривиальными.
Также есть вариант с автоматической неслучайной генерацией.

Вот именно. никогда не использую случайные данные в тестах. если нужно что-то симулировать, или нужен большой объём данных, использую генераторы псевдослучайных чисел с заранее заданным seed. Т.е. последовательность чисел всегда одинаковая (может поменяться если поменять код теста).
Согласен. Если seed задан заранее, то мы можем смело случайными данные не считать: просто некий нетривиальный генератор. Правда, если это возможно, я бы рекомендовал использовать тривиальный генератор.
Sign up to leave a comment.