Как программист, я полностью погрузился в философию TDD и прилагаю усилия для проведения обширных модульных тестов для любого нетривиального кода, который я пишу. Иногда этот путь может быть болезненным (поведенческие изменения вызывают каскадные изменения нескольких модульных тестов; требуется большое количество строительных лесов), но в целом я отказываюсь программировать без тестов, которые я могу запускать после каждого изменения, и мой код гораздо менее глючен, чем результат.
Недавно я играл с Haskell и его резидентной библиотекой тестирования QuickCheck. В отличие от TDD, QuickCheck делает упор на тестирование инвариантов кода, то есть определенных свойств, которые сохраняются для всех (или основных подмножеств) входных данных. Быстрый пример: стабильный алгоритм сортировки должен дать тот же ответ, если мы запустим его дважды, должен иметь увеличивающийся результат, должен быть перестановкой входных данных и т. Д. Затем QuickCheck генерирует множество случайных данных для проверки этих инвариантов.
Мне кажется, по крайней мере, для чистых функций (то есть функций без побочных эффектов - и если вы правильно делаете насмешку, вы можете преобразовать грязные функции в чистые), что инвариантное тестирование могло бы вытеснить модульное тестирование как строгий надмножество этих возможностей. . Каждый модульный тест состоит из входа и выхода (в императивных языках программирования «выходом» является не только возврат функции, но и любое измененное состояние, но это может быть инкапсулировано). Можно было бы создать генератор случайных входных данных, который был бы достаточно хорош, чтобы охватить все входные данные модульного теста, которые вы создали бы вручную (а затем и некоторые, потому что он будет генерировать случаи, о которых вы даже не подумали бы); Если вы обнаружите ошибку в своей программе из-за какого-либо граничного условия, вы улучшите свой генератор случайного ввода, чтобы он также генерировал этот случай.
Таким образом, проблема заключается в том, можно ли сформулировать полезные инварианты для каждой проблемы. Я бы сказал, что это так: когда у вас есть ответ, гораздо проще проверить, верен ли он, чем вычислить ответ в первую очередь. Обдумывание инвариантов также помогает прояснить спецификацию сложного алгоритма намного лучше, чем специальные тестовые примеры, которые поощряют своеобразное индивидуальное рассмотрение проблемы. Вы можете использовать предыдущую версию своей программы в качестве реализации модели или версию программы на другом языке. И т. Д. В конце концов, вы можете охватить все свои предыдущие тестовые примеры без необходимости явно кодировать ввод или вывод.
Я сошел с ума или я что-то понял?