Мы сравниваем следующие средства ScalaTest для тестирования кода, который возвращает Future
s:
Черты асинхронного стиля
class AsyncSpec extends AsyncFlatSpec {
...
Future(3).map { v => assert(v == 3) }
...
}
- неблокирующую
- мы можем утверждать до завершения
Future
, т. е. возвращать Future[Assertion]
вместо Assertion
- поточно-ориентированный
- однопоточный контекст последовательного выполнения
Futures
выполняется и завершается в порядке их запуска, и один за другим - тот же поток, который используется для постановки задач в теле теста, также используется для их последующего выполнения
- Утверждения могут быть сопоставлены с
Futures
- нет необходимости блокировать внутри тестового тела, т. Е. Использование
Await
, whenReady
- устраняет слабость из-за истощения потока
- последнее выражение в теле теста должно быть
Future[Assertion]
- не поддерживает множественные утверждения iВ тестовом теле
- нельзя использовать блокирующие конструкции внутри тестового тела, так как тест будет зависать навсегда из-за ожидания задачи, поставленной в очередь, но никогда не запускаемой
ScalaFutures
class ScalaFuturesSpec extends FlatSpec with ScalaFutures {
...
whenReady(Future(3) { v => assert(v == 3) }
...
}
- блокировка
- нам нужно дождаться завершения
Future
, прежде чем мы сможем вернуть Assertion
- не поточно-ориентированный
- Вероятно, будет использоваться сглобальный контекст выполнения
scala.concurrent.ExecutionContext.Implicits.global
, который является многопоточным пулом для параллельного выполнения - поддерживает несколько утверждений в одном и том же теле теста
- последнее выражение в теле теста не обязательно должно быть
Assertion
В конечном итоге
class EventuallySpec extends FlatSpec with Eventually {
...
eventually { assert(Future(3).value.contains(Success(3))) }
...
}
- более общим средством, предназначенным не только для семантики
Futures
- , здесь является попытка повторения блока кода любоготип, передаваемый по имени до тех пор, пока утверждение не будет удовлетворено
- при тестировании
Futures
, вероятно, будет использоваться глобальный контекст выполнения - , предназначенный главным образом для интеграционного тестирования, когда тестирование выполняется на основе реального обслуживанияces с непредсказуемым временем отклика
Однопоточная модель последовательного выполнения против глобальной модели выполнения с пулом
scalatest-async-testing-сравнение является примеромдемонстрируя разницу в двух исполнениях модели.
Учитывая следующее тело теста
val f1 = Future {
val tmp = mutableSharedState
Thread.sleep(5000)
println(s"Start Future1 with mutableSharedState=$tmp in thread=${Thread.currentThread}")
mutableSharedState = tmp + 1
println(s"Complete Future1 with mutableSharedState=$mutableSharedState")
}
val f2 = Future {
val tmp = mutableSharedState
println(s"Start Future2 with mutableSharedState=$tmp in thread=${Thread.currentThread}")
mutableSharedState = tmp + 1
println(s"Complete Future2 with mutableSharedState=$mutableSharedState")
}
for {
_ <- f1
_ <- f2
} yield {
assert(mutableSharedState == 2)
}
, давайте рассмотрим вывод AsyncSpec
против ScalaFuturesSpec
testOnly example.AsyncSpec:
Start Future1 with mutableSharedState=0 in thread=Thread[pool-11-thread-3-ScalaTest-running-AsyncSpec,5,main]
Complete Future1 with mutableSharedState=1
Start Future2 with mutableSharedState=1 in thread=Thread[pool-11-thread-3-ScalaTest-running-AsyncSpec,5,main]
Complete Future2 with mutableSharedState=2
пример testOnly.ScalaFuturesSpec:
Start Future2 with mutableSharedState=0 in thread=Thread[scala-execution-context-global-119,5,main]
Complete Future2 with mutableSharedState=1
Start Future1 with mutableSharedState=0 in thread=Thread[scala-execution-context-global-120,5,main]
Complete Future1 with mutableSharedState=1
Обратите внимание, как в модели с последовательным исполнением используется тот же поток, и фьючерсы завершаются по порядку,С другой стороны, в модели глобального выполнения использовались разные потоки, и Future2
завершалось до Future1
, что вызывало состояние гонки в общем изменяемом состоянии, что, в свою очередь, приводило к неудаче теста.
Какой из нихмы должны использовать (IMO)?
В модульных тестах мы должны использовать ложные подсистемы, где возвращаемое значение Futures
должно завершаться почти мгновенно, поэтому нет необходимости в Eventually
в модульных тестах.Следовательно, выбор между асинхронными стилями и ScalaFutures
.Основное различие между ними состоит в том, что первое не является блокирующим в отличие от второго.Если возможно, мы никогда не должны блокировать, поэтому мы должны предпочитать асинхронные стили, такие как AsyncFlatSpec
.Еще одна большая разница - модель исполнения.По умолчанию в асинхронных стилях используется настраиваемая модель последовательного выполнения, которая обеспечивает безопасность потоков в совместно изменяемом состоянии, в отличие от глобальной модели выполнения с поддержкой пула потоков, часто используемой с ScalaFutures
.В заключение я предлагаю использовать черты асинхронного стиля, если у нас нет веских причин не делать этого.