Я читал об этом snafu: ошибка программирования стоит Citigroup $ 7 млн после того, как допустимые транзакции ошибочно принимались за тестовые данные в течение 15 лет .
Когда система была представлена в середине 1990-х годов, программный код отфильтровывал все транзакции, которым были присвоены трехзначные коды ветвей от 089 до 100, и использовал эти префиксы для целей тестирования.
Но в 1998 году компания начала использовать буквенно-цифровые отраслевые коды по мере расширения своего бизнеса. Среди них были коды 10B, 10C и т. Д., Которые система рассматривала как находящиеся в пределах исключенного диапазона, и поэтому их транзакции были удалены из любых отчетов, отправленных в SEC.
(Я думаю, это показывает, что использование неявного индикатора данных является ... неоптимальным. Было бы намного лучше заполнить и использовать семантически явное Branch.IsLive
свойство.)
Кроме того, моей первой реакцией было «Модульные тесты помогли бы здесь» ... но будут ли они?
Недавно я прочитал, почему большинство модульных тестов с интересом тратятся впустую , и поэтому мой вопрос: как бы выглядели модульные тесты, которые не прошли бы при введении буквенно-цифровых кодов ветвления?
источник
Ответы:
Вы действительно спрашиваете: «Помогли ли здесь модульные тесты?», Или вы спрашиваете: «Могли ли здесь помочь какие-либо тесты?».
Наиболее очевидная форма тестирования, которая могла бы помочь, - это предварительное условие в самом коде, что идентификатор ветви состоит только из цифр (предположим, что это предположение основано на кодере при написании кода).
Это могло произойти в каком-то интеграционном тесте, и как только будут введены новые буквенно-цифровые идентификаторы ветвей, утверждение разрушится. Но это не юнит тест.
В качестве альтернативы может быть проведен интеграционный тест процедуры, которая генерирует отчет SEC. Этот тест гарантирует, что каждый реальный идентификатор ветви сообщает о своих транзакциях (и, следовательно, требует реального ввода данных, списка всех используемых идентификаторов ветви). Так что это тоже не юнит тест.
Я не вижу никакого определения или документации задействованных интерфейсов, но может случиться так, что модульные тесты не могли обнаружить ошибку, потому что модуль не был неисправен . Если подразделению разрешено предполагать, что идентификаторы ветвей состоят только из цифр, и разработчики никогда не принимали решения о том, что должен делать код, если это не так, то они не должнынаписать модульный тест для обеспечения определенного поведения в случае нецифровых идентификаторов, потому что тест отклонил бы гипотетическую допустимую реализацию модуля, который правильно обрабатывал буквенно-цифровые идентификаторы ветвей, и вы обычно не хотите писать модульный тест, который препятствует действительному будущие реализации и расширения. Или, может быть, один документ, написанный 40 лет назад, неявно определил (с помощью некоторого лексикографического диапазона в необработанном EBCDIC, вместо более удобного для человека правила сопоставления), что 10B является идентификатором теста, потому что в действительности он находится между 089 и 100. Но тогда 15 лет назад кто-то решил использовать его в качестве реального идентификатора, поэтому «ошибка» не заключается в блоке, который правильно реализует исходное определение: он заключается в процессе, который не заметил, что 10B определен как тестовый идентификатор и, следовательно, не должен назначаться ветви. То же самое произошло бы в ASCII, если вы определили 089-100 в качестве диапазона тестирования, а затем ввели идентификатор 10 $ или 1,0. Просто так получается, что в EBCDIC цифры идут после букв.
Один модульный тест (или, возможно, функциональный тест), который возможновозможно, спас день, является тестом модуля, который генерирует или проверяет новые идентификаторы ветви. Этот тест подтвердит, что идентификаторы должны содержать только цифры, и будет записан, чтобы позволить пользователям идентификаторов ветвей предполагать то же самое. Или, может быть, где-то есть модуль, который импортирует реальные идентификаторы ветвей, но никогда не видит тестовые, и который может быть проверен модулем, чтобы убедиться, что он отклоняет все тестовые идентификаторы (если идентификаторы состоят только из трех символов, мы можем перечислить их все и сравнить поведение валидатор к тестовому фильтру, чтобы убедиться, что они совпадают, что касается обычного возражения против выборочных тестов). Затем, когда кто-то изменил правила, модульный тест не прошел бы, так как он противоречит новому обязательному поведению.
Поскольку тест проводился по уважительной причине, точка, в которой вам нужно удалить его из-за изменившихся бизнес-требований, дает возможность кому-то получить работу: «найдите в коде все места, которые зависят от поведения, которое мы хотим менять". Конечно, это сложно и, следовательно, ненадежно, так что это никоим образом не гарантирует спасения дня. Но если вы фиксируете свои предположения в тестах юнитов, свойства которых вы приобретаете, то вы даете себе шанс, и поэтому усилия не пропадают впустую.
Я согласен, конечно, что если бы единица не была определена в первую очередь с помощью ввода «забавной формы», то проверять было бы нечего. Непросто правильно проверить разделение пространства имен, поскольку сложность заключается не в реализации вашего забавного определения, а в том, чтобы все понимали и уважали ваше забавное определение. Это не локальное свойство одной единицы кода. Кроме того, изменение некоторого типа данных с «строки цифр» на «строку буквенно-цифровых символов» сродни созданию программы, основанной на ASCII, для обработки Unicode: это будет непросто, если ваш код сильно связан с исходным определением, и когда тип данных является основополагающим для того, что делает программа, тогда он часто тесно связан.
Если ваши юнит-тесты иногда терпят неудачу (например, во время рефакторинга) и при этом дают вам полезную информацию (например, ваши изменения неверны), тогда усилия не пропали даром. Чего они не делают, так это проверяют, работает ли ваша система. Так что, если вы пишете модульные тесты вместо функциональных и интеграционных тестов, вы можете использовать свое время неоптимально.
источник
Модульные тесты могли бы обнаружить, что коды ветвей 10B и 10C были ошибочно классифицированы как «тестирующие ветви», но я считаю маловероятным, что тесты для этой классификации ветвей были бы достаточно обширными, чтобы уловить эту ошибку.
С другой стороны, выборочные проверки сгенерированных отчетов могли бы выявить, что разветвленные 10B и 10C постоянно отсутствовали в отчетах гораздо раньше, чем за 15 лет, в течение которых ошибка была оставлена.
Наконец, это хорошая иллюстрация, почему плохая идея смешивать данные тестирования с реальными производственными данными в одной базе данных. Если бы они использовали отдельную базу данных, которая содержит данные тестирования, не было бы необходимости отфильтровывать это из официальных отчетов, и было бы невозможно отфильтровать слишком много.
источник
Программное обеспечение должно было обрабатывать определенные бизнес-правила. Если бы были модульные тесты, модульные тесты проверили бы, правильно ли программное обеспечение обрабатывает бизнес-правила.
Бизнес-правила изменились.
Очевидно, никто не понимал, что бизнес-правила изменились, и никто не изменил программное обеспечение, чтобы применить новые бизнес-правила. Если бы были модульные тесты, эти модульные тесты пришлось бы менять, но никто бы этого не сделал, потому что никто не понимал, что бизнес-правила изменились.
Так что нет, юнит тесты не поймали бы это.
Исключение составляют случаи, когда модульные тесты и программное обеспечение были созданы независимыми группами, а команда, выполняющая модульные тесты, изменила тесты, чтобы применить новые бизнес-правила. Тогда модульные тесты не пройдут, что, надеюсь, приведет к смене программного обеспечения.
Конечно, в том же случае, если было изменено только программное обеспечение, а не юнит-тесты, то и юнит-тесты тоже не пройдут. Всякий раз, когда модульный тест не проходит, это не означает, что программное обеспечение неверно, это означает, что либо программное обеспечение, либо модульный тест (иногда оба) неправильны.
источник
Нет. Это одна из больших проблем с модульным тестированием: они вводят вас в заблуждение о безопасности.
Если все ваши тесты пройдены, это не значит, что ваша система работает правильно; это означает, что все ваши тесты проходят . Это означает, что части вашего дизайна, о которых вы сознательно думали и для которых написали тесты, работают так, как вы сознательно думали, что они будут, что на самом деле не так уж и важно: это было то, на что вы действительно обращали пристальное внимание к, так что, скорее всего, вы все правильно поняли! Но это не делает ничего, чтобы поймать случаи, о которых вы никогда не задумывались, такие как этот, потому что вы никогда не думали написать тест для них. (И если бы вы это сделали, вы бы знали, что это означало, что изменения кода были необходимы, и вы бы их изменили.)
источник
Нет, не обязательно
Первоначальное требование заключалось в использовании числовых кодов ветвления, поэтому для компонента, который принимал бы различные коды и отклонял любые, например, 10В, проводился модульный тест. Система была бы передана как работающая (что это было).
Затем требование изменилось бы, и коды были бы обновлены, но это означало бы, что код модульного теста, который предоставил неверные данные (теперь это хорошие данные), должен быть изменен.
Теперь мы предполагаем, что люди, управляющие системой, будут знать, что это так, и будут менять модульный тест для обработки новых кодов ... но если бы они знали, что это происходит, они также знали бы, что нужно изменить код, который обрабатывает эти коды. коды в любом случае .. и они этого не сделали. Модульный тест, который первоначально отклонил код 10B, с радостью сказал бы, что «все хорошо здесь» при запуске, если вы не знали, как обновить этот тест.
Модульное тестирование хорошо для оригинальной разработки, но не для тестирования системы, особенно не спустя 15 лет после того, как требования давно забыты.
Что им нужно в такой ситуации, так это комплексный интеграционный тест. Тот, где вы можете передать данные, которые вы ожидаете работать и посмотреть, если это работает. Кто-то заметил бы, что их новые входные данные не дали отчета, а затем продолжил бы расследование.
источник
Типовое тестирование (процесс тестирования инвариантов с использованием случайно сгенерированных достоверных данных, примером которого является библиотека тестирования Haskell QuickCheck и различные порты / альтернативы, вдохновленные ею на других языках), вполне могли бы решить эту проблему, модульное тестирование почти наверняка не было бы выполнено ,
Это связано с тем, что, когда правила достоверности кодов ветвления были обновлены, вряд ли кто-то мог подумать проверить эти конкретные диапазоны, чтобы убедиться, что они работают правильно.
Однако, если используется типовое тестирование, кто-то должен был во время реализации исходной системы написать пару свойств: одно для проверки того, что конкретные коды для тестовых ветвей были обработаны как тестовые данные, а другое - чтобы убедиться, что никаких других кодов нет. были ... когда определение типа данных для кода ветви было обновлено (что потребовалось бы для того, чтобы можно было проверить, работало ли любое изменение кода ветви с цифры на цифру), этот тест начал бы тестирование значений в новый диапазон и, скорее всего, выявил бы ошибку.
Конечно, QuickCheck был впервые разработан в 1999 году, поэтому было уже слишком поздно, чтобы разобраться в этой проблеме.
источник
Я действительно сомневаюсь, что модульное тестирование поможет решить эту проблему. Похоже, что это одна из тех ситуаций с туннельным зрением, потому что функциональность была изменена для поддержки новых кодов ветвей, но это не было выполнено во всех областях системы.
Мы используем модульное тестирование для разработки класса. Повторный запуск модульного теста требуется только в том случае, если проект изменился. Если конкретный модуль не изменяется, то неизмененные модульные тесты будут возвращать те же результаты, что и раньше. Модульные тесты не будут показывать вам влияние изменений на другие модули (если они есть, вы не пишете модульные тесты).
Вы можете только разумно обнаружить эту проблему с помощью:
Отсутствие достаточного сквозного тестирования вызывает больше беспокойства. Вы не можете полагаться на модульное тестирование в качестве единственного или основного теста для изменений системы. Похоже, что требуется только кто-то, чтобы запустить отчет о недавно поддерживаемых форматах кода филиала.
источник
Утверждение, встроенное во время выполнения, могло бы помочь; например:
bool isTestOnly(string branchCode) { ... }
Смотрите также:
источник
Вывод из этого - Fail Fast .
У нас нет кода, и при этом у нас нет многих примеров префиксов, которые являются или не являются префиксами тестовой ветви в соответствии с кодом. Все, что у нас есть, это:
Тот факт, что код допускает числа и строки, более чем немного странен. Конечно, 10B и 10C можно считать шестнадцатеричными числами, но если все префиксы обрабатываются как шестнадцатеричные числа, 10B и 10C выходят за пределы тестового диапазона и будут рассматриваться как реальные ветви.
Это, вероятно, означает, что префикс хранится в виде строки, но в некоторых случаях рассматривается как число. Вот самый простой код, который я могу себе представить, который повторяет это поведение (используя C # в иллюстративных целях):
На английском языке, если строка является числом и находится между 89 и 100, это тест. Если это не число, это тест. В противном случае это не тест.
Если код следует этому шаблону, ни один модульный тест не обнаружил бы это во время развертывания кода. Вот несколько примеров модульных тестов:
Модульный тест показывает, что «10B» следует рассматривать как тестовую ветвь. Пользователь @ gnasher729 выше говорит, что изменились бизнес-правила, и это показывает последнее утверждение выше. В какой-то момент это утверждение должно было переключиться на
isFalse
, но этого не произошло. Модульные тесты запускаются во время разработки и сборки, но потом ни в коем случае.Какой урок здесь? Код должен каким-то образом сигнализировать о получении неожиданного ввода. Вот альтернативный способ написания этого кода, который подчеркивает, что он ожидает, что префикс будет числом:
Для тех, кто не знает C #, возвращаемое значение указывает, смог ли код проанализировать префикс из заданной строки. Если возвращаемое значение равно true, вызывающий код может использовать переменную isTest out, чтобы проверить, является ли префикс ветви префиксом теста. Если возвращаемое значение равно false, вызывающий код должен сообщить, что данный префикс не ожидается, а переменная isTest out не имеет смысла и должна игнорироваться.
Если вы согласны с исключениями, вы можете сделать это:
Эта альтернатива является более простой. В этом случае вызывающему коду необходимо перехватить исключение. В любом случае код должен иметь возможность сообщать вызывающей стороне, что он не ожидает strPrefix, который не может быть преобразован в целое число. Таким образом, код быстро терпит неудачу, и банк может быстро найти проблему без штрафа SEC.
источник
Так много ответов и даже ни одной цитаты Дейкстры:
Поэтому это зависит. Если код был протестирован правильно, скорее всего, эта ошибка не будет существовать.
источник
Я думаю, что модульный тест здесь гарантировал бы, что проблема никогда не существовала в первую очередь.
Учтите, вы написали
bool IsTestData(string branchCode)
функцию.Первый модульный тест, который вы пишете, должен быть для пустой и пустой строки. Тогда для строк неправильной длины, затем для нецелых строк.
Чтобы все эти тесты прошли успешно, вам нужно добавить проверку параметров в функцию.
Даже если вы затем проверяете только «хорошие» данные 001 -> 999, не думая о возможности 10А, проверка параметров заставит вас переписать функцию, когда вы начнете использовать алфавитно-цифровые символы, чтобы избежать исключений, которые она выдаст.
источник
IsValidBranchCode
для выполнения этой проверки? И эта функция, вероятно, была бы изменена без необходимости изменятьIsTestData
? Так что, если вы тестируете только «хорошие данные», тест не помог бы. Для того, чтобы начать сбои, тест граничного случая должен был бы включать в себя некоторый теперь действующий код ветвления (а не просто некоторые еще недействительные).