Статьи

Блок против Интеграции Акка Тестирование

Это шестая статья в серии об интеграции клиентов синхронизации с асинхронными системами ( 1, 2, 3, 4, 5 ). Здесь мы увидим, как тестировать актеров Akka с разными стилями тестирования.

Блок против Интеграционного Тестирования

В настоящее время все согласны с известной испытательной пирамидой :

пирамида

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

  • Единица: ключ здесь — изоляция. Некоторые люди говорят об изолированном производственном коде, например, об одной функции или группе методов в классе (один общедоступный, а остальные частные). Некоторые другие люди говорят об изолированных или независимых тестах, то есть тестах, которые могут выполняться параллельно, поскольку они не имеют доступа к общему ресурсу. Третье мнение состоит в том, что выполнение модульного теста должно быть синхронным без какой-либо проблемы параллелизма. Это мнение Акки :

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

  • Интеграция: этот тип тестов часто включает несколько классов, модулей или сервисов. С Akka мы протестируем несколько актеров, но ключевая концепция заключается в том, что мы будем использовать многопоточное планирование:

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

Акка Юнит Тестирование

Когда мы тестируем объекты, мы ищем:

  • Проверьте возвращаемое значение.
  • Проверять звонки соавторам.
  • Осмотрите внутреннее состояние. Это может быть запахом в некоторых случаях.

Концепция возвращаемого значения немного отличается в Akka. Акка фокусируется на сообщениях, а не на вызовах методов. Проверка возвращаемого значения включает в себя двух участников и два сообщения. Если соавторы тестируемого актера также являются актерами, проверка вызовов будет включать двух актеров и два сообщения. Если мы используем многопоточный диспетчер для планирования этого, этот сценарий будет вне нашего определения модульного тестирования. Давайте сосредоточимся на тестировании внутреннего состояния.

Актеры Akka полностью инкапсулированы, и единственным каналом связи является почтовый ящик. TestActorRef предоставляется Akka, поэтому мы можем получить доступ к внутренним TestActorRef актера и протестировать его. Одна из его специализированных форм — TestFSMRef позволяющая нам тестировать конечные автоматы . Давайте посмотрим пример с нашей платформы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
"Item FSM" should {
  "move into active state when it receives an item" in {
    val fsm = TestFSMRef(new ItemFSM(itemReportedProducer, itemDeletedBus))
    fsm.stateName shouldBe Idle
    fsm.stateData shouldBe Uninitialized
 
    fsm ! ItemReported(itemId)
 
    within(200 millis){
      fsm.stateName shouldBe Active
      fsm.stateData.asInstanceOf[ItemsToBeDeleted].items shouldBe items
    }
  }
}

Как вы видите, TestFSMRef оборачивает актера, которого мы хотим протестировать, и раскрывает его внутреннее состояние. У этой оболочки есть и другие полезные методы, такие как программная установка состояния или манипулирование таймерами FSM.

Я хочу поделиться тем, что немного смутило меня в первый раз, но это важно понять. Мы должны напомнить, что модульное тестирование в Akka означает использование одного потока для достижения детерминированного порядка событий. TestActorRef по умолчанию использует CallingThreadDispatcher . Этот диспетчер запускает вызовы только в текущем потоке, чтобы мы могли выполнить модульный тест, который проверяет возвращаемое значение субъекта с этим стилем.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class EchoActor extends Actor {
 override def receive = {
   case message ⇒ sender() ! message
 }
}
 
"send back messages unchanged" in {
  import akka.pattern.ask
  import scala.concurrent.duration._
  implicit val timeout = Timeout(5 seconds)
 
  val actorRef = TestActorRef(new EchoActor)
 
  val future = actorRef ? "hello world"
  val Success(result: String) = future.value.get
  result should be("hello world")
}

Давайте посмотрим, как мы можем протестировать этот и другие сценарии с помощью стиля тестирования интеграции в Akka.

Акка Интеграционное тестирование

Akka предоставляет класс TestKit для интеграционного тестирования. Давайте посмотрим на один из наших тестов, написанных с использованием этого класса:

01
02
03
04
05
06
07
08
09
10
11
12
class ItemFSMSpec() extends TestKit(ActorSystem("ItemFSMSpec")) with ImplicitSender
 
"send a complete message to the original sender when one deleted item is received and there are no more messages pending" in {
    val worker = TestFSMRef(new ItemFSM(itemReportedProducer, itemDeletedBus))
    worker ! ItemReported(itemId)
 
    itemDeletedBus.publish(MsgEnvelope(item.partitionKey, ItemDeleted(item)))
 
    within(200 millis) {
      expectMsg(Result(Right()))
    }
 }

В этом конкретном тесте нас интересует сообщение о том, что FSM отправляет отправителю. Давайте еще раз посмотрим на эту строку:

1
worker ! ItemReported(itemId)

Здесь мы говорим: отправьте сообщение типа ItemReported актеру, назначенному в val worker . Но кто отправляет это сообщение? Расширение и смешивание TestKit и ImplicitSender создает testActor который будет отправителем сообщений. TestKit предоставляет некоторые методы, такие как expectMsg для проверки почтового ящика этого testActor . within конечном eventually действует как Scalatest, но с большей силой. Например, как сказано в документации:

Следует отметить, что если последним подтверждением получения сообщения в блоке является waitNoMsg или receiveWhile, окончательная проверка внутренней части пропускается, чтобы избежать ложных срабатываний из-за задержек при пробуждении. Это означает, что, хотя отдельные содержащиеся в утверждениях по-прежнему используют максимальную временную привязку, общий блок в этом случае может занять произвольно больше времени.

Еще один интересный класс — TestProbe . Если в нашем интеграционном тесте есть несколько участников, и мы хотим проверить разные сообщения, отправленные между различными участниками, использование одного testActor может привести к путанице. Даже с одним актером, использование TestProbe улучшает читабельность в некоторых случаях:

1
2
3
4
5
6
7
8
9
"flush a FSM when it receives a Failed message" in {
  val fsmProbe = TestProbe()
  val actorFactory: (ActorContext, ActorRef) => ActorRef = (context, self) => fsmProbe.ref
  val coordinator = TestActorRef(ItemReportedCoordinator.props(actorFactory))
 
  fsmProbe.send(coordinator, Result(Left(Exception("Some exception message"))))
 
  fsmProbe.expectMsg(FlushItemFSM)
}

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

Резюме

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

Часть 1 | Часть 2 | Часть 3 | Часть 4 | Часть 5

Ссылка: Модуль против интеграции Akka Testing от нашего партнера JCG Фелипе Фернандеса в блоге Crafted Software .