Недавно я писал небольшой кусочек кода, который по-человечески укажет, сколько лет событию. Например, это может указывать на то, что событие произошло «Три недели назад», «Месяц назад» или «Вчера».
Требования были относительно ясны, и это был идеальный случай для разработки, основанной на тестировании. Я писал тесты один за другим, реализуя код для прохождения каждого теста, и все, казалось, работало идеально. Пока ошибка не появилась в производстве.
Вот соответствующий кусок кода:
now = datetime.datetime.utcnow()
today = now.date()
if event_date.date() == today:
return "Today"
yesterday = today - datetime.timedelta(1)
if event_date.date() == yesterday:
return "Yesterday"
delta = (now - event_date).days
if delta < 7:
return _number_to_text(delta) + " days ago"
if delta < 30:
weeks = math.floor(delta / 7)
if weeks == 1:
return "A week ago"
return _number_to_text(weeks) + " weeks ago"
if delta < 365:
... # Handle months and years in similar manner.
Тесты проверяли случай события, происходящего сегодня, вчера, четыре дня назад, две недели назад, неделю назад и т. Д., И код был построен соответствующим образом.
Что я пропустил, так это то, что событие может произойти позавчера, когда оно произошло один день назад: например, событие, которое произошло двадцать шесть часов назад, было бы один день назад, а не совсем вчера, если сейчас 1 час ночи. Точнее, это одно очко что-то, но так как delta
это целое число, оно будет только один. В этом случае приложение отображает «Один день назад», что является неожиданным и необработанным в коде. Это можно исправить, добавив:
if delta == 1:
return "A day ago"
только после вычисления delta
.
Хотя единственным негативным последствием этой ошибки является то, что я потратил полчаса, размышляя о том, как может произойти этот случай (и полагая, что это связано с часовыми поясами, несмотря на равномерное использование UTC в коде), его присутствие беспокоит меня. Это указывает на то, что:
- Совершенно легко совершить логическую ошибку даже в таком простом исходном коде.
- Разработка через тестирование не помогла.
Также беспокоит то, что я не вижу, как можно избежать таких ошибок. Помимо размышлений перед написанием кода, единственный способ, которым я могу придумать, - это добавить множество утверждений для случаев, которые, как я считаю, никогда не произойдут (как я полагал, что день назад обязательно является вчерашним днем), а затем просматривать каждую секунду для последние десять лет проверяют наличие каких-либо нарушений утверждений, что кажется слишком сложным.
Как я мог избежать создания этой ошибки в первую очередь?
источник
Ответы:
Такого рода ошибки вы обычно обнаруживаете на этапе рефакторинга : красный / зеленый / рефактор. Не забудь этот шаг! Рассмотрим рефакторинг, подобный следующему (не проверено):
Здесь вы создали 3 функции на более низком уровне абстракции, которые гораздо более сплоченные и их проще тестировать в изоляции. Если вы пропустите нужный промежуток времени, он упадет, как больной палец, в более простых вспомогательных функциях. Кроме того, удаляя дублирование, вы уменьшаете вероятность ошибки. На самом деле вам придется добавить код для реализации вашего разбитого случая.
Другие более тонкие тестовые случаи также легче приходят на ум, когда вы смотрите на переработанную форму, подобную этой. Например, что
best_unit
делать, еслиdelta
отрицательный?Другими словами, рефакторинг не только для того, чтобы сделать его красивым. Людям легче обнаружить ошибки, которые не может компилятор.
источник
pluralize
работать только подмножество английских слов.pluralize
с использованиемnum
иunit
создание какого-либо ключа для извлечения строки формата из некоторого файла таблицы / ресурса. ИЛИ вам может потребоваться полное переписывание логики, потому что вам нужны разные единицы измерения ;-)Кажется, что это помогло, просто у вас не было теста для сценария «день назад». Предположительно, вы добавили тест после того, как этот случай был найден; это все еще TDD, в том случае, когда при обнаружении ошибок вы пишете юнит-тест для обнаружения ошибки, а затем исправляете ее.
Если вы забудете написать тест для поведения, TDD не сможет вам помочь; вы забыли написать тест и поэтому не пишите реализацию.
источник
datetime.utcnow()
исключить его из функции и вместо этого передатьnow
в качестве (воспроизводимого) аргумента.Тесты не сильно помогут, если проблема плохо определена. Вы, очевидно, смешиваете календарные дни с днями, считающимися в часах. Если придерживаться календарных дней, то в 1 час ночи 26 часов назад не вчерашний день. А если придерживаться часов, то 26 часов назад округляет до 1 дня назад независимо от времени.
источник
Ты не можешь TDD отлично защищает вас от возможных проблем, о которых вы знаете. Это не поможет, если вы столкнетесь с проблемами, которые вы никогда не рассматривали. Лучше всего, чтобы кто-то еще тестировал систему, и он может найти крайние случаи, которые вы никогда не рассматривали.
Связанное чтение: возможно ли достичь абсолютного нулевого состояния ошибки для крупномасштабного программного обеспечения?
источник
Есть два подхода, которые я обычно использую, которые могут помочь.
Во-первых, я ищу крайние случаи. Это места, где поведение меняется. В вашем случае поведение меняется в нескольких точках в последовательности целых положительных дней. Есть граничный случай в нуле, в один, в семь и т. Д. Затем я бы написал контрольные примеры в и вокруг краевых случаев. У меня были бы тестовые случаи в -1, 0, 1, 23, 24, 25, 6, 7, 8 и т. Д. Дней.
Второе, на что я обращаю внимание - это модели поведения. В вашей логике в течение нескольких недель у вас есть специальная обработка в течение одной недели. Вы, вероятно, имеете аналогичную логику в каждом из ваших других интервалов, не показанных. Эта логика не существует в течение нескольких дней, хотя. Я бы смотрел на это с подозрением, пока не смогу либо достоверно объяснить, почему этот случай отличается, либо я добавил бы логику.
источник
Вы не можете поймать логические ошибки, которые присутствуют в ваших требованиях с TDD. Но все же TDD помогает. В конце концов, вы нашли ошибку и добавили контрольный пример. Но в основном, TDD только гарантирует , что код соответствует вашей мысленной модели. Если ваша ментальная модель имеет недостатки, тестовые примеры не поймают их.
Но имейте в виду, что во время исправления ошибки тестовые примеры, которые вы уже проверили, не нарушали ни одного существующего, функционирующего поведения. Это очень важно, одну ошибку легко исправить, но ввести другую.
Чтобы заранее найти эти ошибки, вы обычно стараетесь использовать контрольные примеры, основанные на классе эквивалентности. используя этот принцип, вы выбираете один случай из каждого класса эквивалентности, а затем все крайние случаи.
В качестве примеров из каждого класса эквивалентности вы бы выбрали дату с сегодняшнего дня, вчера, несколько дней назад, ровно неделю назад и несколько недель назад. При проверке дат вы также должны убедиться, что в ваших тестах не использовалась системная дата, а использовалась заранее определенная дата для сравнения. Это также выделило бы некоторые крайние случаи: вы должны были запускать свои тесты в любое произвольное время дня, запускать их сразу после полуночи, непосредственно до полуночи и даже прямо в полночь. Это означает, что для каждого теста будет четыре базовых раза, с которыми он тестируется.
Затем вы систематически добавляете граничные случаи ко всем остальным классам. У тебя тест на сегодня. Поэтому добавьте время непосредственно перед и после того, как поведение должно переключиться. То же самое для вчерашнего дня. То же самое для недели назад и т. Д.
Скорее всего, систематически перечисляя все крайние случаи и записывая для них контрольные примеры, вы обнаружите, что в вашей спецификации отсутствуют некоторые детали, и добавляете их. Обратите внимание, что обработка дат - это то, что люди часто ошибаются, потому что люди часто забывают написать свои тесты, чтобы их можно было запускать в разное время.
Обратите внимание, однако, что большая часть того, что я написал, имеет мало общего с TDD. Речь идет о том, чтобы записать классы эквивалентности и убедиться, что ваши собственные спецификации достаточно подробно о них. Это процесс, с помощью которого вы минимизируете логические ошибки. TDD просто гарантирует, что ваш код соответствует вашей ментальной модели.
Придумать контрольные примеры сложно . Тестирование на основе класса эквивалентности - это еще не конец, и в некоторых случаях оно может значительно увеличить количество тестов. В реальном мире добавление всех этих тестов зачастую экономически нецелесообразно (хотя теоретически это следует делать).
источник
Почему нет? Это звучит как довольно хорошая идея!
Добавление контрактов (утверждений) в код является довольно надежным способом улучшить его корректность. Обычно мы добавляем их как предварительные условия для входа в функцию и постусловия для возврата функции. Например, мы могли бы добавить постусловие , что все возвращаемые значения являются либо формы «A [единицы] назад» или «[число] [единица измерения] S назад». Когда это делается дисциплинированным образом, это приводит к проектированию по контракту и является одним из наиболее распространенных способов написания высоконадежного кода.
Критически, контракты не предназначены для проверки; они являются такими же характеристиками вашего кода, как и ваши тесты. Однако вы можете выполнить тестирование через контракты: вызовите код в вашем тесте и, если ни один из контрактов не вызовет ошибок, тест пройден. Цикл каждой секунды последних десяти лет - это немного. Но мы можем использовать другой стиль тестирования, называемый тестированием на основе свойств .
В PBT вместо тестирования на конкретные выходные данные кода вы проверяете, что выходные данные подчиняются некоторому свойству. Например, одна свойства
reverse()
функции является то , что для любого спискаl
,reverse(reverse(l)) = l
. Преимущество написания таких тестов состоит в том, что вы можете заставить механизм PBT сгенерировать несколько сотен произвольных списков (и несколько патологических) и проверить, что все они обладают этим свойством. Если что-то не так , движок «сжимает» неудачный случай, чтобы найти минимальный список, который нарушает ваш код. Похоже, вы пишете Python, в котором гипотеза является основной платформой PBT.Поэтому, если вы хотите найти хороший способ найти более сложные крайние случаи, о которых вы можете и не подумать, совместное использование контрактов и тестирование на основе свойств очень поможет. Конечно, это не заменяет написание модульных тестов, но увеличивает его, что действительно является лучшим, что мы можем сделать как инженеры.
источник
/(today)|(yesterday)|([2-6] days ago)|...
), а затем вы можете запустить процесс со случайно выбранными входными данными, пока не найдете тот, который не входит в набор ожидаемых выходных данных. При таком подходе можно было бы поймать эту ошибку и не потребовать осознания того, что ошибка может существовать заранее.Это пример, где было бы полезно добавить немного модульности. Если подверженный ошибкам сегмент кода используется несколько раз, рекомендуется по возможности заключать его в функцию.
источник
TDD лучше всего работает как метод, если человек, пишущий тесты, состязателен. Это сложно, если вы не занимаетесь парным программированием, поэтому вы можете подумать еще об этом:
Это другая техника, которая применяется для написания правильного кода с TDD или без него, и, возможно, такая же сложная (если не более), чем собственно написание кода. Это то, что вам нужно практиковать, и это то, на что нет простого, простого и простого ответа.
Основной метод написания надежного программного обеспечения также является основным методом понимания того, как писать эффективные тесты:
Понять предварительные условия для функции - действительные состояния (то есть, какие предположения вы делаете для состояния класса, для которого используется функция) и допустимые диапазоны входных параметров - каждый тип данных имеет диапазон возможных значений - подмножество которых будет обрабатываться вашей функцией.
Если вы просто делаете не более чем явное тестирование этих допущений при вводе функции и гарантируете, что нарушение регистрируется или выдается, и / или выявляются ошибки функции без дальнейшей обработки, вы можете быстро узнать, не работает ли ваше программное обеспечение в рабочем состоянии, сделайте его надежным и устойчивы к ошибкам, и развивать свои навыки состязательного написания теста.
NB. Существует целая литература по предварительным и последующим условиям, инвариантам и т. Д., А также библиотеки, которые могут применять их с помощью атрибутов. Лично я не фанат такой формальности, но на это стоит обратить внимание.
источник
Это один из самых важных фактов о разработке программного обеспечения: абсолютно, совершенно невозможно писать код без ошибок.
TDD не спасет вас от появления ошибок, соответствующих тестовым примерам, о которых вы не задумывались. Это также не спасет вас от написания неправильного теста без его осознания, а затем от написания неверного кода, который проходит тест с ошибками. И каждая другая технология разработки программного обеспечения, когда-либо созданная, имеет подобные дыры. Как разработчики, мы несовершенные люди. В конце концов, нет никакого способа написать 100% безошибочный код. Этого никогда не было и не будет.
Это не значит, что вы должны оставить надежду. Хотя невозможно написать совершенно совершенный код, очень возможно написать код, в котором так мало ошибок, которые появляются в таких редких случаях, что программное обеспечение чрезвычайно практично в использовании. Программное обеспечение, которое не демонстрирует ошибочное поведение на практике , очень много можно написать.
Но написание этого требует, чтобы мы приняли тот факт, что мы будем производить программное обеспечение с ошибками. Почти каждая современная практика разработки программного обеспечения на каком-то уровне построена вокруг либо предотвращения появления ошибок в первую очередь, либо защиты от последствий ошибок, которые мы неизбежно создаем:
Окончательное решение проблемы, которую вы определили, заключается не в том, чтобы бороться с тем фактом, что вы не можете гарантировать, что вы будете писать код без ошибок, а в том, чтобы принять его. Применяйте лучшие отраслевые практики во всех областях вашего процесса разработки, и вы будете постоянно предоставлять своим пользователям код, который, хотя и не совсем совершенен, более чем достаточно надежен для работы.
источник
Вы просто не думали об этом случае раньше, и поэтому у вас не было тестового примера для него.
Это происходит постоянно и просто нормально. Это всегда компромисс, сколько усилий вы приложили для создания всех возможных тестовых случаев. Вы можете потратить бесконечное время, чтобы рассмотреть все тестовые случаи.
Для автопилота самолета вы потратите гораздо больше времени, чем для простого инструмента.
Это часто помогает подумать о допустимых диапазонах ваших входных переменных и проверить эти границы.
Кроме того, если тестером является другой человек, а не разработчик, часто встречаются более значимые случаи.
источник
Это еще одна логическая ошибка в вашем коде, для которой у вас еще нет модульного теста :) - ваш метод будет возвращать неверные результаты для пользователей в часовых поясах, отличных от UTC. Вам необходимо преобразовать как «сейчас», так и дату события в местный часовой пояс пользователя перед расчетом.
Пример: в Австралии событие происходит в 9 утра по местному времени. В 11 утра он будет отображаться как «вчера», потому что дата UTC изменилась.
источник
Пусть кто-нибудь еще напишет тесты. Таким образом, кто-то незнакомый с вашей реализацией может проверить редкие ситуации, о которых вы даже не думали.
Если возможно, введите тестовые наборы в виде коллекций. Это делает добавление еще одного теста так же просто, как добавление еще одной строки, как
yield return new TestCase(...)
. Это может пойти в направлении исследовательского тестирования , автоматизируя создание тестовых случаев: «Давайте посмотрим, что код возвращает за все секунды недели назад».источник
Похоже, вы заблуждаетесь, что если все ваши тесты пройдены, у вас нет ошибок. В действительности, если все ваши тесты пройдены, все известное поведение является правильным. Вы все еще не знаете, является ли неизвестное поведение правильным или нет.
Надеюсь, вы используете покрытие кода с вашим TDD. Добавьте новый тест для неожиданного поведения. Затем вы можете запустить только тест на неожиданное поведение, чтобы увидеть, какой путь он на самом деле проходит через код. Как только вы узнаете текущее поведение, вы можете внести изменения, чтобы исправить его, и когда все тесты пройдут снова, вы узнаете, что сделали это правильно.
Это по-прежнему не означает, что ваш код не содержит ошибок, просто он лучше, чем раньше, и снова все известное поведение корректно!
Правильное использование TDD не означает, что вы будете писать код без ошибок, это означает, что вы будете писать меньше ошибок. Ты говоришь:
Означает ли это, что в требованиях было указано поведение «больше, чем один день, но не вчера»? Если вы пропустили письменное требование, это ваша вина. Если вы поняли, что требования были неполными, так как вы их кодировали, это хорошо для вас! Если все, кто работал над требованиями, пропустили этот случай, вы не хуже других. Все делают ошибки, и чем тоньше они, тем легче их пропустить. Большая проблема в том, что TDD не предотвращает все ошибки!
источник
Да. Разработка через тестирование не меняет этого. Вы все еще можете создавать ошибки в реальном коде, а также в тестовом коде.
О, но это так! Прежде всего, когда вы заметили ошибку, у вас уже была готовая тестовая среда, и вам просто нужно было исправить ошибку в тесте (и сам код). Во-вторых, вы не знаете, сколько еще ошибок было бы, если бы вы не делали TDD в начале.
Ты не можешь Даже НАСА не нашло способ избежать ошибок; мы, меньшие люди, конечно, тоже нет.
Это заблуждение. Одно из величайших преимуществ TDD заключается в том, что вы можете программировать с меньшим количеством размышлений, потому что все эти тесты, по крайней мере, довольно хорошо улавливают регрессии. Кроме того, даже, или особенно с TDD, он не должен предоставлять код без ошибок в первую очередь (или ваша скорость разработки просто остановится).
Это явно противоречило бы принципу только того, что вы действительно хотите кодировать. Вы думали, что вам нужны эти дела, и так оно и было. Это был некритический кусок кода; как вы сказали, не было никакого ущерба, кроме как вы задавались вопросом об этом в течение 30 минут.
Для критически важного кода вы действительно можете делать то, что говорите, но не для своего повседневного стандартного кода.
Вы не Вы доверяете своим тестам, чтобы найти большинство регрессий; вы придерживаетесь цикла «красный-зеленый-рефакторинг», пишете тесты до / во время фактического кодирования и (важно!) применяете минимальное количество, необходимое для включения переключателя «красный-зеленый» (не больше, не меньше). В результате получится отличный тестовый охват, по крайней мере, положительный.
Когда, если нет, вы найдете ошибку, вы пишете тест, чтобы воспроизвести эту ошибку, и исправляете ошибку с наименьшим количеством работы, чтобы сделать этот тест из красного в зеленый.
источник
Вы только что обнаружили, что как бы вы ни старались, вы никогда не сможете обнаружить все возможные ошибки в вашем коде.
Так что это означает, что даже попытка поймать все ошибки - бесполезное упражнение, и поэтому вы должны использовать только такие методы, как TDD, как способ написания лучшего кода, кода, в котором меньше ошибок, а не 0 ошибок.
Это, в свою очередь, означает, что вы должны тратить меньше времени на использование этих методов и тратить сэкономленное время на работу над альтернативными способами поиска ошибок, которые просачиваются через сеть разработки.
альтернативы, такие как интеграционное тестирование или группа тестирования, тестирование системы, а также ведение журнала и анализ этих журналов.
Если вы не можете поймать все ошибки, то у вас должна быть стратегия для смягчения последствий ошибок, которые проскальзывают мимо вас. Если вам все равно придется делать это, то прилагать больше усилий для этого имеет больше смысла, чем пытаться (напрасно) остановить их в первую очередь.
В конце концов, бессмысленно тратить целое состояние на написание тестов, и в первый день, когда вы отдаете свой продукт покупателю, оно падает, особенно если у вас нет понятия, как найти и устранить эту ошибку. Устранение ошибок после и после доставки очень важно и требует большего внимания, чем большинство людей тратят на написание юнит-тестов. Сохраните модульное тестирование для сложных битов и не пытайтесь достичь совершенства заранее.
источник
That in turn means you should spend less time using these techniques
- но вы только что сказали, что это поможет с меньшим количеством ошибок ?!