Перечисления - это просто конечные типы с собственными (надеюсь значащими) именами. Перечисление может иметь только одно значение, например, void
которое содержит только null
(некоторые языки называют это unit
и используют имя void
для перечисления без элементов!). Может иметь два значения, например, bool
которое имеет false
и true
. Может быть три, как colourChannel
с red
, green
и blue
. И так далее.
Если два перечисления имеют одинаковое количество значений, то они «изоморфны»; то есть, если мы систематически переключаем все имена, то мы можем использовать одно вместо другого, и наша программа не будет вести себя по-другому. В частности, наши тесты не будут вести себя иначе!
Например, result
содержание win
/ lose
/ draw
изоморфно вышеприведенному colourChannel
, поскольку мы можем заменить, например, colourChannel
на result
, red
с win
, green
с lose
и blue
с draw
и до тех пор, пока мы делаем это везде (производители и потребители, анализаторы и сериализаторы, записи базы данных, файлы журналов и т. Д. ) тогда не будет никаких изменений в нашей программе. Любые « colourChannel
тесты», которые мы написали, все равно пройдут, хотя их больше нет colourChannel
!
Кроме того, если перечисление содержит более одного значения, мы всегда можем переставить эти значения, чтобы получить новое перечисление с тем же числом значений. Поскольку число значений не изменилось, новое расположение изоморфно старому, и, следовательно, мы могли бы отключить все имена, и наши тесты все равно бы прошли (обратите внимание, что мы не можем просто переключить определение; мы должны все еще отключите все сайты использования также).
Это означает, что для машины перечисления являются «различимыми именами» и ничем иным . Единственное, что мы можем сделать с перечислением, это определить, являются ли два значения одинаковыми (например, red
/ red
) или разными (например, red
/ blue
). Так что это единственное, что может сделать «модульный тест», например
( red == red ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
( red != green) || throw TestFailure;
( red != blue ) || throw TestFailure;
...
Как говорит @ jesm00, такой тест проверяет реализацию языка, а не вашу программу. Эти тесты никогда не являются хорошей идеей: даже если вы не доверяете языковой реализации, вы должны тестировать ее извне , поскольку нельзя доверять правильному запуску тестов!
Такова теория; как насчет практики? Основная проблема, связанная с такой характеристикой перечислений, заключается в том, что программы «реального мира» редко бывают автономными: у нас есть устаревшие версии, удаленные / встроенные развертывания, исторические данные, резервные копии, действующие базы данных и т. Д., Поэтому мы никогда не сможем «отключиться» все вхождения имени, не пропуская некоторые использования.
Однако такие вещи не являются «обязанностью» самого перечисления: изменение перечисления может нарушить связь с удаленной системой, но, наоборот, мы могли бы решить такую проблему, изменив перечисление!
В таких случаях, перечисление является красно-селедка: что если одна система нуждается в этом , чтобы быть этот путь, и еще нужно, чтобы это было что путь? Это не может быть и то и другое, независимо от того, сколько тестов мы пишем! Настоящим виновником здесь является интерфейс ввода / вывода, который должен производить / потреблять четко определенные форматы, а не «любое целое число, которое выбирает интерпретатор». Таким образом, реальным решением является тестирование интерфейсов ввода / вывода : с помощью модульных тестов, чтобы проверить, анализирует ли он / печатает ли ожидаемый формат, и с помощью интеграционных тестов, чтобы проверить, что формат действительно принят другой стороной.
Мы все еще можем задаться вопросом, достаточно ли тщательно прорабатывается перечисление, но в этом случае перечисление снова представляет собой красную сельдь. На самом деле нас беспокоит сам набор тестов . Мы можем обрести уверенность здесь несколькими способами:
- Покрытие кода может сказать нам, достаточно ли разнообразных значений enum, поступающих из набора тестов, для запуска различных ветвей в коде. Если нет, мы можем добавить тесты, которые запускают непокрытые ветви, или генерировать более широкий спектр перечислений в существующих тестах.
- Проверка свойств может сказать нам, достаточно ли разнообразия ветвей в коде для обработки возможностей времени выполнения. Например, если код только обрабатывает
red
, а мы только тестируем red
, то мы имеем 100% охват. Свойство шашка будет (пытаться) генерировать контрпримеры наших утверждений, например, генерируя green
и blue
ценность , которые мы забыли проверить.
- Мутационное тестирование может сказать нам, действительно ли наши утверждения проверяют перечисление, а не просто следуют ветвям и игнорируют их различия.