Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
Reactive Amsterdam meetup 12.07.2016
Mike Kotsur

@sotomajor_ua
Actor testing patterns
with TestKit
• Testing
• Testing Akka applications
• Better testing Akka applications
• Testing Akka applications with less problems
Ag...
TDD
A provocative talk and blog
posts has led to a conversation
where we aim to understand
each others' views and
experiences....
Programmers at work maintaining a Ruby on Rails testless application
Eero Järnefelt, Oil on canvas, 1893
A test
An async test
An async test
• Retry the assertion many times;
• The worker tells when the work is done.
2 conceptual solutions
• Retry the assertion many times;
• The worker tells when the work is done.
2 conceptual solutions
questions
How many time...
• Not just actors and messages!
• Willing to help you with the test kit.
So, what about Akka?
object IncrementorActorMessages {

case class Inc(i: Int)

}



class IncrementorActor extends Actor {

var sum: Int = 0

...
class IncrementorActorTest

extends TestKit(ActorSystem("test-system")) {





// it(“should …”) { … }





}
// We need a...
it("should have sum = 0 by default") {



val actorRef = TestActorRef[IncrementorActor]

actorRef.underlyingActor.sum shou...
it("should increment on new messages1") {



val actorRef = TestActorRef[IncrementorActor]



actorRef ! Inc(2)

actorRef....
class LazyIncrementorActor extends Actor {

var sum: Int = 0



override def receive: Receive = {

case Inc(i) =>

Future ...
object IncrementorActorMessages {
case class Inc(i: Int)
case object Result
}
class IncrementorActor extends Actor {
var s...
// ... with ImplicitSender
it("should have sum = 0 by default") {
val actorRef = system
.actorOf(Props(classOf[Incrementor...
it("should increment on new messages") {
val actorRef = system
.actorOf(Props(classOf[IncrementorActor]))
actorRef ! Inc(2...
TestKit
def expectMsg[T](d: Duration, msg: T): T
def expectMsgPF[T](d: Duration)
(pf:PartialFunction[Any, T]): T
def expectMsgClas...
def receiveN(n: Int, d: Duration): Seq[AnyRef]
def receiveWhile[T](max: Duration, idle: Duration, n: Int)
(pf: PartialFunc...
def awaitCond(p: => Boolean, max: Duration, interval:
Duration)
def awaitAssert(a: => Any, max: Duration, interval: Durati...
def ignoreMsg(pf: PartialFunction[AnyRef, Boolean])
def ignoreNoMsg()
// ignore*
val probe = TestProbe()
probe watch target
target ! PoisonPill
probe.expectTerminated(target)
// death watch
Somewhere in ...
• context.actorOf()
• context.parent
• context.child(name)
Dependency injection
• 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 Wor...
// akka.loggers = ["akka.testkit.TestEventListener"]
EventFilter.info(
message = "Hello World!”, occurrences = 1
).interce...
class MyActor extends Actor with ActorLogging {
override def supervisorStrategy: Unit =
OneForOneStrategy() {
case _: Fata...
// unit-test style
val actorRef = TestActorRef[MyActor](MyActor.props())
val pf = actorRef.underlyingActor
.supervisorStra...
// coding time
// better tests
flaky /ˈfleɪki/ adjective,
informal
(of a device or
software) prone to
break down; unreliable.
class MyActorTest
extends TestKit(ActorSystem("test-system"))
with FunSpecLike {
override protected def afterAll(): Unit =...
trait AkkaTestBase extends BeforeAndAfterAll
with FunSpecLike { this: TestKit with Suite =>
override protected def afterAl...
akka.test.single-expect-default = 3 seconds
akka.test.timefactor = 10
// timeouts
import scala.concurrent.duration._
impor...
class Settings(...) extends Extension {
object Jdbc {
val Driver = config.getString("app.jdbc.driver")
val Url = config.ge...
// test
val config = ConfigFactory.parseString("""
app.jdbc.driver = "org.h2.Driver"
app.jdbc.url = "jdbc:h2:mem:repositor...
case class Identify(messageId: Any)
case class ActorIdentity(
correlationId: Any,
ref: Option[ActorRef]
)
// dynamic actors
// Beware of unique name lifecycles and scopes…
// Prefer checking messages over checking side-effects.
// Single responsibility principle.
// Run your tests on slow VM and different OS.
// Extract *all* timeouts into conf files. So that you can easily
override them
-Dakka.test.someImportantProperty=3000
// Don’t hesitate to rewrite a test, or the code.
// Don’t trust assertion errors, check logs.
// Base your decisions on historical data.
// QA?
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Akka Testkit Patterns
Nächste SlideShare
Wird geladen in …5
×

Akka Testkit Patterns

Actors testing is different from what you are used to. First, you have messages instead of calls, second, you have to deal with concurrency and all the consequences that it brings with it:
* Thread.sleeps in tests;
* Flakiness;
* Green on laptop / red on jenkins;
* Missed test cases.

Fortunately Akka provides a TestKit which helps to avoid all these things when used properly. Let's take out and inspect tools from this kit and learn couple of useful patterns.

  • Als Erste(r) kommentieren

Akka Testkit Patterns

  1. 1. Reactive Amsterdam meetup 12.07.2016 Mike Kotsur
 @sotomajor_ua Actor testing patterns with TestKit
  2. 2. • Testing • Testing Akka applications • Better testing Akka applications • Testing Akka applications with less problems Agenda
  3. 3. TDD
  4. 4. 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
  5. 5. Programmers at work maintaining a Ruby on Rails testless application Eero Järnefelt, Oil on canvas, 1893
  6. 6. A test
  7. 7. An async test
  8. 8. An async test
  9. 9. • Retry the assertion many times; • The worker tells when the work is done. 2 conceptual solutions
  10. 10. • Retry the assertion many times; • The worker tells when the work is done. 2 conceptual solutions questions How many times? How long to wait?
  11. 11. • Not just actors and messages! • Willing to help you with the test kit. So, what about Akka?
  12. 12. 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
  13. 13. class IncrementorActorTest
 extends TestKit(ActorSystem("test-system")) {
 
 
 // it(“should …”) { … }
 
 
 } // We need an actor system
  14. 14. it("should have sum = 0 by default") {
 
 val actorRef = TestActorRef[IncrementorActor]
 actorRef.underlyingActor.sum shouldEqual 0
 
 } // Uses the same thread Has a real type!
  15. 15. 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
  16. 16. class LazyIncrementorActor extends Actor {
 var sum: Int = 0
 
 override def receive: Receive = {
 case Inc(i) =>
 Future {
 Thread.sleep(100)
 sum = sum + i
 }
 }
 
 }
  17. 17. 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
  18. 18. // ... with ImplicitSender it("should have sum = 0 by default") { val actorRef = system .actorOf(Props(classOf[IncrementorActor])) actorRef ! Result expectMsg(0) } TestKit trait
  19. 19. 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) }
  20. 20. TestKit
  21. 21. 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*
  22. 22. 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*
  23. 23. 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*
  24. 24. def ignoreMsg(pf: PartialFunction[AnyRef, Boolean]) def ignoreNoMsg() // ignore*
  25. 25. val probe = TestProbe() probe watch target target ! PoisonPill probe.expectTerminated(target) // death watch Somewhere in the app code
  26. 26. • context.actorOf() • context.parent • context.child(name) Dependency injection
  27. 27. • Use props; • Use childMaker: ActorRefFactory => ActorRef; • Use a fabricated parent. Dependency Injection
  28. 28. class MyActor extends Actor with ActorLogging { override def receive: Receive = { case DoSideEffect => log.info("Hello World!") } } // event filter
  29. 29. // akka.loggers = ["akka.testkit.TestEventListener"] EventFilter.info( message = "Hello World!”, occurrences = 1 ).intercept { myActor ! DoSomething } // event filter
  30. 30. class MyActor extends Actor with ActorLogging { override def supervisorStrategy: Unit = OneForOneStrategy() { case _: FatalException => SupervisorStrategy.Escalate case _: ShitHappensException => SupervisorStrategy.Restart } } // supervision
  31. 31. // 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
  32. 32. // coding time
  33. 33. // better tests
  34. 34. flaky /ˈfleɪki/ adjective, informal (of a device or software) prone to break down; unreliable.
  35. 35. 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
  36. 36. 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
  37. 37. akka.test.single-expect-default = 3 seconds akka.test.timefactor = 10 // timeouts import scala.concurrent.duration._ import akka.testkit._ 10.milliseconds.dilated
  38. 38. class Settings(...) extends Extension { object Jdbc { val Driver = config.getString("app.jdbc.driver") val Url = config.getString("app.jdbc.url") } } // settings extension
  39. 39. // 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
  40. 40. case class Identify(messageId: Any) case class ActorIdentity( correlationId: Any, ref: Option[ActorRef] ) // dynamic actors
  41. 41. // Beware of unique name lifecycles and scopes…
  42. 42. // Prefer checking messages over checking side-effects.
  43. 43. // Single responsibility principle.
  44. 44. // Run your tests on slow VM and different OS.
  45. 45. // Extract *all* timeouts into conf files. So that you can easily override them -Dakka.test.someImportantProperty=3000
  46. 46. // Don’t hesitate to rewrite a test, or the code.
  47. 47. // Don’t trust assertion errors, check logs.
  48. 48. // Base your decisions on historical data.
  49. 49. // QA?

×