Testing
Most of us are familiar with unit testing code with more traditional concurrency models. In this case of threaded code, for many, this means throwing up your hands in disgust and walking away. Testing concurrency with threads is hard. Testing concurrency in Pony is much easier but, if you haven't encountered anything like it before it can be hard to know where to start.
The patterns in this chapter will cover various ways of testing your Pony code. We will pay particular attention to how to test actors. Please note that this chapter assumes that you are familiar with the basics of using PonyTest, the Pony unit testing framework. If you aren't, please review the PonyTest documentation.
Testing Notifier Interactions
Problem
Event driven code is very common in Pony. Many classes take a "notifier" class that has callbacks that get triggered when certain events happen. The network code such as UDPNotify
and TCPNotify
are examples of this. As you write your own Pony code, the notifier pattern is one you'll end up using quite a bit. Testing that your code is correctly interacting with notifiers is straightforward; however, how you go about doing that isn't immediately obvious. Imagine for a moment that you have the following actor:
actor Receiver
let _notify: Notified
new create(notify: Notified iso) =>
_notify = consume notify
be receive(msg: String) =>
_notify.received(this, msg)
It's really simple. When it receives some string, it will pass its own identity and that string along to the object it is supposed to notify. What is the object it will notify? Anything that conforms to the interface
interface Notified
fun ref received(rec: Receiver ref, msg: String)
In our contrived, simplified example, we want to know that if we call receive
on the Receiver
actor, then the string we call it with will end up being passed to our notifier where we can process it. So, how do we go about that?
Solution
use "ponytest"
actor Main is TestList
new create(env: Env) => PonyTest(env, this)
new make() => None
fun tag tests(test: PonyTest) =>
test(_TestNotifier)
class iso _TestNotifier is UnitTest
fun name(): String => "test notifier"
fun apply(h: TestHelper) =>
let r = Receiver(recover TestNotifier(h, "Hi") end)
r.receive("Hi")
h.long_test(2_000_000_000)
fun timed_out(h: TestHelper) =>
h.complete(false)
class TestNotifier is Notified
let _h: TestHelper
let _expected: String
new iso create(h: TestHelper, expected: String) =>
_h = h
_expected = expected
fun ref received(rec: Receiver ref, msg: String) =>
_h.assert_eq[String](_expected, msg)
_h.complete(true)
interface Notified
fun ref received(rec: Receiver ref, msg: String)
actor Receiver
let _notify: Notified
new create(notify: Notified iso) =>
_notify = consume notify
be receive(msg: String) =>
_notify.received(this, msg)
Discussion
Notifiers work by using structural typing. We define an interface that the given notifier has to implement and then create concrete implementations. In our above solution, you can see this with:
interface Notified
fun ref received(rec: Receiver ref, msg: String)
and
class TestNotifier is Notified
let _h: TestHelper
let _e: String
new iso create(h: TestHelper, e: String) =>
_h = h
_e = e
fun ref received(rec: Receiver ref, msg: String) =>
_h.assert_eq[String](_e, msg)
_h.complete(true)
Our test is verifying that our Receiver correctly uses the notifier and that when we call receive
on a Receiver
, we pass the correct data to the notifier's received
method:
fun ref received(rec: Receiver ref, msg: String) =>
_h.assert_eq[String](_e, msg)
_h.complete(true)