[翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (二)
3.THROW IN A 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>
我们把这个放在跟application.conf一样的位置, main/resources。 请保证main/resources在你的eclipse或其他IDE的classpath中。并且把logback和slf4j-api放到你的build.sbt文件里。
当我们启动StudentSimulatorApp并发了一条消息给我们的新TeacherLogActor,我们配置的输出日志文件akkaxxxxx.log文件是这样的。
测试AKKA
我们这里并无意进行一个详尽的Akka覆盖测试。我们会在下面增加新特性的时候进行测试。这些测试用例主要是用来覆盖我们之前写的Actors代码。
当StudentSimulatorApp做了我们想要的,
想摆脱测试之痛, Akka带了一套很牛的测试工具能让我们做一些很神奇的事情,例如让你的测试代码直接进入到Actor的内部实现里。
说的差不多了,让我们看下测试用例。
让我们先将StudentSimulatorApp映射到一个测试用例(Testcase)上。
让我们看一下代码的声明:
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,2 - 发送消息给ACTORS
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
}
3-断言(ASSERTING)ACTORS的内部状态
第三个例子用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)
}
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,这个测试用例就会失败。
5 - 用构造函数参数测试ACTORS
请注意我们在用例中创建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
}
}
关闭ACTORSYSTEM
最后,afterAll生命周期方法
override def afterAll() {
super.afterAll()
system.shutdown()
}
CODE 代码
跟往常一样,整个项目可以在github这里下载。
文章来自微信平台「麦芽面包」,微信号「darkjune_think」。转载请注明。