Как написать scala unit test, который не позволяет выполнить сбой?
Есть ли способ написать что-то вроде "unit test", которое гарантирует, что какой-то код не компилируется?
Зачем мне такое? Две причины.
1) Проверьте безопасность моего API. Я бы хотел, чтобы убедиться, что кто-то переходит в плохую ценность, вы получаете ошибку компилятора, а не только ошибку времени выполнения. Очевидно, что я могу просто запустить компилятор и проверить наличие ошибки, но формализация его в unit test хороша для предотвращения регрессии, а также для документации.
Например, рассмотрите этот тест. Существует какой-то прокомментированный код, который я использовал для проверки безопасности типа:
https://github.com/squito/boxwood/blob/master/core/src/test/scala/com/quantifind/boxwood/EnumUnionTest.scala#L42
(строки 42 и 48 - строка 34 Я вызываю другой API, который имеет исключение во время выполнения, которое я могу проверить)
На самом деле мне потребовалось некоторое время, чтобы получить право на безопасность типа, поэтому это были важные проверки. Теперь, если я перейду и изменю базовую реализацию, я не могу просто запустить свой тестовый пакет - мне также нужно помнить о том, чтобы раскомментировать эти строки и проверить ошибку компилятора.
2) Проверка обработки ошибок макросов. Если у макроса есть некорректный ввод, это должно привести к ошибке компилятора. Те же проблемы здесь, одно и то же желание иметь его в легкодоступном тестовом наборе.
Я использую ScalaTest, но я рад, что здесь есть решение с любой инфраструктурой модульного тестирования.
Ответы
Ответ 1
Как я уже отмечал выше, Shapeless 2.0 (еще не выпущенный, но в настоящее время доступный как веха) имеет очень хорошую реализацию функциональность, которую вы ищете, на основе решения Stefan Zeiger. Я добавил демо к вашему проекту здесь (обратите внимание, что мне пришлось обновить до Scala 2.10, так как это решение использует макро). Он работает следующим образом:
import shapeless.test.illTyped
//this version won't even compile
illTyped("getIdx(C.Ooga)")
//We can have multiple enum unions exist side by side
import Union_B_C._
B.values().foreach {b => Union_B_C.getIdx(b) should be (b.ordinal())}
C.values().foreach {c => Union_B_C.getIdx(c) should be (c.ordinal() + 2)}
//Though A exists in some union type, Union_B_C still doesn't know about it,
// so this won't compile
illTyped("""
A.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())}
""")
Если бы мы изменили код во втором вызове на illTyped
на то, что будет скомпилировано:
B.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())}
Мы получим следующую ошибку компиляции:
[error] .../EnumUnionTest.scala:56: Type-checking succeeded unexpectedly.
[error] Expected some error.
[error] illTyped("""
[error] ^
[error] one error found
[error] (core/test:compile) Compilation failed
Если вы предпочитаете неудачный тест, вы можете легко адаптировать реализацию в Shapeless. См. Мили ответьте на мой предыдущий вопрос для обсуждения с добавлением.
Ответ 2
Scalatest также может это сделать.
Проверка того, что фрагмент кода не компилируется
Часто при создании библиотек вы можете устройства кода, которые представляют потенциальные "пользовательские ошибки", не скомпилируйте, чтобы ваша библиотека была более устойчивой к ошибкам. ScalaTest Для этой цели используется следующий синтаксис:
"val a: String = 1" shouldNot compile
Если вы хотите, чтобы фрагмент кода не компилируется из-за ошибки типа (в отличие от к синтаксической ошибке), используйте:
"val a: String = 1" shouldNot typeCheck
Обратите внимание, что синтаксис shouldNot typeCheck
будет успешным только в том случае, если данный фрагмент кода не компилируются из-за ошибки типа. Синтаксическая ошибка будет по-прежнему на брошенном TestFailedException
.
Если вы хотите указать, что фрагмент кода компилируется, вы можете сделать что более очевидно:
"val a: Int = 1" should compile
Хотя предыдущие три конструкции реализованы с помощью макросов, которые во время компиляции определяют, фрагмент кода, представленный этой строкой, выполняется или не компилируется, ошибки сообщаются как сбои тестирования во время выполнения.
Ответ 3
Ответ Тревиса Брауна абсолютно правильный. Просто в интересах полноты, я хочу добавить, что это также работает для тестирования макросов, как показано здесь.
Одна незначительная точка: проверка illTyped
, похоже, не работает в repl. Он никогда не выдает ошибку, даже если данное выражение проверяет тип. Но не позволяйте этому обмануть вас, это хорошо работает.
> test:console
Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_65).
Type in expressions to have them evaluated.
Type :help for more information.
scala> def foo(s: String) = s
foo: (s: String)String
scala> import shapeless.test.illTyped
import shapeless.test.illTyped
scala> foo(1)
<console>:10: error: type mismatch;
found : Int(1)
required: String
foo(1)
^
scala> illTyped("""foo(1)""")
scala> illTyped("""foo("hi there")""") // <--- works, but shouldn't!