Может ли инвариантное тестирование заменить модульное тестирование?

StackOverflow https://stackoverflow.com/questions/766867

  •  12-09-2019
  •  | 
  •  

Вопрос

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

Недавно я играл с Haskell, и ее жительница тестирования, QuickCheck. В моде, отчетливо отличается от TDD, QuickCheck уделяется тестированию инвариантов кода, то есть определенными свойствами, которые поддерживают все (или существенные подмножества) входных данных. Быстрый пример: стабильный алгоритм сортировки должен дать один и тот же ответ, если мы запустим его дважды, должен иметь увеличение вывода, должен быть перестановкой ввода и т. Д. Затем QuickCheck генерирует множество случайных данных, чтобы проверить эти инварианты.

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

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

Я сошел с ума, или я что -то к чему?

Это было полезно?

Решение

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

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

Другие советы

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

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

Сомнительно

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

Вводящие в заблуждение результаты

Я слышал о таких тестах, как:

  • reverse(reverse(list)) должен равняться list
  • unzip(zip(data)) должен равняться data

Было бы здорово знать, что они верны для широкого диапазона входов. Но оба эти теста пройдут, если функции просто вернут их вход.

Мне кажется, что вы хотели бы проверить это, например, reverse([1 2 3]) равно [3 2 1] Чтобы доказать правильное поведение по крайней мере в одном случае, тогда добавлять Некоторое тестирование со случайными данными.

Тест сложности

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

Хороший модульный тест, напротив, слишком прост, чтобы испортить или неправильно понять как читатель. Только опечатка может создать ошибку в «Ожидайте reverse([1 2 3]) равному [3 2 1]".

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top