• Testing
• Testing Akka applications
• Better testing Akka applications
• Testing Akka applications with less problems
Agenda
A provocative talk and blog posts has led to a conversation where we aim to understand each others' views and experiences.
http://martinfowler.com/articles/is-tdd-dead/
Is TDD dead?M. Fowler
K. Beck
David Heinemeier Hansson
Programmers at work maintaining a Ruby on Rails testless application
Eero Järnefelt, Oil on canvas, 1893
• Retry the assertion many times;
• The worker tells when the work is done.
2 conceptual solutions questions
How many times?
How long to wait?
object IncrementorActorMessages { case class Inc(i: Int)} class IncrementorActor extends Actor { var sum: Int = 0 override def receive: Receive = { case Inc(i) => sum = sum + i }}
// TODO: test this
class IncrementorActorTest extends TestKit(ActorSystem("test-system")) { // it(“should …”) { … }}
// We need an actor system
it("should have sum = 0 by default") { val actorRef = TestActorRef[IncrementorActor] actorRef.underlyingActor.sum shouldEqual 0 }
// Uses the same thread
Has a real type!
it("should increment on new messages1") { val actorRef = TestActorRef[IncrementorActor] actorRef ! Inc(2) actorRef.underlyingActor.sum shouldEqual 2 actorRef.underlyingActor.receive(Inc(3)) actorRef.underlyingActor.sum shouldEqual 5 } it("should increment on new messages2") { val actorRef = TestActorRef[IncrementorActor] actorRef ! Inc(2) actorRef.underlyingActor.sum shouldEqual 2 actorRef.underlyingActor.receive(Inc(3)) actorRef.underlyingActor.sum shouldEqual 5 }
// “Sending” messages
Style 1
Style 2
class LazyIncrementorActor extends Actor { var sum: Int = 0 override def receive: Receive = { case Inc(i) => Future { Thread.sleep(100) sum = sum + i } }}
object IncrementorActorMessages {
case class Inc(i: Int)
case object Result
}
class IncrementorActor extends Actor {
var sum: Int = 0
override def receive: Receive = { case Inc(i) => sum = sum + i case Result => sender() ! sum }
}
New message
// ... with ImplicitSender
it("should have sum = 0 by default") {
val actorRef = system .actorOf(Props(classOf[IncrementorActor]))
actorRef ! Result
expectMsg(0) }
TestKit trait
it("should increment on new messages") { val actorRef = system .actorOf(Props(classOf[IncrementorActor]))
actorRef ! Inc(2) actorRef ! Result expectMsg(2)
actorRef ! Inc(3) actorRef ! Result expectMsg(5) }
def expectMsg[T](d: Duration, msg: T): T
def expectMsgPF[T](d: Duration) (pf:PartialFunction[Any, T]): T
def expectMsgClass[T](d: Duration, c: Class[T]): T
def expectNoMsg(d: Duration) // blocks
// expectMsg*
def receiveN(n: Int, d: Duration): Seq[AnyRef]
def receiveWhile[T](max: Duration, idle: Duration, n: Int) (pf: PartialFunction[Any, T]): Seq[T]
def fishForMessage(max: Duration, hint: String) (pf: PartialFunction[Any, Boolean]): Any
// fishing*
def awaitCond(p: => Boolean, max: Duration, interval: Duration)
def awaitAssert(a: => Any, max: Duration, interval: Duration)
// from ScalaTest def eventually[T](fun: => T) (implicit config: PatienceConfig): T
// await*
val probe = TestProbe()
probe watch target
target ! PoisonPill
probe.expectTerminated(target)
// death watch
Somewhere in the app code
• Use props;
• Use childMaker: ActorRefFactory => ActorRef;
• Use a fabricated parent.
Dependency Injection
class MyActor extends Actor with ActorLogging {
override def receive: Receive = { case DoSideEffect =>
log.info("Hello World!") }
}
// event filter
// akka.loggers = ["akka.testkit.TestEventListener"]
EventFilter.info( message = "Hello World!”, occurrences = 1 ).intercept { myActor ! DoSomething }
// event filter
class MyActor extends Actor with ActorLogging {
override def supervisorStrategy: Unit = OneForOneStrategy() { case _: FatalException => SupervisorStrategy.Escalate case _: ShitHappensException => SupervisorStrategy.Restart }
}
// supervision
// unit-test style
val actorRef = TestActorRef[MyActor](MyActor.props())
val pf = actorRef.underlyingActor .supervisorStrategy.decider
pf(new FatalException()) should be (Escalate) pf(new ShitHappensException()) should be (Restart)
// supervision
class MyActorTest extends TestKit(ActorSystem("test-system")) with FunSpecLike {
override protected def afterAll(): Unit = { super.afterAll() system.shutdown() system.awaitTermination() }
}
// shutting down the actor system
trait AkkaTestBase extends BeforeAndAfterAll with FunSpecLike { this: TestKit with Suite =>
override protected def afterAll() { super.afterAll() system.shutdown() system.awaitTermination() }
}
// shutting down the actor system
akka.test.single-expect-default = 3 seconds akka.test.timefactor = 10
// timeouts
import scala.concurrent.duration._ import akka.testkit._ 10.milliseconds.dilated
class Settings(...) extends Extension {
object Jdbc { val Driver = config.getString("app.jdbc.driver") val Url = config.getString("app.jdbc.url") }
}
// settings extension
// test val config = ConfigFactory.parseString(""" app.jdbc.driver = "org.h2.Driver" app.jdbc.url = "jdbc:h2:mem:repository" """) val system = ActorSystem("testsystem", config)
// app class MyActor extends Actor { val settings = Settings(context.system) val connection = client.connect( settings.Jdbc.Driver, settings.Jdbc.Url ) }
// settings extension
case class Identify(messageId: Any)
case class ActorIdentity( correlationId: Any, ref: Option[ActorRef] )
// dynamic actors
// Extract *all* timeouts into conf files. So that you can easily override them
-Dakka.test.someImportantProperty=3000