В первых двух частях ( одна , две ) мы кратко говорили об актерах и о том, как работает обмен сообщениями. В этой части давайте рассмотрим исправление регистрации и тестирования нашего TeacherActor
.
резюмировать
Вот как выглядел наш актер из предыдущей части:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class TeacherActor extends Actor { val quotes = List( "Moderation is for cowards" , "Anything worth doing is worth overdoing" , "The trouble is you think you have time" , "You never gonna know if you never even try" ) def receive = { case QuoteRequest => { import util.Random //Get a random Quote from the list and construct a response val quoteResponse=QuoteResponse(quotes(Random.nextInt(quotes.size))) println (quoteResponse) } } } |
Регистрация Akka с SLF4J
Вы замечаете, что в коде мы quoteResponse
к стандартному выводу, который вы, очевидно, согласитесь, — плохая идея. Давайте исправим это, включив регистрацию через фасад SLF4J.
1. Исправьте класс, чтобы использовать ведение журнала
Akka предлагает ActorLogging
черту ActorLogging
для достижения этой цели. Давайте смешаем это в:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class TeacherLogActor extends Actor with ActorLogging { val quotes = List( "Moderation is for cowards" , "Anything worth doing is worth overdoing" , "The trouble is you think you have time" , "You never gonna know if you never even try" ) def receive = { case QuoteRequest => { import util.Random //get a random element (for now) val quoteResponse=QuoteResponse(quotes(Random.nextInt(quotes.size))) log.info(quoteResponse.toString()) } } //We'll cover the purpose of this method in the Testing section def quoteList=quotes } |
Небольшой объезд здесь:
Внутренне, когда мы регистрируем сообщение, методы регистрации в ActorLogging (в конечном итоге) публикуют сообщение журнала в EventStream . Да, я сказал publish
. Итак, что на самом деле является EventStream?
EventStream и Logging
EventStream
ведет себя так же, как брокер сообщений, которому мы можем публиковать и получать сообщения. Одно тонкое отличие от обычной MOM заключается в том, что подписчиками EventStream может быть только Актер.
В случае регистрации сообщений все сообщения журнала будут опубликованы в EventStream. По умолчанию подписчик на эти сообщения — это DefaultLogger, который просто печатает сообщение в стандартный вывод.
1
2
3
4
5
6
|
class DefaultLogger extends Actor with StdOutLogger { override def receive: Receive = { ... case event: LogEvent ⇒ print(event) } } |
Вот почему, когда мы пытаемся запустить StudentSimulatorApp , мы видим сообщение журнала, записанное на консоль.
Тем не менее, EventStream не подходит только для регистрации. Это универсальный механизм публикации-подписки, доступный внутри ActorWorld внутри виртуальной машины (подробнее об этом позже).
Вернуться к настройке SLF4J:
2. Настройте Akka для использования SLF4J
1
2
3
4
5
|
akka{ loggers = [ "akka.event.slf4j.Slf4jLogger" ] loglevel = "DEBUG" logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" } |
Мы храним эту информацию в файле с именем application.conf
который должен находиться в вашем пути к классам. В нашей структуре папок sbt мы добавили бы это в вашу директорию main/resources
.
Из конфигурации мы можем вывести, что:
- свойство
loggers
указывает Актера, который собирается подписаться на события журнала. Что делает Slf4jLogger , так это просто использует сообщения журнала и передает их фасаду SLF4J Logger. - свойство
loglevel
просто указывает минимальный уровень, который следует учитывать при ведении журнала. -
logging-filter
сравниваетloglevel
настроенный уровень журнала и уровень входящего сообщения журнала и отбрасывает любое сообщение журнала ниже настроенного уровня журнала перед публикацией в EventStream.
Но почему у нас не было application.conf для предыдущего примера?
Просто потому, что Akka предоставляет некоторые нормальные значения по умолчанию, так что нам не нужно создавать файл конфигурации, прежде чем мы начнем с ним играть. Здесь мы будем слишком часто возвращаться к этому файлу для настройки различных вещей. Существует целый ряд потрясающих параметров, которые вы можете использовать внутри application.conf
для самостоятельной регистрации. Они подробно объяснены здесь .
3. Добавьте logback.xml
Сейчас мы будем настраивать регистратор SLF4J с поддержкой logback .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
<? xml version = "1.0" encoding = "UTF-8" ?> < configuration > < appender name = "FILE" class = "ch.qos.logback.core.rolling.RollingFileAppender" > < encoder > < pattern >%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</ pattern > </ encoder > < rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > < fileNamePattern >logs\akka.%d{yyyy-MM-dd}.%i.log</ fileNamePattern > < timeBasedFileNamingAndTriggeringPolicy class = "ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > < maxFileSize >50MB</ maxFileSize > </ timeBasedFileNamingAndTriggeringPolicy > </ rollingPolicy > </ appender > < root level = "DEBUG" > < appender-ref ref = "FILE" /> </ root > </ configuration > |
Я бросил это в папку main/resources
вместе с application.conf
. Убедитесь, что main/resources
теперь находится в вашем затмении или в другом пути к классам IDE. Также добавьте logback и slf4j-api в ваш build.sbt .
И когда мы TeacherLogActor
akkaxxxxx.log
и отправляем сообщение нашему новому TeacherLogActor
, akkaxxxxx.log
файл akkaxxxxx.log
выглядит следующим образом.
Тестирование Акки
Обратите внимание, что это ни в коем случае не является исчерпывающим охватом Testing Akka. Мы построим наши тесты на дополнительных возможностях тестирования в следующих частях под заголовками соответствующих тем. Эти тестовые сценарии нацелены на актеров, которых мы написали ранее.
В то время как StudentSimulatorApp
делает то, что нам нужно, вы согласитесь с тем, что его следует вытеснить из тестовых случаев.
Чтобы облегчить задачу тестирования, Akka разработала удивительный инструментарий тестирования, с помощью которого мы могли бы выполнять некоторые магические действия, например, исследовать внутреннюю часть реализации Actor.
Хватит разговоров, посмотрим на тестовые случаи.
Давайте сначала попробуем отобразить StudentSimulatorApp в Testcase.
Давайте теперь посмотрим на одну декларацию.
1
2
3
4
|
class TeacherPreTest extends TestKit(ActorSystem( "UniversityMessageSystem" )) with WordSpecLike with MustMatchers with BeforeAndAfterAll { |
Итак, из определения класса TestCase мы видим, что:
- Черта
TestKit
принимаетActorSystem
через которую мы будем создавать Actors. Внутри TestKit украшает ActorSystem и заменяет диспетчер по умолчанию. - Мы используем WordSpec, который является одним из многих забавных способов написания тестовых случаев с ScalaTest.
- MustMatchers предоставляют удобные методы, чтобы сделать тестовый пример похожим на естественный язык
- Мы
BeforeAndAfterAll
чтобыBeforeAndAfterAll
работу ActorSystem после завершения тестовых случаев. МетодafterAll
который обеспечивает эта черта, больше похож на нашtearDown
в JUnit
1, 2 — отправка сообщения актерам
- Первый тестовый пример просто отправляет сообщение в PrintActor. Это ничего не утверждает!
- Во втором случае сообщение отправляется субъекту Log, который использует поле журнала ActorLogging для публикации сообщения в EventStream. Это тоже ничего не утверждает!
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
//1. Sends message to the Print Actor. Not even a testcase actually "A teacher" must { "print a quote when a QuoteRequest message is sent" in { val teacherRef = TestActorRef[TeacherActor] teacherRef ! QuoteRequest } } //2. Sends message to the Log Actor. Again, not a testcase per se "A teacher with ActorLogging" must { "log a quote when a QuoteRequest message is sent" in { val teacherRef = TestActorRef[TeacherLogActor] teacherRef ! QuoteRequest } |
3 — Утверждение внутреннего состояния актеров
В третьем случае используется метод TestActorRef
и вызывается метод quoteList
. Метод quoteList
возвращает список цитат обратно. Мы используем этот список, чтобы утверждать его размер.
Если ссылка на quoteList
отбрасывает вас назад, обратитесь к указанному выше коду TeacherLogActor и найдите:
1
2
3
|
//From TeacherLogActor //We'll cover the purpose of this method in the Testing section def quoteList=quotes |
1
2
3
4
5
6
7
|
//3. Asserts the internal State of the Log Actor. "have a quote list of size 4" in { val teacherRef = TestActorRef[TeacherLogActor] teacherRef.underlyingActor.quoteList must have size ( 4 ) teacherRef.underlyingActor.quoteList must have size ( 4 ) } |
4 — Утверждение сообщений журнала
Как мы уже обсуждали ранее в разделе EventStream and Logging (выше), все сообщения журнала отправляются на EventStream
и SLF4JLogger
подписывается на него и использует свои приложения для записи в файл / консоль журнала и т. Д. Не было бы неплохо подписаться на EventStream прямо в нашем тестовом примере и утверждать наличие самого сообщения журнала? Похоже, мы тоже можем это сделать.
Это включает в себя два этапа:
- Вам нужно добавить дополнительную конфигурацию в ваш
TestKit
например, так:1234class
TeacherTest
extends
TestKit(ActorSystem(
"UniversityMessageSystem"
, ConfigFactory.parseString(
""
"akka.loggers = ["
akka.testkit.TestEventListener
"]"
""
)))
with WordSpecLike
with MustMatchers
with BeforeAndAfterAll {
- Теперь, когда у нас есть подписка на EventStream, мы можем утверждать ее из нашего тестового примера как:
12345678
//4. Verifying log messages from eventStream
"be verifiable via EventFilter in response to a QuoteRequest that is sent"
in {
val teacherRef = TestActorRef[TeacherLogActor]
EventFilter.info(pattern =
"QuoteResponse*"
, occurrences =
1
) intercept {
teacherRef ! QuoteRequest
}
}
Блок EventFilter.info
просто перехватывает 1 лог-сообщение, которое начинается с QuoteResponse ( pattern='QuoteResponse*
). (Вы также можете достичь этого, используя start='QuoteResponse'
. Если в результате отправки сообщения в TeacherLogActor нет сообщения журнала, тестовый случай завершится неудачно.
5 — Актеры тестирования с параметрами конструктора
Обратите внимание, что способ создания Actors в TestActorRef[TeacherLogActor]
— это TestActorRef[TeacherLogActor]
а не system.actorOf
. Это просто для того, чтобы мы могли получить доступ к внутренним объектам актера через метод basicActor в TeacherActorRef. Мы не сможем достичь этого через ActorRef
которому у нас есть доступ во время обычной среды выполнения. (Это не дает нам никаких оправданий для использования TestActorRef в производстве. Вы будете охотиться).
Если Actor принимает параметры, то способ создания TestActorRef будет таким:
1
|
val teacherRef = TestActorRef( new TeacherLogParameterActor(quotes)) |
Весь тестовый пример будет выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
//5. have a quote list of the same size as the input parameter " have a quote list of the same size as the input parameter" in { val quotes = List( "Moderation is for cowards" , "Anything worth doing is worth overdoing" , "The trouble is you think you have time" , "You never gonna know if you never even try" ) val teacherRef = TestActorRef( new TeacherLogParameterActor(quotes)) //val teacherRef = TestActorRef(Props(new TeacherLogParameterActor(quotes))) teacherRef.underlyingActor.quoteList must have size ( 4 ) EventFilter.info(pattern = "QuoteResponse*" , occurrences = 1 ) intercept { teacherRef ! QuoteRequest } } |
Завершение работы ActorSystem
И, наконец, afterAll
жизненного цикла afterAll
:
1
2
3
4
|
override def afterAll() { super .afterAll() system.shutdown() } |
КОД
- Как всегда, весь проект можно скачать с github здесь .
Ссылка: | Заметки Акки — регистрация актеров и тестирование от нашего партнера JCG Аруна Маниваннана в блоге Rerun.me . |