Это шестая статья в серии об интеграции клиентов синхронизации с асинхронными системами ( 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 . |