任何人都知道单元测试 Scala 演员的好方法吗?在一般意义上,我有一个接收消息并会发送其他消息作为响应的参与者。这是在多个线程上完成的,不正确的参与者可能会发送错误消息或根本不发送消息。我需要一种简单的方法来创建一个模型 Actor,它可以向被测试的 Actor 发送和接收消息。有这方面的经验吗?
5 回答
由于 Actor 式消息传递的动态特性,模拟 Actor 通常完全没有问题。只需创建一个接收所需消息的演员,您就可以回家了。您当然需要确保这个模拟 actor 是传递消息的那个,但只要您尝试测试的 actor 是可重入的,这应该不是问题。
我认为复杂性取决于几个因素......
- 演员的状态如何?
如果它表现得像一个幂等函数,只是异步的,那么它应该是一个简单的问题,即模拟一个发送消息的参与者,然后检查它是否接收到预期的消息。您可能希望在模拟演员上使用 react/receiveWithin,以防在合理的时间内有响应,您可能会失败而不是挂起。
但是,如果消息不是相互独立的,那么您应该使用各种消息序列和预期结果对其进行测试。
- 被测试的演员将与多少演员互动?
如果期望一个参与者与许多其他参与者交互,并且它是有状态的,那么应该使用多个发送/接收消息的参与者进行测试。由于您可能无法保证消息到达的顺序,因此您应该确保置换参与者发送消息的顺序,或者在生成消息的参与者中引入随机暂停并多次运行测试。
我不知道有任何用于测试演员的预构建框架,但您可以向 Erlang 寻求灵感。
http://svn.process-one.net/contribs/trunk/eunit/doc/overview-summary.html
我一直想知道如何自己测试演员。
这是我想出的,有人看到这种方法有问题吗?
如果您的参与者将消息发送委托给一个函数,而不是直接发送消息,该怎么办?
然后,您的测试可以将函数替换为跟踪调用次数和/或调用该方法的参数的函数:
class MyActor extends Actor {
var sendMessage:(Actor, ContactMsg) => Unit = {
(contactActor, msg) => {
Log.trace("real sendMessage called")
contactActor ! msg
}
}
var reactImpl:PartialFunction(Any, Unit) = {
case INCOMING(otherActor1, otherActor2, args) => {
/* logic to test */
if(args){
sendMessage(otherActor1, OUTGOING_1("foo"))
} else {
sendMessage(otherActor2, OUTGOING_2("bar"))
}
}
}
final def act = loop {
react {
reactImpl
}
}
您的测试用例可能包含如下代码:
// setup the test
var myActor = new MyActor
var target1 = new MyActor
var target2 = new MyActor
var sendMessageCalls:List[(Actor, String)] = Nil
/*
* Create a fake implementation of sendMessage
* that tracks the arguments it was called with
* in the sendMessageCalls list:
*/
myActor.sendMessage = (actor, message) => {
Log.trace("fake sendMessage called")
message match {
case OUTGOING_1(payload) => {
sendMessageCalls = (actor, payload) :: sendMessageCalls
}
case _ => { fail("Unexpected Message sent:"+message) }
}
}
// run the test
myActor.start
myActor.reactImpl(Incoming(target1, target2, true))
// assert the results
assertEquals(1, sendMessageCalls.size)
val(sentActor, sentPayload) = sendMessageCalls(0)
assertSame(target1, sentActor)
assertEquals("foo", sentPayload)
// .. etc.
我尝试对演员进行单元测试(它有效)。我使用Specs作为框架。
object ControllerSpec extends Specification {
"ChatController" should{
"add a listener and respond SendFriends" in{
var res = false
val a = actor{}
val mos = {ChatController !? AddListener(a)}
mos match{
case SendFriends => res = true
case _ => res = false
}
res must beTrue
}
其工作原理是向单例 ChatController 发送同步调用。ChatController 通过使用reply()来响应。响应作为被调用函数的返回发送,该函数被存储到 mos 中。然后对 mos 应用匹配,获取从 ChatController 发送的案例类。如果结果是预期的 (SendFriends),则将 res 设置为 true。res must beTrue断言决定了测试的成功或失败。
我正在测试的演员单身人士
import ldc.socialirc.model._
import scala.collection.mutable.{HashMap, HashSet}
import scala.actors.Actor
import scala.actors.Actor._
import net.liftweb.util.Helpers._
//Message types
case class AddListener(listener: Actor)
case class RemoveListener(listener: Actor)
case class SendFriends
//Data Types
case class Authority(usr: Actor, role: String)
case class Channel(channelName: String, password: String, creator: String, motd: String, users: HashSet[Authority])
object ChatController extends Actor {
// The Channel List - Shows what actors are in each Chan
val chanList = new HashMap[String, Channel]
// The Actor List - Shows what channels its in
val actorList = new HashMap[Actor, HashSet[String]]
def notifyListeners = {
}
def act = {
loop {
react {
case AddListener(listener: Actor)=>
actorList += listener -> new HashSet[String]
reply(SendFriends)
}
}
}
start //Dont forget to start
}
虽然它不完整,但它确实按预期返回了 Sendfriends 案例类。