В первых двух частях ( одна , две ) мы кратко говорили об актерах и о том, как работает обмен сообщениями. В этой части давайте рассмотрим исправление регистрации и тестирования нашего 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например, так:1234classTeacherTestextendsTestKit(ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")))with WordSpecLikewith MustMatcherswith 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 . |

