Scala アクターを単体テストする良い方法を知っている人はいますか? 一般的な意味では、メッセージを受信し、それに応答して他のメッセージを送信するアクターがあります。これは複数のスレッドで行われ、正しくないアクターは間違ったメッセージを送信するか、メッセージをまったく送信しない可能性があります。テスト対象のアクターにメッセージを送受信するモックアップ アクターを作成する簡単な方法が必要です。この分野での経験はありますか?
5 に答える
アクター スタイルのメッセージ パッシングの動的な性質のため、アクターをモックすることは通常はまったく問題ありません。目的のメッセージを受信するアクターを作成するだけで、家から解放されます。もちろん、このモック アクターがメッセージの受け渡し先であることを確認する必要がありますが、テストしようとしているアクターが再入可能である限り、問題にはなりません。
複雑さはいくつかの要因に依存すると思います...
- アクターはどの程度ステートフルですか?
非同期のみの冪等関数のように動作する場合は、メッセージを送信するアクターをモックアップして、期待されるメッセージを受信することを確認するだけの簡単な作業である必要があります。ハングするのではなく、失敗する可能性がある妥当な時間内に応答がある場合に備えて、モック アクターで react/receiveWithin を使用することをお勧めします。
ただし、メッセージが互いに独立していない場合は、さまざまな一連のメッセージと期待される結果でテストする必要があります。
- テスト対象のアクターは何人のアクターとやり取りしますか?
アクターが他の多くのユーザーと対話することが予想され、それがステートフルである場合、メッセージを送受信する複数のアクターでテストする必要があります。メッセージが到着する順序を保証できない可能性があるため、アクターがメッセージを送信する順序を変更するか、メッセージを生成するアクターにランダムな一時停止を導入してテストを何度も実行する必要があります。
アクターをテストするためのビルド済みフレームワークについては知りませんが、Erlang にインスピレーションを求めることができるかもしれません。
http://svn.process-one.net/contribs/trunk/eunit/doc/overview-summary.html
アクターの単体テストの試み (動作します)。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 に格納されます。次に、ChatController から送信されたケース クラスを取得する mos に一致が適用されます。結果が期待どおりの場合 (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 ケース クラスを返します。
アクターを自分でテストする方法について疑問に思っていました。
これが私が思いついたものです。このアプローチに問題がある人はいますか?
メッセージを直接送信するのではなく、アクターがメッセージ送信を関数に委任した場合はどうなるでしょうか?
次に、テストでは、呼び出された回数やメソッドが呼び出された引数を追跡する関数で関数を交換できます。
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.