如今咱們把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」。轉載請註明。