Как избежать логических ошибок в коде, когда TDD не помог?


67

Недавно я писал небольшой кусочек кода, который по-человечески укажет, сколько лет событию. Например, это может указывать на то, что событие произошло «Три недели назад», «Месяц назад» или «Вчера».

Требования были относительно ясны, и это был идеальный случай для разработки, основанной на тестировании. Я писал тесты один за другим, реализуя код для прохождения каждого теста, и все, казалось, работало идеально. Пока ошибка не появилась в производстве.

Вот соответствующий кусок кода:

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 в коде), его присутствие беспокоит меня. Это указывает на то, что:

  • Совершенно легко совершить логическую ошибку даже в таком простом исходном коде.
  • Разработка через тестирование не помогла.

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

Как я мог избежать создания этой ошибки в первую очередь?


38
Имея контрольный пример для этого? Это похоже на то, как вы обнаружили это потом, и мешается с TDD.
Οurous

63
Вы только что поняли, почему я не являюсь поклонником разработки, управляемой тестами - по моему опыту, большинство ошибок, попадающих в производство, - это сценарии, о которых никто не задумывался. Разработка через тестирование и модульные тесты ничего не делают для этого. (Тем не менее, юнит-тесты имеют значение при обнаружении ошибок, появившихся в будущих редакциях.)
Лорен Печтел

102
Повторяйте за мной: «Там нет серебряных пуль, включая TDD». Здесь нет процесса, нет набора правил, нет алгоритма, который вы можете использовать роботом для создания идеального кода. Если бы это было так, мы могли бы автоматизировать весь процесс и покончить с ним.
jpmc26

43
Поздравляем, вы заново открыли старую мудрость о том, что никакие тесты не могут доказать отсутствие ошибок. Но если вы ищете методы для создания лучшего покрытия возможной входной области, вам необходимо провести тщательный анализ области, граничных случаев и классов эквивалентности этой области. Все старые, хорошо известные методы, давно известные еще до изобретения термина TDD.
Док Браун

80
Я не пытаюсь быть вялым, но ваш вопрос можно перефразировать как «как я думаю о вещах, о которых я не думал?». Не уверен, что это имеет отношение к TDD.
Джаред Смит

Ответы:


57

Такого рода ошибки вы обычно обнаруживаете на этапе рефакторинга : красный / зеленый / рефактор. Не забудь этот шаг! Рассмотрим рефакторинг, подобный следующему (не проверено):

def pluralize(num, unit):
    if num == 1:
        return unit
    else:
        return unit + "s"

def convert_to_unit(delta, unit):
    factor = 1
    if unit == "week":
        factor = 7 
    elif unit == "month":
        factor = 30
    elif unit == "year":
        factor = 365
    return delta // factor

def best_unit(delta):
    if delta < 7:
        return "day"
    elif delta < 30:
        return "week"
    elif delta < 365:
        return "month"
    else:
        return "year"

def human_friendly(event_date):
    date = event_date.date()
    today = now.date()
    yesterday = today - datetime.timedelta(1)
    if date == today:
        return "Today"
    elif date == yesterday:
        return "Yesterday"
    else:
        delta = (now - event_date).days
        unit = best_unit(delta)
        converted = convert_to_unit(delta, unit)
        pluralized = pluralize(converted, unit)
        return "{} {} ago".format(converted, pluralized)

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

Другие более тонкие тестовые случаи также легче приходят на ум, когда вы смотрите на переработанную форму, подобную этой. Например, что best_unitделать, если deltaотрицательный?

Другими словами, рефакторинг не только для того, чтобы сделать его красивым. Людям легче обнаружить ошибки, которые не может компилятор.


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

Конечно, @Deduplicator, но в зависимости от того, на какие языки / культуры вы нацеливаетесь, вам может потребоваться только модификация pluralizeс использованием numи unitсоздание какого-либо ключа для извлечения строки формата из некоторого файла таблицы / ресурса. ИЛИ вам может потребоваться полное переписывание логики, потому что вам нужны разные единицы измерения ;-)
Халк

4
Проблема остается даже с этой рефакторизацией, которая заключается в том, что «вчера» не имеет особого смысла в очень крошечные часы утра (вскоре после 12:01 утра). С точки зрения человека, то, что произошло в 23:59, не меняется внезапно с «сегодня» на «вчера», когда часы проскальзывают за полночь. Вместо этого он меняется с «1 минуту назад» на «2 минуты назад». «Сегодня» слишком грубо с точки зрения того, что произошло, но несколько минут назад, а «вчера» чревато проблемами с совами.
Дэвид Хаммен,

@DavidHammen Это проблема юзабилити, и она зависит от того, насколько точным вы должны быть. Если вы хотите узнать хотя бы до часа, я бы не подумал, что «вчера» - это хорошо. «24 часа назад» гораздо яснее и является общеупотребительным человеческим выражением, чтобы подчеркнуть количество часов. Компьютеры, которые пытаются быть «дружественными для человека», почти всегда ошибаются и преувеличивают это до «вчера», что слишком расплывчато. Но чтобы узнать это, вам нужно опросить пользователей, чтобы узнать, что они думают. Для некоторых вещей вам действительно нужны точные дата и время, поэтому «вчера» всегда неверно.
Брандин

149

Разработка через тестирование не помогла.

Кажется, что это помогло, просто у вас не было теста для сценария «день назад». Предположительно, вы добавили тест после того, как этот случай был найден; это все еще TDD, в том случае, когда при обнаружении ошибок вы пишете юнит-тест для обнаружения ошибки, а затем исправляете ее.

Если вы забудете написать тест для поведения, TDD не сможет вам помочь; вы забыли написать тест и поэтому не пишите реализацию.


2
Можно было бы сказать, что если бы разработчик не использовал tdd, они с гораздо большей вероятностью могли бы пропустить и другие случаи.
Калеб

75
И, вдобавок ко всему, подумайте, сколько времени было сэкономлено, когда мы исправляем ошибку? Имея существующие тесты, они сразу поняли, что их изменение не нарушает существующее поведение. И они могли свободно добавлять новые тестовые примеры и рефакторинг, не выполняя обширные ручные тесты впоследствии.
Калеб

15
TDD только так хорошо, как написанные тесты.
Миндвин

Еще одно наблюдение: добавление теста для этого случая улучшит дизайн, заставив нас datetime.utcnow()исключить его из функции и вместо этого передать nowв качестве (воспроизводимого) аргумента.
Тоби Спейт

114

событие, происходящее двадцать шесть часов назад, было бы один день назад

Тесты не сильно помогут, если проблема плохо определена. Вы, очевидно, смешиваете календарные дни с днями, считающимися в часах. Если придерживаться календарных дней, то в 1 час ночи 26 часов назад не вчерашний день. А если придерживаться часов, то 26 часов назад округляет до 1 дня назад независимо от времени.


45
Это замечательный момент. Отсутствие требования не обязательно означает, что ваш процесс внедрения не удался. Это просто означает, что требование не было четко определено. (Или вы просто допустили человеческую ошибку, которая будет происходить время от времени)
Калеб

Это ответ, который я хотел сделать. Я бы определил спецификацию следующим образом: «если бы событием был этот календарный день, то есть текущая дельта в часах. Иначе используйте даты только для определения дельты». Часы тестирования полезны только в течение дня, если за этим разрешением подразумеваются дни.
Болдрикк

1
Мне нравится этот ответ, потому что он указывает на реальную проблему: моменты времени и даты - это две разные величины. Они связаны, но когда вы начинаете сравнивать их, дела идут на юг очень быстро. В программировании логика даты и времени - одна из самых сложных вещей, чтобы понять это правильно. Мне действительно не нравится, что многие даты-реализации в основном хранят дату как 0:00. Это вносит много путаницы.
Питер Б,

38

Ты не можешь TDD отлично защищает вас от возможных проблем, о которых вы знаете. Это не поможет, если вы столкнетесь с проблемами, которые вы никогда не рассматривали. Лучше всего, чтобы кто-то еще тестировал систему, и он может найти крайние случаи, которые вы никогда не рассматривали.

Связанное чтение: возможно ли достичь абсолютного нулевого состояния ошибки для крупномасштабного программного обеспечения?


2
Наличие тестов, написанных кем-то другим, кроме разработчика, всегда является хорошей идеей, это означает, что обеим сторонам необходимо пропустить одно и то же условие ввода для ошибки, чтобы она была запущена в производство.
Майкл Кей

35

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

Во-первых, я ищу крайние случаи. Это места, где поведение меняется. В вашем случае поведение меняется в нескольких точках в последовательности целых положительных дней. Есть граничный случай в нуле, в один, в семь и т. Д. Затем я бы написал контрольные примеры в и вокруг краевых случаев. У меня были бы тестовые случаи в -1, 0, 1, 23, 24, 25, 6, 7, 8 и т. Д. Дней.

Второе, на что я обращаю внимание - это модели поведения. В вашей логике в течение нескольких недель у вас есть специальная обработка в течение одной недели. Вы, вероятно, имеете аналогичную логику в каждом из ваших других интервалов, не показанных. Эта логика не существует в течение нескольких дней, хотя. Я бы смотрел на это с подозрением, пока не смогу либо достоверно объяснить, почему этот случай отличается, либо я добавил бы логику.


9
Это действительно важная часть TDD, о которой часто забывают, и об этом я редко видел в статьях и руководствах - очень важно протестировать крайние случаи и граничные условия, так как я считаю, что это источник 90% ошибок Одни ошибки,
переполнения и переполнения

2
@GoatInTheMachine - и 90% из этих 90% ошибок связаны с переходами на летнее время ..... Хахаха
Калеб

1
Вы можете сначала разделить возможные входные данные на классы эквивалентности, а затем определить граничные случаи на границах классов. С нашей стороны это усилие, которое может быть больше, чем усилие по разработке; стоит ли это того, зависит от того, насколько важно поставлять программное обеспечение настолько безошибочно, насколько это возможно, какой срок и сколько у вас денег и терпения.
Питер - Восстановить Монику

2
Это правильный ответ. Многие бизнес-правила требуют, чтобы вы разделяли диапазон значений на интервалы, когда они являются случаями, которые должны обрабатываться различными способами.
abuzittin gillifirca

14

Вы не можете поймать логические ошибки, которые присутствуют в ваших требованиях с TDD. Но все же TDD помогает. В конце концов, вы нашли ошибку и добавили контрольный пример. Но в основном, TDD только гарантирует , что код соответствует вашей мысленной модели. Если ваша ментальная модель имеет недостатки, тестовые примеры не поймают их.

Но имейте в виду, что во время исправления ошибки тестовые примеры, которые вы уже проверили, не нарушали ни одного существующего, функционирующего поведения. Это очень важно, одну ошибку легко исправить, но ввести другую.

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

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

Затем вы систематически добавляете граничные случаи ко всем остальным классам. У тебя тест на сегодня. Поэтому добавьте время непосредственно перед и после того, как поведение должно переключиться. То же самое для вчерашнего дня. То же самое для недели назад и т. Д.

Скорее всего, систематически перечисляя все крайние случаи и записывая для них контрольные примеры, вы обнаружите, что в вашей спецификации отсутствуют некоторые детали, и добавляете их. Обратите внимание, что обработка дат - это то, что люди часто ошибаются, потому что люди часто забывают написать свои тесты, чтобы их можно было запускать в разное время.

Обратите внимание, однако, что большая часть того, что я написал, имеет мало общего с TDD. Речь идет о том, чтобы записать классы эквивалентности и убедиться, что ваши собственные спецификации достаточно подробно о них. Это процесс, с помощью которого вы минимизируете логические ошибки. TDD просто гарантирует, что ваш код соответствует вашей ментальной модели.

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


12

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

Почему нет? Это звучит как довольно хорошая идея!

Добавление контрактов (утверждений) в код является довольно надежным способом улучшить его корректность. Обычно мы добавляем их как предварительные условия для входа в функцию и постусловия для возврата функции. Например, мы могли бы добавить постусловие , что все возвращаемые значения являются либо формы «A [единицы] назад» или «[число] [единица измерения] S назад». Когда это делается дисциплинированным образом, это приводит к проектированию по контракту и является одним из наиболее распространенных способов написания высоконадежного кода.

Критически, контракты не предназначены для проверки; они являются такими же характеристиками вашего кода, как и ваши тесты. Однако вы можете выполнить тестирование через контракты: вызовите код в вашем тесте и, если ни один из контрактов не вызовет ошибок, тест пройден. Цикл каждой секунды последних десяти лет - это немного. Но мы можем использовать другой стиль тестирования, называемый тестированием на основе свойств .

В PBT вместо тестирования на конкретные выходные данные кода вы проверяете, что выходные данные подчиняются некоторому свойству. Например, одна свойства reverse()функции является то , что для любого списка l, reverse(reverse(l)) = l. Преимущество написания таких тестов состоит в том, что вы можете заставить механизм PBT сгенерировать несколько сотен произвольных списков (и несколько патологических) и проверить, что все они обладают этим свойством. Если что-то не так , движок «сжимает» неудачный случай, чтобы найти минимальный список, который нарушает ваш код. Похоже, вы пишете Python, в котором гипотеза является основной платформой PBT.

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


2
Это как раз правильное решение проблемы такого рода. Набор допустимых выходных данных легко определить (вы могли бы дать регулярное выражение очень просто, что-то вроде этого /(today)|(yesterday)|([2-6] days ago)|...), а затем вы можете запустить процесс со случайно выбранными входными данными, пока не найдете тот, который не входит в набор ожидаемых выходных данных. При таком подходе можно было бы поймать эту ошибку и не потребовать осознания того, что ошибка может существовать заранее.
Жюль

@Jules См. Также проверку / тестирование свойств . Я обычно пишу тесты свойств во время разработки, чтобы охватить как можно больше непредвиденных случаев и заставить меня задуматься об общих свойствах / инвариантах. Я сохраняю одноразовые тесты для регрессий и тому подобного (что является проблемой автора)
Warbo

1
Если вы сделаете это много циклов в тестах, если будет занимать очень много времени, что противоречит одной из главных целей модульного тестирования: запустить тесты быстро !
CJ Деннис

5

Это пример, где было бы полезно добавить немного модульности. Если подверженный ошибкам сегмент кода используется несколько раз, рекомендуется по возможности заключать его в функцию.

def time_ago(delta, unit):
    delta_str = _number_to_text(delta) + " " + unit;
    if delta == 1:
        return delta_str + " ago"
    else:
        return delta_str = "s ago"

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 time_ago(delta, "day")

if delta < 30:
    weeks = math.floor(delta / 7)
    return time_ago(weeks, "week")

if delta < 365:
    months = math.floor(delta / 31)
    return time_ago(months, "month")

5

Разработка через тестирование не помогла.

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

  • Не пишите тесты, чтобы подтвердить, что тестируемая функция работает так, как вы ее сделали. Напишите тесты, которые намеренно сломают его.

Это другая техника, которая применяется для написания правильного кода с TDD или без него, и, возможно, такая же сложная (если не более), чем собственно написание кода. Это то, что вам нужно практиковать, и это то, на что нет простого, простого и простого ответа.

Основной метод написания надежного программного обеспечения также является основным методом понимания того, как писать эффективные тесты:

Понять предварительные условия для функции - действительные состояния (то есть, какие предположения вы делаете для состояния класса, для которого используется функция) и допустимые диапазоны входных параметров - каждый тип данных имеет диапазон возможных значений - подмножество которых будет обрабатываться вашей функцией.

Если вы просто делаете не более чем явное тестирование этих допущений при вводе функции и гарантируете, что нарушение регистрируется или выдается, и / или выявляются ошибки функции без дальнейшей обработки, вы можете быстро узнать, не работает ли ваше программное обеспечение в рабочем состоянии, сделайте его надежным и устойчивы к ошибкам, и развивать свои навыки состязательного написания теста.


NB. Существует целая литература по предварительным и последующим условиям, инвариантам и т. Д., А также библиотеки, которые могут применять их с помощью атрибутов. Лично я не фанат такой формальности, но на это стоит обратить внимание.


1

Это один из самых важных фактов о разработке программного обеспечения: абсолютно, совершенно невозможно писать код без ошибок.

TDD не спасет вас от появления ошибок, соответствующих тестовым примерам, о которых вы не задумывались. Это также не спасет вас от написания неправильного теста без его осознания, а затем от написания неверного кода, который проходит тест с ошибками. И каждая другая технология разработки программного обеспечения, когда-либо созданная, имеет подобные дыры. Как разработчики, мы несовершенные люди. В конце концов, нет никакого способа написать 100% безошибочный код. Этого никогда не было и не будет.

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

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

  • Сбор подробных требований позволяет нам узнать, как выглядит некорректное поведение в нашем коде.
  • Написание чистого, тщательно спроектированного кода облегчает, во-первых, избежать появления ошибок и облегчает их исправление, когда мы их идентифицируем.
  • Написание тестов позволяет нам создавать записи о том, что, по нашему мнению, может быть многими из худших возможных ошибок в нашем программном обеспечении, и доказывать, что мы избегаем по крайней мере этих ошибок. TDD производит эти тесты до того, как код, BDD выводит эти тесты из требований, а старомодное модульное тестирование производит тесты после написания кода, но все они предотвращают худшие регрессии в будущем.
  • Экспертные проверки означают, что каждый раз, когда код изменяется, по крайней мере две пары глаз видели код, уменьшая частоту попадания ошибок в мастер.
  • Использование средства отслеживания ошибок или отслеживания пользовательских историй, которое рассматривает ошибки как пользовательские истории, означает, что при появлении ошибок они отслеживаются и, в конечном итоге, устраняются, а не забываются и остаются для постоянных действий пользователей.
  • Использование промежуточного сервера означает, что перед основным выпуском у любых ошибок шоу-стоппера есть шанс появиться и с ними справиться.
  • Использование контроля версий означает, что в худшем случае, когда код с серьезными ошибками доставляется клиентам, вы можете выполнить аварийный откат и вернуть надежный продукт в руки своих клиентов, пока вы разбираетесь.

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


1

Вы просто не думали об этом случае раньше, и поэтому у вас не было тестового примера для него.

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

Для автопилота самолета вы потратите гораздо больше времени, чем для простого инструмента.

Это часто помогает подумать о допустимых диапазонах ваших входных переменных и проверить эти границы.

Кроме того, если тестером является другой человек, а не разработчик, часто встречаются более значимые случаи.


1

(и полагая, что это связано с часовыми поясами, несмотря на единообразное использование UTC в коде)

Это еще одна логическая ошибка в вашем коде, для которой у вас еще нет модульного теста :) - ваш метод будет возвращать неверные результаты для пользователей в часовых поясах, отличных от UTC. Вам необходимо преобразовать как «сейчас», так и дату события в местный часовой пояс пользователя перед расчетом.

Пример: в Австралии событие происходит в 9 утра по местному времени. В 11 утра он будет отображаться как «вчера», потому что дата UTC изменилась.


0
  • Пусть кто-нибудь еще напишет тесты. Таким образом, кто-то незнакомый с вашей реализацией может проверить редкие ситуации, о которых вы даже не думали.

  • Если возможно, введите тестовые наборы в виде коллекций. Это делает добавление еще одного теста так же просто, как добавление еще одной строки, как yield return new TestCase(...). Это может пойти в направлении исследовательского тестирования , автоматизируя создание тестовых случаев: «Давайте посмотрим, что код возвращает за все секунды недели назад».


0

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

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

Это по-прежнему не означает, что ваш код не содержит ошибок, просто он лучше, чем раньше, и снова все известное поведение корректно!

Правильное использование TDD не означает, что вы будете писать код без ошибок, это означает, что вы будете писать меньше ошибок. Ты говоришь:

Требования были относительно ясны

Означает ли это, что в требованиях было указано поведение «больше, чем один день, но не вчера»? Если вы пропустили письменное требование, это ваша вина. Если вы поняли, что требования были неполными, так как вы их кодировали, это хорошо для вас! Если все, кто работал над требованиями, пропустили этот случай, вы не хуже других. Все делают ошибки, и чем тоньше они, тем легче их пропустить. Большая проблема в том, что TDD не предотвращает все ошибки!


0

Совершенно легко совершить логическую ошибку даже в таком простом исходном коде.

Да. Разработка через тестирование не меняет этого. Вы все еще можете создавать ошибки в реальном коде, а также в тестовом коде.

Разработка через тестирование не помогла.

О, но это так! Прежде всего, когда вы заметили ошибку, у вас уже была готовая тестовая среда, и вам просто нужно было исправить ошибку в тесте (и сам код). Во-вторых, вы не знаете, сколько еще ошибок было бы, если бы вы не делали TDD в начале.

Также беспокоит то, что я не вижу, как можно избежать таких ошибок.

Ты не можешь Даже НАСА не нашло способ избежать ошибок; мы, меньшие люди, конечно, тоже нет.

Помимо размышлений, прежде чем писать код,

Это заблуждение. Одно из величайших преимуществ TDD заключается в том, что вы можете программировать с меньшим количеством размышлений, потому что все эти тесты, по крайней мере, довольно хорошо улавливают регрессии. Кроме того, даже, или особенно с TDD, он не должен предоставлять код без ошибок в первую очередь (или ваша скорость разработки просто остановится).

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

Это явно противоречило бы принципу только того, что вы действительно хотите кодировать. Вы думали, что вам нужны эти дела, и так оно и было. Это был некритический кусок кода; как вы сказали, не было никакого ущерба, кроме как вы задавались вопросом об этом в течение 30 минут.

Для критически важного кода вы действительно можете делать то, что говорите, но не для своего повседневного стандартного кода.

Как я мог избежать создания этой ошибки в первую очередь?

Вы не Вы доверяете своим тестам, чтобы найти большинство регрессий; вы придерживаетесь цикла «красный-зеленый-рефакторинг», пишете тесты до / во время фактического кодирования и (важно!) применяете минимальное количество, необходимое для включения переключателя «красный-зеленый» (не больше, не меньше). В результате получится отличный тестовый охват, по крайней мере, положительный.

Когда, если нет, вы найдете ошибку, вы пишете тест, чтобы воспроизвести эту ошибку, и исправляете ошибку с наименьшим количеством работы, чтобы сделать этот тест из красного в зеленый.


-2

Вы только что обнаружили, что как бы вы ни старались, вы никогда не сможете обнаружить все возможные ошибки в вашем коде.

Так что это означает, что даже попытка поймать все ошибки - бесполезное упражнение, и поэтому вы должны использовать только такие методы, как TDD, как способ написания лучшего кода, кода, в котором меньше ошибок, а не 0 ошибок.

Это, в свою очередь, означает, что вы должны тратить меньше времени на использование этих методов и тратить сэкономленное время на работу над альтернативными способами поиска ошибок, которые просачиваются через сеть разработки.

альтернативы, такие как интеграционное тестирование или группа тестирования, тестирование системы, а также ведение журнала и анализ этих журналов.

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

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


Это крайне неудачно. That in turn means you should spend less time using these techniques- но вы только что сказали, что это поможет с меньшим количеством ошибок ?!
JᴀʏM18

@ JᴀʏMᴇᴇ более прагматичное отношение , какой метод получает вас максимальную отдачу для ваших buck.I знают человек , которые гордятся тем , что они тратят в 10 раз писать тесты , чем они сделали на свой код, и они до сих пор есть ошибки Так быть разумными, а не догматической, о методах тестирования имеет важное значение. И интеграционные тесты должны быть использованы в любом случае, поэтому приложите больше усилий к ним, чем к юнит-тестированию.
gbjbaanb
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.