Статьи

Akka Notes: Актеры регистрации и тестирования — 2

В первых двух частях ( одна , две ) мы кратко говорили об актерах и о том, как работает обмен сообщениями. В этой части давайте рассмотрим исправление регистрации и тестирования нашего TeacherActor.

резюмировать

Вот как выглядел наш актер из предыдущей части:

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 для достижения этой цели. Давайте смешаем это в:

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 . Да, я сказал опубликовать. Итак, что на самом деле является EventStream?

EventStream и Logging

EventStream ведет себя так же, как брокер сообщений, которому мы можем публиковать и получать сообщения. Одно тонкое отличие от обычной MOM заключается в том, что подписчиками EventStream может быть только Актер.

В случае регистрации сообщений все сообщения журнала будут опубликованы в EventStream. По умолчанию подписчик на эти сообщения — это DefaultLogger, который просто печатает сообщение в стандартный вывод.

class DefaultLogger extends Actor with StdOutLogger {  
    override def receive: Receive = {
        ...
        case event: LogEvent ⇒ print(event)
    }
}

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

Тем не менее, EventStream не подходит только для регистрации. Это универсальный механизм публикации-подписки, доступный внутри ActorWorld внутри виртуальной машины (подробнее об этом позже).


Вернуться к настройке SLF4J:

2. Настройте Akka для использования SLF4J
akka{  
    loggers = ["akka.event.slf4j.Slf4jLogger"]
    loglevel = "DEBUG"
    logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}

Мы храним эту информацию в файле с именем application.conf, который должен находиться в вашем пути к классам. В нашей структуре папок sbt мы добавили бы это в вашу директорию main / resources.

Из конфигурации мы могли бы получить

  1. свойство loggers указывает Актера, который собирается подписаться на события журнала. Что делает Slf4jLogger , так это просто использует сообщения журнала и передает их фасаду SLF4J Logger.
  2. свойство loglevel просто указывает минимальный уровень, который следует учитывать при ведении журнала.
  3. фильтр журналов сравнивает текущий настроенный уровень журнала и уровень входящего сообщения журнала и отбрасывает любое сообщение журнала ниже настроенного уровня журнала перед публикацией в EventStream.

Но почему у нас не было application.conf для предыдущего примера?

Просто потому, что Akka предоставляет некоторые нормальные значения по умолчанию, так что нам не нужно создавать файл конфигурации, прежде чем мы начнем с ним играть. Здесь мы будем слишком часто возвращаться к этому файлу для настройки различных вещей. Существует целый ряд потрясающих параметров, которые вы можете использовать внутри application.conf для самостоятельной регистрации. Они подробно объяснены здесь .

3. Добавьте logback.xml

Сейчас мы будем настраивать регистратор SLF4J с поддержкой logback .

<?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 .

И когда мы запускаем приложение StudentSimulatorApp и отправляем сообщение нашему новому TeacherLogActor, настроенный нами файл akkaxxxxx.log выглядит следующим образом.

Акка лог файл

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


Обратите внимание, что это ни в коем случае не является исчерпывающим охватом Testing Akka. Мы построим наши тесты на дополнительных возможностях тестирования в следующих частях под заголовками соответствующих тем. Эти тестовые сценарии нацелены на актеров, которых мы написали ранее.


В то время как StudentSimulatorApp делает то, что нам нужно, вы согласитесь с тем, что его следует вытеснить из тестовых случаев.

Чтобы облегчить боль при тестировании, Akka разработала удивительный инструментарий для тестирования, с помощью которого мы могли бы выполнять некоторые магические действия, например, исследовать внутреннюю часть реализации Actor.

Хватит разговоров, посмотрим на тестовые случаи.

Давайте сначала попробуем отобразить StudentSimulatorApp в Testcase.

Сопоставление приложения с Testcase

Давайте теперь посмотрим на одну декларацию.

class TeacherPreTest extends TestKit(ActorSystem("UniversityMessageSystem"))  
  with WordSpecLike
  with MustMatchers
  with BeforeAndAfterAll {

Итак, из определения класса TestCase мы видим, что:

  1. Черта TestKit принимает ActorSystem, через которую мы будем создавать Actors. Внутри TestKit украшает ActorSystem и заменяет диспетчер по умолчанию.
  2. Мы используем WordSpec, который является одним из многих забавных способов написания тестовых случаев с ScalaTest.
  3. В MustMatchers обеспечивают удобные методы , чтобы сделать TestCase выглядеть естественный язык
  4. Мы смешиваем BeforeAndAfterAll, чтобы завершить работу ActorSystem после завершения тестовых случаев. Метод afterAll, который обеспечивает эта черта, больше похож на наш tearDown в JUnit

1, 2 — отправка сообщения актерам

1) Первый тестовый пример просто отправляет сообщение в PrintActor. Ничего не утверждает 🙁

2) Во втором случае отправляется сообщение субъекту Log, который использует поле журнала ActorLogging для публикации сообщения в EventStream. Это тоже ничего не утверждает 🙁

  //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 — Утверждение внутреннего состояния актеров

В третьем случае используется метод basicActor объекта TestActorRef и вызывается метод quoteList объекта TeacherActor. Метод quoteList возвращает список цитат обратно. Мы используем этот список, чтобы утверждать его размер.

Если ссылка на quoteList отбрасывает вас назад, обратитесь к приведенному выше коду TeacherLogActor и найдите

//From TeacherLogActor
//We'll cover the purpose of this method in the Testing section
  def quoteList=quotes
    //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 прямо в нашем тестовом примере и утверждать наличие самого сообщения журнала? Похоже, мы тоже можем это сделать.

Это включает в себя два этапа:

1) Вам нужно добавить дополнительную конфигурацию в свой TestKit следующим образом:

class TeacherTest extends TestKit(ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")))  
  with WordSpecLike
  with MustMatchers
  with BeforeAndAfterAll {

2) Теперь, когда у нас есть подписка на EventStream, мы можем утверждать ее из нашего тестового примера как:

 //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], а не system.actorOf. Это просто для того, чтобы мы могли получить доступ к внутренним объектам актера через метод basicActor в TeacherActorRef. Мы не сможем достичь этого через ActorRef, к которому у нас есть доступ во время обычной среды выполнения. (Это не дает нам никаких оправданий для использования TestActorRef в производстве. Вы будете охотиться).

Если Actor принимает параметры, то способ создания TestActorRef будет таким:

val teacherRef = TestActorRef(new TeacherLogParameterActor(quotes))

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

//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

override def afterAll() {  
    super.afterAll()
    system.shutdown()
  }

КОД

Как всегда, весь проект можно скачать с github здесь .