Следует ли проверять значения перечисления с помощью модульных тестов?

15

Если у вас есть только перечисление со значениями (нет методов, которые можно было бы сделать в Java), и это перечисление является частью бизнес-определения системы, следует ли писать для него модульные тесты?

Я думал, что они должны быть написаны, даже если они могут показаться простыми и избыточными. Я считаю, что то, что касается бизнес-спецификации, должно быть явно написано в тесте, будь то с помощью unit /gration / ui / и т.д. тесты или с использованием системы типов языка в качестве метода тестирования. Поскольку значения, которые должен иметь enum (например, в Java), с точки зрения бизнеса, не могут быть протестированы с использованием системы типов, я думаю, что для этого должен быть модульный тест.

Этот вопрос не похож на этот, поскольку он не решает ту же проблему, что и мой. В этом вопросе есть бизнес-функция (savePeople), и человек интересуется внутренней реализацией (forEach). Там есть средний бизнес-уровень (функция спасения людей), инкапсулирующий языковую конструкцию (forEach). Здесь языковая конструкция (enum) используется для определения поведения с точки зрения бизнеса.

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

IS1_SO
источник
19
Как именно выглядел бы модульный тест перечисления?
Джонршарп
@jonrsharpe Было бы утверждать, что значения, которые находятся внутри перечисления, это те, которые вы ожидаете. Я бы сделал это, перебирая значения перечисления, добавляя их в набор, например, в виде строк. Заказать этот набор. Сравните это с упорядоченным списком значений, написанным вручную в тесте. Они должны совпадать.
IS1_SO
1
@jonrsharpe, мне нравится думать о модульных тестах также как «определения» или «требования», написанные в коде. Модульный тест перечисления будет таким же простым, как проверка количества элементов в перечислении и их значений. Особенно в C #, где перечисления не являются классами, но могут быть напрямую отображены на целые числа, гарантируя их значения, а не программирование по совпадению, может оказаться полезным для целей сериализации.
Мачадо
2
ИМХО, это не намного полезнее, чем тестирование, если 2 + 2 = 4, чтобы проверить, верна ли Вселенная. Вы проверяете код, используя это перечисление, а не само перечисление.
Agent_L

Ответы:

39

Если у вас есть только перечисление со значениями (нет методов, которые можно было бы сделать в Java), и это перечисление является частью бизнес-определения системы, следует ли писать для него модульные тесты?

Нет, они просто государственные.

По сути, тот факт, что вы используете enum - это деталь реализации ; это то, что вы можете захотеть изменить в другой дизайн.

Проверка перечислений на полноту аналогична проверке наличия всех представимых целых чисел.

Однако проверка поведения, поддерживаемого перечислениями, является хорошей идеей. Другими словами, если вы начнете с проходного набора тестов и закомментируете какое-либо одно значение перечисления, то по крайней мере один тест должен провалиться (ошибки компиляции считаются ошибками).

VoiceOfUnreason
источник
5
Но в этом случае детали реализации совпадают с «истинной природой» данных, то есть набором (в математическом смысле) значений. Возможно, вы могли бы использовать неизменный набор, но там должны присутствовать те же значения. Если вы используете массив, то же самое необходимо сделать для проверки бизнес-логики. Я думаю, что загадка здесь заключается в том, что языковая конструкция очень хорошо совпадает с природой данных. Я не уверен, правильно ли я объяснил.
IS1_SO
4
@ IS1_SO - к мнению VOU, что один тест должен провалиться: правда? В этом случае вам не нужно было специально тестировать Enum. Не так ли? Может быть , это знак того, что вы могли бы смоделировать ваш код более просто и создать абстракцию над «истинной природой» данные - например , независимо от карт в колоде, вы действительно должны иметь представление [ Hearts, Spades, Diamonds, Clubs] если Вы когда-нибудь карту, если карта красная / черная?
anotherdave
1
@ IS1_SO скажем, у вас есть набор кодов ошибок, и вы хотите выбросить null_ptrошибку. Теперь это имеет код ошибки через enum. Код, проверяющий на наличие null_ptrошибок, ищет код и через enum. Так что это может иметь значение 5(например). Теперь вам нужно добавить еще один код ошибки. Перечисление изменено (допустим, мы добавили новое в начало перечисления). Значение null_ptrтеперь 6. Это проблема? Теперь вы возвращаете код ошибки 6и проверяете 6. Пока все логически непротиворечиво, у вас все хорошо, несмотря на то, что это изменение нарушает ваш теоретический тест.
Baldrickk
17

Вы не проверяете объявление enum . Вы можете проверить, имеет ли функция ввода / вывода ожидаемые значения перечисления. Пример:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

Вы не пишете тесты, проверяющие, тогда enum Parityопределяет имена Evenи Odd. Такой тест был бы бессмысленным, поскольку вы просто повторяли бы то, что уже указано в коде. Сказать одно и то же дважды не значит более правильно.

Вы делаете тесты записи проверочных GetParityслова будут возвращать Evenна 0, Oddдля 1 и так далее. Это ценно, потому что вы не повторяете код, вы проверяете поведение кода независимо от реализации. Если код внутри GetParityбыл полностью переписан, тесты все равно были бы действительными. Действительно, основными преимуществами модульных тестов является то, что они дают вам возможность безопасно переписывать и реорганизовывать код, гарантируя, что код по-прежнему работает, как и ожидалось.

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

JacquesB
источник
Я обновил свой вопрос, пытаясь найти ответ на этот вопрос, чтобы проверить, помогает ли это.
IS1_SO
@ IS1_SO: Хорошо, это меня смущает - вы динамически генерируете значения enum или что происходит?
JacquesB
Нет. Я имел в виду, что в этом случае языковая конструкция, выбранная для представления значений, является перечислением. Но, как мы знаем, это деталь реализации. Что произойдет, если выбрать массив, или Set <> (в Java), или строку с некоторыми токенами разделения для представления значений? Если это так, то имеет смысл проверить, что содержащиеся в нем значения представляют интерес для бизнеса. Это моя точка зрения. Это объяснение помогает?
IS1_SO
3
@ IS1_SO: Вы говорите о том, что тестирование экземпляра enum, возвращаемого функцией, имеет определенное ожидаемое значение? Потому что да, ты можешь это проверить. Вам просто не нужно тестировать объявление enum.
JacquesB
11

Если есть риск, что изменение enum нарушит ваш код, тогда, конечно, все, что с атрибутом [Flags] в C # будет хорошим случаем, потому что добавление значения между 2 и 4 (3) будет побитовым 1 и 2, а не сдержанный предмет.

Это слой защиты.

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

Я видел, как люди «исправляли» заглавные буквы в записях enum, сортировали их по алфавиту или по какой-то другой логической группировке, которая порвала другие биты плохого кода.

Ян
источник
5
Если числовые значения перечисления используются где-либо, например, когда они хранятся в базе данных, то переупорядочение (включая удаление или вставку перед последним значением) может привести к тому, что существующие записи станут недействительными.
Станниус
3
+1, этот ответ недооценен. Если ваши перечисления являются либо частью сериализации, либо интерфейсом ввода с внешним словом или побитовой компонуемой информацией, они обязательно должны быть проверены на согласованность в каждой версии системы. По крайней мере, если вы беспокоитесь о обратной совместимости, что, как правило, хорошо.
Мачадо
11

Нет, проверка, проверяющая, что перечисление содержит все допустимые значения и ничего более, по сути, не повторяет объявление перечисления. Вы бы только проверяли, что язык правильно реализует конструкцию enum, которая является бессмысленным тестом.

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

jesm00
источник
3

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

Но у вас может быть код, в котором изменение значения перечисления может привести к поломке. Например, если значение enum хранится во внешнем файле, и после изменения значения enum чтение внешнего файла даст неправильный результат. В этом случае у вас будет БОЛЬШОЙ комментарий рядом с перечислением, предупреждающий любого не изменять никакие значения, и вы вполне можете написать модульный тест, который проверяет числовые значения.

gnasher729
источник
1

В общем, просто проверка того, что перечисление имеет жестко запрограммированный список значений, не имеет большого значения, как говорили другие ответы, потому что тогда вам просто нужно обновить test и enum вместе.

Однажды у меня был случай, когда один модуль использовал перечисления типов из двух других модулей и отображал их между собой. (У одного из перечислений была дополнительная логика, у другого был доступ к БД, у обоих были зависимости, которые должны быть изолированы друг от друга.)

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

Таким образом, когда кто-то добавил запись enum к одному из перечислений и забыл добавить соответствующую запись к другому, тест начал проваливаться.

Пауло Эберманн
источник
1

Перечисления - это просто конечные типы с собственными (надеюсь значащими) именами. Перечисление может иметь только одно значение, например, 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ценность , которые мы забыли проверить.
  • Мутационное тестирование может сказать нам, действительно ли наши утверждения проверяют перечисление, а не просто следуют ветвям и игнорируют их различия.
Warbo
источник
1

Нет. Юнит-тесты предназначены для тестирования юнитов.

В объектно-ориентированном программировании единица часто представляет собой целый интерфейс, такой как класс, но может быть отдельным методом.

https://en.wikipedia.org/wiki/Unit_testing

Автоматизированный тест для объявленного перечисления будет проверять целостность языка и платформы, на которой он работает, а не логику в коде, созданном разработчиком. Это не будет служить никакой полезной цели - документация включена, поскольку код, объявляющий перечисление, служит как документация, так и код, который будет его проверять.

digimunk
источник
0

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

Вам не нужно явно утверждать, что у типа enum есть ожидаемые вами записи, точно так же, как вы явно не утверждаете, что класс действительно существует или что у него есть ожидаемые методы и атрибуты.

На самом деле, тестируя поведение, вы неявно утверждаете, что классы, методы и значения, участвующие в тесте, существуют, поэтому вам не нужно явно это утверждать.

Обратите внимание, что вам не нужны значимые имена для вашего кода, чтобы делать правильные вещи, это просто удобство для людей, читающих ваш код. Вы можете заставить свой код работать с перечисляемыми значениями вроде foo, bar... и такими методами frobnicate().

Хватит вредить Монике
источник