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