English |  Español |  Français |  Italiano |  Português |  Русский |  Shqip

Developing an Akka edge

Chapter 9: Testing

In this chapter, we focus our attention on testing actor systems. We start with understanding the testing strategy and toolkit that Akka has to provide and we will use examples to illustrates these test strategies. In this chapter, we will be using ScalaTest as our test framework which works well with SBT. The nice thing about sbt is that the popular Jenkins and Hudson Continuous Integration environments gel together pretty nice and allows the developer to execute test runs on a automated basis, mostly happening right after code checkins. Writing tests for actors is a little trickier that writing actor code for the reason that when you are writing actor code, you tend to express the code in a sequential manner that reflects the work process requirement (see illustration below) but before you launch it into production, you definitely want to gain a certain level of confidence that the work process is correct for all users, in a concurrent / asynchronous environment.

Akka provides a tool kit akka-testkit which provides you the capability to write tests that validate your actor's processing logic (in isolation, without threads) and its inter-actor processing (with threads, communicating with other actors) and it's quite natural. Taking a step back about what you've learnt so far and reflecting on crafting actors, you would notice that the Akka model focuses your attention into crafting your application's logic into the message handler i.e. def receive = { ... } 

0001 class YourActor extends Actor {
0002 def receive = {
0003 // business logic
0004 // step 1, do this
0005 // step 2, do that
0006 ...
0007 // step N, complete
0008 }
0009 }

and Akka's actor model detaches you from thinking about how data/messages are transported between actors; in fact Akka provides the developer a configuration based approach to configure how the actor system might be customized based on your requirements on scaling by increasing/decreasing parallelism, message routing / dispatching etc as illustrated in previous chapters.

Setting Up

Setting up the dependency is easy, you just add the following line to your build.sbt 

0001 libraryDependencies ++= Seq( ...
0002 "com.typesafe.akka" %% "akka-testkit" % "2.2.1")

Akka is agnostic about which test framework you would like to use though Akka's tests was written using ScalaTest and in this case we'll follow suit too and we add the following dependency to our build.sbt too

0001 libraryDependencies ++= Seq(...
0002 "com.typesafe.akka" %% "akka-testkit" % "2.2.1",
0003 "org.scalatest" % "scalatest_2.10" % "2.0.M6" % "test")

The next few sections will examine how you would go about using Akka's testing toolkit to write tests and you'll notice some parallels in the JVM world of unit-testing.

Isolation Testing

This style of writing tests is almost the first form you will begin with, reason being that you would naturally want to validate your application's logic before subjecting that logic to a concurrency test (which is the subject of the next chapter). Let's use an example assuming an crazy problem of an Actor hold a reference to a stack of invoices, modelled by Order,  and let's assume that this actor can only add invoices upon receiving a message, Checkin, and finally it'll tally the total monies on that stack upon receiving the message, Checkout. Let's take a look at our code 

0001 import akka.actor._
0002 import collection.mutable.SynchronizedStack
0003 sealed trait Message
0004 case class Checkin(amount: Int) extends Message
0005 case class Checkout extends Message
0006 class InvoiceActor extends Actor with ActorLogging {
0007 private[this] var orders = Order()
0008 def receive = {
0009 case Checkin(amount) ⇒ orders.add(amount)
0010 case Checkout ⇒ sender ! orders.tally
0011 }
0012 def topOfOrder = orders.top
0013 }
0014 trait Order {
0015 // Our restaurant is special: we only accept dollars, no coins!
0016 // we need to synchronized access to our shared structure
0017 private[this] var orders : SynchronizedStack[Int] = new SynchronizedStack[Int]()
0018 def add(amount: Int) : Unit = orders = orders push amount
0019 def apply(x: Int) = orders.applyOrElse(x, (_:Int) match { case _ ⇒ 0 })
0020 def tally : Int = orders.foldLeft(0)(_ + _)
0021 def numOfOrders : Int = orders.size
0022 def top : Int = orders.top
0023 }
0024 object Order {
0025 def apply() = new Order {}
0026 }

There are a few ways you can go about testing this code and for starters, we can identify two things we need to test in isolation and subsequently under concurrency. Admittedly, we cheated a little here by using the synchronized stack in our choice because otherwise you might start with a stack and run into concurrency problems before switching out for a synchronized stack.

  • Test the validity of Order
  • Test the validity of the business logic within the actor, Invoices

It's probably a good idea to test Order in isolation so that we can iron the bugs before we begin writing tests for our business logic in our actor. Here is how that test might look like using a test framework like ScalaTest:

0001 import org.scalatest.FlatSpec
0002
0003 class OrderSpec extends FlatSpec {
0004 behavior of "An default Order i.e. no orders have been added"
0005
0006 private[this] val order = Order()
0007
0008 it should "have zero orders" in {
0009 assert(order.numOfOrders === 0)
0010 }
0011
0012 it should "return 0 when tally is invoked" in {
0013 assert(order.tally === 0)
0014 }
0015
0016 behavior of "An order when > 1 order(s) have been added"
0017
0018 it should "have exactly 2 orders when 2 order are added" in {
0019 order.add(1)
0020 assert(order.top === 1)
0021 order.add(2)
0022 assert(order.numOfOrders === 2)
0023 }
0024
0025 it should "return 13 when tally is invoked for orders of values {1,2,5,5}" in {
0026 order.add(5)
0027 order.add(5)
0028 assert(order.tally === 13)
0029 }
0030 }

At this point in time, we are satisfied that Order has been tested and we can move on to begin writing tests for our actor. In our actor, Invoices, we would like to actually make sure the behavior is not only consistent in a single-threaded execution environment but also in a multi-threaded environment. To achieve the former, we can hook our actor into the akka-testkit module by extending from akka.testkit.TestKit and providing an container in which to host our actors i.e. ActorSystem

The following code shows one way of crafting this test using the BDD i.e. Behavior Driven Developmentstyle in ScalaTest.

0001 import akka.testkit._
0002 import akka.actor.{ActorRef, ActorSystem, Props}
0003 import org.scalatest.{FlatSpecLike, BeforeAndAfterAll}
0004
0005 class InvoiceActorSpec extends TestKit(ActorSystem()) with ImplicitSender
0006 with FlatSpecLike
0007 with MustMatchers with BeforeAndAfterAll {
0008
0009 behavior of "`Invoice` actor taking 2 orders"
0010
0011 it should "have two values {400, 500} when the same two orders are issued" in {
0012 val actor = TestActorRef[InvoiceActor]
0013 val ref = actor.underlyingActor
0014 actor ! Checkin(400)
0015 ref.topOfOrder must be (400)
0016 actor ! Checkin(500)
0017 ref.topOfOrder must be (500)
0018 }
0019
0020 it should "return a tally of 900 after two orders of value 400 & 500" in {
0021 val actor = TestActorRef[InvoiceActor]
0022 actor ! Checkin(400)
0023 actor ! Checkin(500)
0024 actor ! Checkout
0025 expectMsg(900)
0026 }
0027 override def afterAll() { system.shutdown }
0028 }

Let's go through what we did to arrive at this test. The first thing we did was to extend from TestKit and provided TestKit with a container in which all actors are hosted (for this test only); next, we mixed in a trait called FlatSpecLike which is the cousin of FlatSpec and we did this because FlatSpec is a class and we cannot extend from two classes, even in Scala. Moving on, we mixed in another trait called ImplicitSender and this trait is necessary for the test to succeed because it allows the test code to receive the response messsages. 

The last statement we made about ImplicitSender deserves more attention. This particular trait is needed in your tests because it provides a special environment in which your tests will execute on and that environment is actually an actor that's single-threaded so that your tests are executed in written order.

Inside InvoiceActorSpec, we have two tests and once testing completes it is necessary to shutdown the ActorSystem or else you would have unintentionally leaked the ActorSystem (that's bad since we are essentially wasting the resources on the computer). We need to find a way to inject the clean up code and we would like it to occur when all of our test completes; but then again you may argue that you would like to create and destroy an ActorSystem before and after every test and you can definitely do that but if you recall one of the core principles of unit testing is that it should complete rather quickly and this cycling of ActorSystem's do take time which leaves us the former option. We are fortunate as ScalaTest's BeforeAndAfterAll trait provides a method, afterAll, which allows us to place our clean up code.

Since we've understood the basics of our actor testing lifecycle, we can dive into the two tests we have. You will notice that they look similar and differ in ways. Common to both tests is the fact that we need to create a reference to our actor under test and Akka named it TestActorRef and this reference basically wraps around our actor, InvoiceActor, along with a special dispatcher called CallingThreadDispatcher (it doesn't create a new thread, but uses the current one i.e. piggy back). This dispatcher is ideal for our cause because we wanted to make sure our test succeeds in isolation (which means our actor's logic works!) before subjecting it to concurrency. 

The next thing you almost want to do when writing tests for actors is to make sure:

  • Messages are received and acted upon
  • Message handler logic is correct

In the former, we send messages to our actor under test via the familiar tell syntax i.e. ! and we know that its acted upon because our actor's logic says to send back the results to the actor and by using the function, expectMsg, we know that it did. There is a entire plethora of functions that allows the developer to test the validity of responses that's embedded in akka.testkit.TestKit and we encourage you to explore the Scala docs on that.

In the latter, our actor's code indicates that upon receiving a Checkin message, we'll deposit that amount into the Order held by the actor and we wanted to make sure that the said amount is the received amount and let's assume we are not allowed to add another message, LastCheckin, to add this feature (though we see no real harm, really) like this:

0001 case class LastCheckin
0002 class InvoiceActor extends Actor with ActorLogging {
0003 def receive = {
0004 case Checkin(amount) ⇒ // as above
0005 case Checkout ⇒ // as above
0006 case LastCheckin ⇒ sender ! topOfOrder
0007 }
0008 // other code omitted
0009 private def topOfOrder = orders.top
0010 }

In honesty, this would work but there's another way! Akka provided us another way of achieving this without much fanfare by allowing us access to our actor, InvoiceActor, by invoking a method, underlyingActor, and this allows us to add a function to check the state of the Order after each Checkin by the expression

0001 ref.topOfOrder must be (400) // now, topOfOrder is made 'public' & no synchronization

There are a few ways we can see the tests we have written in action. Since we are using SBT, we could try the following

>test

and that would trigger a one-time only trigger of all tests in our project. You would see the following output on the console window:

0001 [info] OrderSpec:
0002 [info] An default Order i.e. no orders have been added
0003 [info] - should have zero orders
0004 [info] - should return 0 when tally is invoked
0005 [info] An order when > 1 order(s) have been added
0006 [info] - should have exactly 2 orders when 2 order are added
0007 [info] - should return 13 when tally is invoked for orders of values {1,2,5,5}
0008 [info] InvoiceActorSpec:
0009 [info] `Invoice` actor taking 2 orders
0010 [info] - should have two values {400, 500} when the same two orders are issued
0011 [info] - should return a tally of 900 after two orders of value 400 & 500
0012 [info] Passed: : Total 6, Failed 0, Errors 0, Passed 6, Skipped 0
0013 [success] Total time: 1 s, completed Sep 15, 2013 11:51:54 AM

At this point, you should see that Akka does not interfere with how we write tests and this beneficial to developers as they can continue the testing and mocking frameworks that worked well for them and only worry about asynchronicity when they have to.

Parallel Testing

This section is really about running tests in parallel and you normally would want to do that is so that your test cycle can complete quicker than when running your test suites in sequence. There are two ways you can accomplish this and one way is to not involve Akka and the unit testing framework will handle running parallel tests; the second way is to involve Akka and this manner allows not only parallelism in your unit tests but also you may launch multiple Scala modules, dependent on your requirement.

In the scenario where you want your unit test to take care of launching tests in parallel, in the context of ScalaTest, we need to create tests suites (its really a Scala class which you use to categorize your tests, in a meaningful way) with the following mixins

  • FunSuite [with BeforeAndAfter] with ParallelTestExecution 
  • FunSuiteLike [with BeforeAndAfter] with ParallelTestExecution

where the trait BeforeAndAfter should be mixed in before ParallelTestExecution so ensure the As far as we know in the the ScalaTest 2.0.M7 release, ParallelTestExecution must be the last trait to be mixed in a method, runTest, has been marked final.

The following code demonstrates what the test code looks like after the appropriate traits have been mixed in to allow parallel execution.

0001 import akka.testkit._
0002 import akka.actor.{ActorRef, ActorSystem, Props}
0003 import org.scalatest.{FlatSpecLike, BeforeAndAfterAll, FunSuiteLike, ParallelTestExecution}
0004 import org.scalatest.matchers.MustMatchers
0005 class InvoiceActorFunParSpec extends TestKit(ActorSystem()) with ImplicitSender
0006 with FunSuiteLike
0007 with BeforeAndAfterAll
0008 with MustMatchers
0009 with ParallelTestExecution {
0010 test("have three values {400, 500, 600} when the same three orders are issued") {
0011 val actor = TestActorRef[InvoiceActor]
0012 val ref = actor.underlyingActor
0013 actor ! Checkin(400)
0014 ref.topOfOrder must be (400)
0015 actor ! Checkin(500)
0016 ref.topOfOrder must be (500)
0017 actor ! Checkin(600)
0018 ref.topOfOrder must be (600)
0019 }
0020 test("return a tally of 1,500 after three orders of value 400, 500 & 600") {
0021 val actor = TestActorRef[InvoiceActor]
0022 actor ! Checkin(400)
0023 actor ! Checkin(500)
0024 actor ! Checkin(600)
0025 actor ! Checkout
0026 expectMsg(1500)
0027 }
0028 override def afterAll() = { system.shutdown}
0029 }
0030 class InvoiceActorFunParSpec2 extends TestKit(ActorSystem()) with ImplicitSender
0031 with FunSuiteLike
0032 with BeforeAndAfterAll
0033 with MustMatchers
0034 with ParallelTestExecution {
0035 test("have one value {400} when one order is issued") {
0036 val actor = TestActorRef[InvoiceActor]
0037 val ref = actor.underlyingActor
0038 actor ! Checkin(400)
0039 ref.topOfOrder must be (400)
0040 }
0041 test("return a tally of 500 after one order of value 500") {
0042 val actor = TestActorRef[InvoiceActor]
0043 actor ! Checkin(500)
0044 actor ! Checkout
0045 expectMsg(500)
0046 }
0047 override def afterAll() = { system.shutdown}
0048 }

To run these new tests, we go back to SBT and trigger them again. Depending on how many times you trigger these tests, the order of execution, for InvoiceActorFunParSpec and InvoiceActorFunParSpec2, would be different. A sample output from my machine (with the test suite names in bold) is as follows:

0001 [info] OrderSpec:
0002 [info] An default Order i.e. no orders have been added
0003 [info] - should have zero orders
0004 [info] - should return 0 when tally is invoked
0005 [info] An order when > 1 order(s) have been added
0006 [info] - should have exactly 2 orders when 2 order are added
0007 [info] - should return 13 when tally is invoked for orders of values {1,2,5,5}
0008 [info] InvoiceActorFunParSpec2:
0009 [info] - have one value {400} when one order is issued
0010 [info] - return a tally of 500 after one order of value 500
0011 [info] InvoiceActorFunParSpec:
0012 [info] - have three values {400, 500, 600} when the same three orders are issued
0013 [info] - return a tally of 1,500 after three orders of value 400, 500 & 600
0014 [info] InvoiceActorSpec:
0015 [info] `Invoice` actor taking 2 orders
0016 [info] - should have two values {400, 500} when the same two orders are issued
0017 [info] - should return a tally of 900 after two orders of value 400 & 500
0018 [info] Passed: : Total 10, Failed 0, Errors 0, Passed 10, Skipped 0
0019 [success] Total time: 2 s, completed Sep 16, 2013 10:07:31 AM

The way ScalaTest handles parallel test execution is that it would run the test suites in parallel while each test within the test suite would execute in sequence i.e. written text order. We'll take a look at another form of parallel testing, in the following section. In the second scenario of involving Akka to take care of launching your tests in parallel, then you have the choice of running tests in parallel on a single host, running tests in a coordinated manner across distributed hosts etc.

Concurrent Testing

In the previous section, you've seen how the test framework we've chose allows the developer the capability of writing test logic, validating your actor and also the capability to execute those tests in parallel. Well, Akka has another tool in the toolbox which allows us, to launch tests in parallel across multiple JVM instances. The module we will be discussing is the sbt-multi-jvm and it's different from akka-testkit in that it just cares about one thing: detects the applications it needs to work with and launch each of them into separate JVMs, possibly on different machines.

The sbt-multi-jvm module is actually part of a larger module, akka-multi-node-testkit which allows the developer to craft distributed tests that reflects more of a reality than not i.e. coordinated distributed test. We'll take a look at how to accomplish that a little while later.

In this section, we will take a look at hooking our current tests i.e. written with ScalaTest into sbt-multi-jvm, followed by an illustration on how to launch Scala modules within a test (i.e. controlled) environment. Finally, we'll demonstrate how to use the multi-node testkit. 

Setting Up

Akka, the cool dude that he is, has only two requirements in order to get this to work. First, we need to incorporate the plugin into your build file for your project/plugins.sbt

addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8")

You can either restart sbt or issue the update command to the sbt console to update this new setting. The second thing is to include this new addition to the build file, project/Build.scala and for starters, it should look like this:

0001 import sbt._
0002 import Keys._
0003 import com.typesafe.sbt.SbtMultiJvm
0004 import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.{ MultiJvm }
0005 object Chapter9Build extends Build {
0006 lazy val root = Project(id = "chapter9-testing-actors",
0007 base = file("."),
0008 settings = Project.defaultSettings ++ multiJvmSettings) configs(MultiJvm)
0009 lazy val multiJvmSettings = SbtMultiJvm.multiJvmSettings ++ Seq(
0010 // make sure that MultiJvm test are compiled by the default test compilation
0011 compile in MultiJvm <<= (compile="" in="" multijvm)="" triggeredby="" test),="" disable="" parallel="" tests="" parallelexecution="" test="" :="false," make="" sure="" that="" multijvm="" are="" executed="" by="" the="" default="" target="" executetests="" <<="((executeTests" (executetests="" multijvm))="" map="" {="" case="" ((_,="" testresults),="" (_,="" multijvmresults))=">" val="" results="testResults" ++="" multijvmresults="" (tests.overall(results.values),="" results)})}<="" pre="">
0012
0013 <p>The final thing you need to do is to create a directory&nbsp;<em>src/multi-jvm/scala</em>&nbsp;and start depositing the tests you will write in distributed mode over there. The way in which plugin knows what test to pick out is based on the naming convention that candidates for such execution runs are identified when this string,&nbsp;<span style="constant">MultiJvm</span>, is detected as part of the test name and this applies to the name of the source file. As an example of how this particular test would look like now with this modification contained in a file named&nbsp;<em>InvoiceActorMultiJvmNode1.scala</em> (also<em>&nbsp;InvoiceActorMultiJvmNode2.scala</em> which you'll see later is grouped and executed as part of a group named<em> InvoiceActor</em>)</p>
0014 <pre>
0015 class InvoiceActorMultiJvmNode1 extends TestKit(ActorSystem()) with ImplicitSender
0016 with FlatSpecLike
0017 with BeforeAndAfterAll
0018 with MustMatchers { // as before }</pre>
0019
0020 <p>The portion of the above code marked in <strong>bold</strong> shows the difference; you may wish to contrast this approach against the previous approach we took with ScalaTest and you might actually like it. The mechanism for discovering which tests to execute is based on naming convention. We briefly mentioned earlier that it's looking the files and tests with the same string as <span style="constant">MultiJvm</span> and more specifically we are talking about the value abstracted by <span style="constant">multiJvmMarker</span> (See the tip below on how to change that value). The test kit starts pouring over the files which respect the naming convention and groups them according to <span style="constant">{Test Name}</span>MultiJvm<span style="constant">{Node Name}</span> and the following schematics on our directory structure illustrates the schematics where the tests found in those two files would be grouped with the name InvoiceActor and the test output would reflect that they ran on two JVMs.</p>
0021 <pre>
0022 multi-jvm/
0023 └── scala
0024 ├── InvoiceActorMultiJvmNode1.scala
0025 └── InvoiceActorMultiJvmNode2.scala</pre>
0026
0027 <p>The following figure illustrates how the tests are grouped, as we described previously.</p>
0028 <p><img src="static/chapter-9-actor-testing.001_2.jpg" alt="How tests are grouped" width="512" height="384">&nbsp;</p>
0029 <p><strong>Tip</strong>: If you, or your organization, isn't too keen on the name <span style="constant">MultiJvm</span>, you can change it by placing an expression similar to this in your <span style="constant">Build.scala</span> file:</p>
0030 <pre>
0031 multiJvmMarker in MultiJvm := "GroupTest"
0032 and the placement of that expression can be like this:
0033 object Chapter9Build extends Build {
0034 lazy val root = Project(id = "chapter9-testing-actors",
0035 base = file("."),
0036 settings = Project.defaultSettings ++
0037 multiJvmSettings ++
0038 Seq(multiJvmMarker in MultiJvm := "GroupTest")) configs(MultiJvm) // as before </pre>
0039
0040 <p>When you next trigger the multiple jvm test to run, it'll look for files which has <span style="constant">GroupTest</span> embedded within and start inspecting those files and executing those tests with a name that has <span style="constant">GroupTest</span>.</p>
0041 <p>To illustrate how this would work, you would again employ&nbsp;<em>sbt&nbsp;</em>and enter the following expression into the console</p>
0042 <pre>> multi-jvm:test</pre>
0043 <p>And in our scenario, we have two tests running concurrently i.e. InvoiceActorMultiJvmNode1 and InvoiceActorMultiJvmNode2 and when you trigger that test, you should see an output on the&nbsp;<em>sbt&nbsp;</em>console like the following:</p>
0044 <pre>
0045 [info] * InvoiceActor
0046 [JVM-1] Run starting. Expected test count is: 2
0047 [JVM-1] InvoiceActorMultiJvmNode1:
0048 [JVM-1] Node1 : `Invoice` actor taking 2 orders
0049 [JVM-2] Run starting. Expected test count is: 2
0050 [JVM-2] InvoiceActorMultiJvmNode2:
0051 [JVM-2] Node2 : `Invoice` actor taking 2 orders
0052 [JVM-1] - should have two values {400, 500} when the same two orders are issued
0053 [JVM-1] - should return a tally of 900 after two orders of value 400 & 500
0054 [JVM-1] Run completed in 636 milliseconds.
0055 [JVM-1] Total number of tests run: 2
0056 [JVM-1] Suites: completed 1, aborted 0
0057 [JVM-1] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
0058 [JVM-1] All tests passed.
0059 [JVM-2] - should have two values {400, 500} when the same two orders are issued
0060 [JVM-2] - should return a tally of 900 after two orders of value 400 & 500
0061 [JVM-2] Run completed in 666 milliseconds.
0062 [JVM-2] Total number of tests run: 2
0063 [JVM-2] Suites: completed 1, aborted 0
0064 [JVM-2] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
0065 [JVM-2] All tests passed.
0066 [success] Total time: 1 s, completed Sep 19, 2013 4:48:01 PM</pre>
0067
0068 <p>When you constrast the approach we took here with the one we took with ScalaTest, you'll quickly realize that it doesn't take too much effort to convert those ScalaTests (or whatever test framework you're familiar with) to the ones here. We believe they are not meant to replace those tests you've already prepared with your favourite test framework but its always good to know you have alternatives.</p>
0069 <p>Next, let's take a look at another use case for this module. Let's assume that you have crafted several applications i.e. Scala modules and they start up depending on certain attributes or characteristics that you've represented in a configuration file or even passed them through the command line using the familiar<em> -Dkey=value</em> pairs that we are accustomed in the Java world. The following pseudocode represents a typical (skeletal) Scala module, that starts up based on some assumptions:</p>
0070 <pre>
0071 object MyApplication {
0072 val property1 = System.getProperty("some_property1")
0073 val property2 = System.getProperty("some_property2")
0074 // ...
0075 def main(args: Array[String]) = {
0076 // Asserting properties
0077 // Start up your application depending on the characteristics of those properties
0078 }
0079 }</pre>
0080
0081 <p>At this point, we can enlist the help of sbt-multi-jvm to help us out! Assuming that we have factored out the nasty looking <em>System.getProperty("XXX")</em> methods out to a trait <span style="constant">Configuration</span> and mixed in that into our two fictitious Scala modules, the code now looks cleaner and prettier! Now, our Scala modules / applications can mixin the traits and the work of discovering system specific properties is abstracted away.</p>
0082 <pre>
0083 trait Configuration {
0084 val default = "Nothing"
0085 // System.getProperty is weird...but that's in the Java world...
0086 def getPropertyOrElse(prop: String, default: ⇒ String = default) =
0087 System.getProperty(prop) match { case null ⇒ default; case x ⇒ x }
0088 def getCustomMessageForNode = getPropertyOrElse("custom_message")
0089 def getThreadPoolName = getPropertyOrElse("custom_pool_name")
0090 def getThreadPoolType = Class.forName(getThreadPoolName)
0091 }
0092 object Configuration {
0093 def apply = new Configuration {}
0094 }
0095 object SimpleModuleMultiJvmNode1 extends Configuration with FlatSpecLike with MustMatchers {
0096 def main(args: Array[String]) : Unit = {
0097 val p = getCustomMessageForNode
0098 val pn = getThreadPoolName
0099 pn must include ("ForkJoinPool") // assertion
0100 }
0101 }
0102 object SimpleModuleMultiJvmNode2 extends Configuration with FlatSpecLike with MustMatchers {
0103 def main(args: Array[String]) : Unit = {
0104 val p = getCustomMessageForNode
0105 val pn = getThreadPoolName
0106 pn must include ("ThreadPoolExecutor") // assertion
0107 }
0108 }</pre>
0109 <p>In our example, we again followed the naming conventions as described earlier to group those use cases that we would like to categorized them by. When we accomplished this (as shown), we have two modules that can be started up in separate JVMs that tests for the validity of our properties. To trigger this, we input the expression multi-jvm:run SimpleModule into the sbt console and a test failure would result in error messages spewed onto the console and below is a sample run on our sbt console:</p>
0110 <pre>
0111 > multi-jvm:run SimpleModule
0112 [info] Compiling 5 Scala sources to /Users/raymondtay/akka-edge/ch9-testing-actors/target/scala-2.10/multi-jvm-classes...
0113 [warn] there were 2 deprecation warning(s); re-run with -deprecation for details
0114 [warn] one warning found
0115 [info] * SimpleModule
0116 [success] Total time: 5 s, completed Sep 22, 2013 9:51:26 PM</pre>
0117
0118 <p>Before concluding this section, let us demonstrate to you how we can bootstrap our InvoiceActor into a Scala module and have it run a test; this covers the use case where you have actors and you like them to be launched from a Scala module that resembles a standalone application. Here's what happened to that piece of code, now, as compared to what we did earlier</p>
0119 <pre>
0120 object SimpleModuleMultiJvmNode1 extends TestKit(ActorSystem("SimpleModuleAS")) with Configuration with ImplicitSender
0121 with FlatSpecLike
0122 with MustMatchers
0123 with BeforeAndAfterAll {
0124 def doAction(f: ⇒ Unit) = f
0125 def main(args: Array[String]) : Unit = {
0126 try doAction{
0127 val p = getCustomMessageForNode
0128 val pn = getThreadPoolName
0129 pn must include ("ForkJoinPool")
0130 val invoice = TestActorRef[InvoiceActor]
0131 invoice ! Checkin(500)
0132 invoice ! Checkout
0133 expectMsg(500)
0134 } finally afterAll
0135 }
0136 override def afterAll = { system.shutdown }
0137 }</pre>
0138
0139 <p>What happened here is that we used the previous technique of creating a <span style="constant">TestActorRef</span> to wrap around our <span style="constant">InvoiceActor</span> (which is the real actor we want to protect) and have it execute the simple test of checking in and out and making sure that the amount he/she pays is exactly what he/she ordered. Once the actor has been bootstrapped into the module, you could probably recognize that the test pretty much stays the same and we can still conduct an array of tests on the use case but from the standpoint of a single JVM (as compared to within a test suite for example).</p>
0140 <h2>Multi-Node Testing</h2>
0141 <p>Previously, we demonstrated that you can improve your testing cycle by running your tests concurrently but that doesn't really tell the whole story because a big part of your actor application would be communicating with other machines in the network. As you continue to write actor code, you would quickly realize that testing on a single host would out lived its usefulness and we need to find some manner to put the application to a distributed test. The following simplified diagram illustrates the concurrent testing we have encountered so far where actor-1 delivers the payload to actor-3 through actor-2 (which we'll assume is performing some middle-man processing) and the right-hand side of the diagram illustrates how each actor code is tested by launching parallel JVMs.</p>
0142 <p style="text-align: justify;">&nbsp;<img src="static/chapter-9-actor-testing.002.jpg" alt="" width="1024" height="768"></p>
0143 <p style="text-align: justify;">When we want to test our application in a distributed way, ideally we would need a framework that allows our actor code to be hosted within a test framework like ScalaTest and the tests would be packaged along with our dependencies and shipped off to the destination nodes and begins the distributed testing. Fortunately for us, the framework is already present and is known as <em><strong>akka-multi-node-testkit</strong></em> and we are going to take a look at it.</p>
0144 <p style="text-align: justify;">How this testkit works is that there is a <span style="constant">TestConductor</span> that coordinates and runs the tests across the nodes that we have deployed our application to. The next thing is for us to hook our tests to a specification which allows our tests to be hooked to the multi-node test framework's lifecycle and its called <span style="constant">MultiNodeSpecCallbacks</span>&nbsp;(a Scala trait) and the two abstract methods in it allows the developer to define lifecycle methods that controls what happens<em>&nbsp;</em>before and after&nbsp;<em>all&nbsp;</em>the tests are run by providing your own version of the trait's methods <span style="constant">multiNodeSpecAfterAll</span> and <span style="constant">multiNodeSpecBeforeall</span>. Next, we need to provide a configuration object that allows this testkit to know how many nodes are undergoing the test and placing barriers at appropriate places to correctly simulate the test; these barriers are meant to simulate the various startup/shutdown sequences of your machines or application code. Let's begin by setting this up.</p>
0145 <h3 style="text-align: justify;">Setting up</h3>
0146 <p style="text-align: justify;">Edit the build.sbt file and include the following dependency to it:</p>
0147 <pre>"com.typesafe.akka" %% "akka-multi-node-testkit" % "2.2.1"</pre>
0148 <p style="text-align: justify;">and update the project by triggering the <em>update</em> command to the <em>sbt</em> console and sbt should shortly download the dependency to your local file system if it hasn't already done so. This testkit allows your test to be delivered and executed on various machines, so its probably a good idea to create password-less <em>SSH</em> access to those distributed nodes designated for your tests.</p>
0149 <h3 style="text-align: justify;">Example</h3>
0150 <p style="text-align: justify;">Let's take a further look by building the classic example of messaging forwarding involving three actors. The first actor will begin by sending the message to the middle-actor and that would be forwarded to the last actor, by which the last actor will send a reply back to the original actor. Next, our test environment would consists of a single host that's attached to two virtual machines with the localhost acting as the <em>master</em> / <em>TestConductor</em> for this example. We have configured password-less access to those virtual machines via <em>SSH</em> using the conventional techniques (e.g. creating a passwordless ssh access within a trusted data centre environment). The diagram below illustrates our scenario</p>
0151 <p style="text-align: justify;">&nbsp;</p>
0152 <p><img src="static/chapter-9-actor-testing.003_1.jpg" alt="" width="1024" height="768"></p>
0153 <p>The following code demonstrates this (we'll explain in more detail in a short while)</p>
0154 <pre>
0155 import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
0156 import org.scalatest.matchers.MustMatchers
0157 import akka.actor._
0158 import akka.testkit.ImplicitSender
0159 import akka.remote.testkit._
0160 // Your specification should mixin what you need for your test
0161 trait ForwardMultiNodeSpec extends MultiNodeSpecCallbacks
0162 with WordSpecLike
0163 with MustMatchers
0164 with BeforeAndAfterAll {
0165 override def beforeAll() = multiNodeSpecBeforeAll()
0166 override def afterAll() = multiNodeSpecAfterAll()
0167 }
0168 //Create as many `roles` as you have `nodes`
0169 object MConfig extends MultiNodeConfig {
0170 val node1 = role("Actor-1")
0171 val node2 = role("Actor-2")
0172 val node3 = role("Actor-3")
0173 }
0174 // the sbt-multi-jvm testkit would launch 1 `class` per `node
0175 class ForwardMNMultiJvmNode1 extends ForwardMultipleNodeDemo
0176 class ForwardMNMultiJvmNode2 extends ForwardMultipleNodeDemo
0177 class ForwardMNMultiJvmNode3 extends ForwardMultipleNodeDemo
0178 class ForwardMultipleNodeDemo extends MultiNodeSpec(MConfig)
0179 with ForwardMultiNodeSpec
0180 with ImplicitSender {
0181 import MConfig._
0182 def initialParticipants = roles.size
0183 // Override these two methods if you like to conduct some
0184 // setup/shutdown for `all` tests
0185 override def atStartup() = println("STARTING UP!")
0186 override def afterTermination() = println("TERMINATION!")
0187 "A multi-node illustrating `message-forwarding`" must {
0188 "wait for all nodes to enter barrier" in {
0189 enterBarrier("startingup")
0190 }
0191 "send to and receive from a remote node via `forwarding`" in {
0192 runOn(node1) {
0193 enterBarrier("deployed")
0194 var msgReturned = false
0195 val next = system.actorFor(node(node2) / "user" / "MiddleMan")
0196 val me = system.actorOf(Props(new Actor with ActorLogging {
0197 def receive = {
0198 case "hello" ⇒ next forward (self, "hello")
0199 case ("hello",Stop) ⇒ condition = true
0200 }}), "Initiator")
0201 me ! "hello"
0202 awaitCond(msgReturned == true)
0203 }
0204 runOn(node2) {
0205 val next = system.actorFor(node(node3) / "user" / "Destinator")
0206 system.actorOf(Props(new Actor with ActorLogging {
0207 def receive = {
0208 case (to, msg) ⇒ next forward (to, msg)
0209 }}), "MiddleMan")
0210 enterBarrier("deployed")
0211 }
0212 runOn(node3) {
0213 system.actorOf(Props(new Actor with ActorLogging {
0214 def receive = {
0215 case (to:ActorRef, msg) ⇒ to ! (msg, Stop)
0216 }}), "Destinator")
0217 enterBarrier("deployed")
0218 }
0219 enterBarrier("finished")
0220 }
0221 }
0222 }</pre>
0223 <p>Let's go through in more detail what we've done over here. What we did was to craft a test that consists of three actors with names Initiator, MiddleMan and Destinator; each of these actors are to be executed on the nodes node1, node2, node3 respectively through the function, runOn. If you examine the actors code, you would notice that its just regular akka actor code but there's a caveat and that is we need to make sure the order of startup is correct for our small simulation.</p>
0224 <p>The Initiator would need to be started up after MiddleMan and Destinator otherwise Initiator would not be able to locate the appropriate actor to forward the message to, and this testkit gave us the answer by allowing us to place barriers i.e. barriers dictate that no one crosses the barrier untill everyone has arrived at the barrier.</p>
0225 <p>Using the barrier, we can control the order of the startup and in our example MiddleMan and Destinator would startup before Initiator (which is what we want). Notice that we place barriers upon startup and shutdown in our test lifecycle and that is probably a good practice to abide by since you won't run into situations where the tests runs out of control because of these synchronization issues. When writing tests, there's often a placement in the framework which prepares and subsequently releases resources for the tests, akin to <em>test fixtures</em>. This testkit has it too and if you want to modify the default behavior beyond the default, then you need to provide your own implementations of:</p>
0226 <pre>
0227 def atStartup() : Unit
0228 def afterTermination() : Unit</pre>
0229
0230 <p>which alters the default behavior, which is to do nothing, when your tests are starting up or shutting down. In our example, we simply gave it a printout and during the test run, you would notice this output. To give this example a run, enter the expression into the&nbsp;<em>sbt&nbsp;</em>console:</p>
0231 <pre>multi-node-test-only ForwardMN</pre>
0232 <p>and the testkit would package our application and its dependencies together producing an artifact jar ch9-testing-actors_2.10.2-1.0-multi-jvm-assembly.jar and transports this over to the hosts. Here's a sample output:</p>
0233 <pre>
0234 [info] Including protobuf-java-2.4.1.jar
0235 [info] Including akka-actor_2.10-2.2.1.jar
0236 [info] Including akka-multi-node-testkit_2.10-2.2.1.jar
0237 ... some lines omitted
0238 [info] Including netty-3.6.6.Final.jar
0239 [info] Including scalatest_2.10-2.0.M7.jar
0240 [info] Including uncommons-maths-1.2.2a.jar
0241 [info] Including scala-library.jar
0242 [info] SHA-1: WrappedArray(-22, 53, -18, 49, -65, 38, -30, -5, 89, -62, 123, -65, -86, 27, -85, 90, -77, 104, -55, 19)
0243 [info] Packaging /Users/tayboonl/akka-edge/ch9-testing-actors/target/ch9-testing-actors_2.10.2-1.0-multi-jvm-assembly.jar ...
0244 [info] Done packaging.
0245 [info] * ForwardMN
0246 ... more lines omitted reporting test results </pre>
0247
0248 <p>and you can correlate this action by observing that several SSH processes have been started and would live as long as the tests lasts, and the test would report that it completely successfully !</p>
0249 <h2>Timed Tests</h2>
0250 <p>Thus far, we have made no assumptions about how quickly should our transactions should complete and to be specific we made no assertions about the latency we are willing to compromise for when placing orders or checking out. The akka-testkit provides us a mini-DSL in which we can express our latency concerns by placing the logic in which we want accomplished within a time range using this</p>
0251 <pre>
0252 within([min, ] max) {
0253 // time sensitive business logic
0254 }</pre>
0255
0256 <p>What this means is the business logic must be completed between the timed window between (min, max) and this mini-DSL can be embedded. Let's take a look at how our tests can be crafted to account for this. Let's assume that our business owner lays the requirement that any single order should take no more than 0.5 seconds to register while 2 or more orders should take no more than 0.8 seconds to register then our system can be validated for this requirement with the following changes made to our code</p>
0257 <pre>
0258 class InvoiceActorTimeSpec extends TestKit(ActorSystem()) with ImplicitSender
0259 with FlatSpecLike
0260 with BeforeAndAfterAll
0261 with MustMatchers
0262 with ParallelTestExecution {
0263 behavior of "`Invoice` actor taking 2 orders must complete within 800 millis"
0264 import scala.concurrent.duration._
0265 it should "have two values {400, 500} when the same two orders are issued" in {
0266 val actor = TestActorRef[InvoiceActor]
0267 val ref = actor.underlyingActor
0268 within (800 millis) {
0269 within(500 millis) {
0270 actor ! Checkin(400)
0271 ref.topOfOrder must be (400)
0272 }
0273 within(500 millis) {
0274 actor ! Checkin(500)
0275 ref.topOfOrder must be (500)
0276 }
0277 }
0278 }</pre>
0279
0280 <p>On the subject of time comes the question: "Would the tests still give the same results when executed on a faster machine compared to a slower machine? " many of the testing techniques with Akka carries a timeout value in which the test must complete or it would report test failures. Most of timeout values are implicit but they can be made explicit and in situations where you need to deliberately slow down the test(s), you can include the expression in your tests:</p>
0281 <p>1.second.dilated</p>
0282 <p>That's with the assumption of that the expression is given within a ActorSystem with the necessary imports. Be careful when the criteria for the deemed success or failure of your tests depends on time since a heavily loaded system would mean that your application has to compete with other stuff for CPU-/IO-time; tests should be, afterall, deterministic.</p></=>

There has been error in communication with Booktype server. Not sure right now where is the problem.

You should refresh this page.