Масштабные асинхронные тестовые наборы против конечных и когда-либо (org.scalatest.concurrent) - PullRequest
1 голос
/ 03 апреля 2019

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

Интересно, сможет ли кто-нибудь, кто разбирается в асинхронном тестировании с использованием самого масштабного, быстро объяснить разницу между наборами асинхронных тестов и org.scalatest.concurrent?Это набор тестов на самом деле добавляет к org.scalatest.concurrent?Иначе лучше использовать один аппрокох над другим

1 Ответ

2 голосов
/ 05 апреля 2019

Мы сравниваем следующие средства 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.В заключение я предлагаю использовать черты асинхронного стиля, если у нас нет веских причин не делать этого.

...