如今咱们把SLF4J日志配置在logback。html
<?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>
咱们把这个放在跟application.conf同样的位置, main/resources。 请保证main/resources在你的eclipse或其余IDE的classpath中。而且把logback和slf4j-api放到你的build.sbt文件里。git
当咱们启动StudentSimulatorApp并发了一条消息给咱们的新TeacherLogActor,咱们配置的输出日志文件akkaxxxxx.log文件是这样的。github
咱们这里并没有意进行一个详尽的Akka覆盖测试。咱们会在下面增长新特性的时候进行测试。这些测试用例主要是用来覆盖咱们以前写的Actors代码。api
当StudentSimulatorApp作了咱们想要的,微信
想摆脱测试之痛, Akka带了一套很牛的测试工具能让咱们作一些很神奇的事情,例如让你的测试代码直接进入到Actor的内部实现里。并发
说的差很少了,让咱们看下测试用例。app
让咱们先将StudentSimulatorApp映射到一个测试用例(Testcase)上。eclipse
让咱们看一下代码的声明:ide
class TeacherPreTest extends TestKit(ActorSystem("UniversityMessageSystem")) with WordSpecLike with MustMatchers with BeforeAndAfterAll {
因此,从TestCase的用例定义咱们能够看到:函数
1.TestKit从ActorSystem接受一个咱们要建立的Actors.在内部,TestKit装饰了ActorSystem而且替换了缺省的分发者(dispatcher)。
2.咱们在写ScalaTest的测试用例时会使用WordSpec,它能够用许多有趣的方式驱邪。
3.MustMatchers提供便利的方法让测试写起来像天然语言。
4.咱们把BeforeAndAfterAll加进来是由于它能够在测试用例结束后关掉ActorSystem。afterAll方法提供的特性很像JUnit中的tearDown方法。
1)在第一个测试用例时咱们发送了一个消息给PrintActor。但并无断言什么东西 :-(
2)在第二个例子中咱们发了一个消息给日志actor,它用一个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 }
第三个例子用TestActorRef的underlyingActor方法去调用TeacherActor的quoteList。quoteList方法返回格言(quoteList)的列表。咱们用这个列表来断言它的size。
若是说到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) }
咱们以前讨论过EventStream和Logging,全部的log消息都会发送到EventStream而后SLF4JLogger订阅了这些消息并将其写到日志文件或控制台等。若是让咱们的测试用例订阅EventStream并直接断言log消息不是更妙?看起来值得一试。
这须要两步:
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这块代码拦截了一条以QuoteResponse(pattern='QuoteResponse* )开头的消息。(若是用start=‘QuoteResponse'也同样能拦截到)。日过没有一条日志消息发送给TeacherLogActor,这个测试用例就会失败。
请注意咱们在用例中建立Actors时是用TestActorRef[TeacherLogActor]而不是用system.actorOf。这是由于咱们能够经过TeacherActorRef的underlyingActor方法来进入Actor的内部。咱们用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 } }
最后,afterAll生命周期方法
override def afterAll() { super.afterAll() system.shutdown() }
跟往常同样,整个项目能够在github这里下载。
文章来自微信平台「麦芽面包」,微信号「darkjune_think」。转载请注明。