Scala 如何模拟儿童演员来测试Akka系统?
当我在Akka中有一个父参与者时,它会在初始化时直接创建一个子参与者,当我想为父参与者编写单元测试时,如何用TestProbe或mock替换子参与者 例如,使用以下人造代码示例:Scala 如何模拟儿童演员来测试Akka系统?,scala,unit-testing,akka,Scala,Unit Testing,Akka,当我在Akka中有一个父参与者时,它会在初始化时直接创建一个子参与者,当我想为父参与者编写单元测试时,如何用TestProbe或mock替换子参与者 例如,使用以下人造代码示例: class TopActor extends Actor { val anotherActor = context.actorOf(AnotherActor.props, "anotherActor") override def receive: Receive = { case "call anot
class TopActor extends Actor {
val anotherActor = context.actorOf(AnotherActor.props, "anotherActor")
override def receive: Receive = {
case "call another actor" => anotherActor ! "hello"
}
}
class AnotherActor extends Actor {
override def recieve: Receive = {
case "hello" => // do some stuff
}
}
如果我想为TopActor编写一个测试,检查发送给另一个Actor的消息是否为“hello”,我如何替换另一个Actor的实现?似乎TopActor直接创建了这个子对象,因此不容易访问。以下方法似乎有效,但直接覆盖另一个参与者的val似乎有点粗糙。我想知道是否还有其他更清洁/推荐的解决方案,这就是为什么我仍然问这个问题,尽管我有这个有效的答案:
class TopActorSpec extends MyActorTestSuiteTrait {
it should "say hello to AnotherActor when receive 'call another actor'" {
val testProbe = TestProbe()
val testTopActor = TestActorRef(Props(new TopActor {
override val anotherActor = testProbe.ref
}))
testTopActor ! "call another actor"
testProbe.expectMsg(500 millis, "hello")
}
}
您可能需要检查我在网上找到的此解决方案(积分转到Stig Brautaset): 这是一个优雅的解决方案,但有点复杂。它首先通过一个trait(ChildrenProvider)创建另一个actor,然后您可以拥有一个productionChildrenProvider,它返回另一个actor实例。在测试中,testChildrenProvider将返回TestProbe。
看看测试代码,它非常干净。但是Actor实现是我必须考虑的事情。我自己对Scala还是相当陌生的。尽管如此,我还是面临同样的问题,并按如下方式处理。我的方法背后的想法是将如何生成子角色的信息注入相应的父角色。为了确保干净的初始化,我创建了一个工厂方法,用于实例化参与者本身:
object Parent {
def props() :Props {
val childSpawner = {
(context :ActorContext) => context.actorOf(Child.props())
}
Props(classOf[Parent], spawnChild)
}
}
class Parent(childSpawner: (ActorContext) => ActorRef) extends Actor {
val childActor = childSpawner(context)
context.watch(childActor)
def receive = {
// Whatever
}
}
object Child {
def props() = { Props(classOf[Child]) }
}
class Child extends Actor {
// Definition of Child
}
然后你可以像这样测试它:
// This returns a new actor spawning function regarding the FakeChild
object FakeChildSpawner{
def spawn(probe :ActorRef) = {
(context: ActorContext) => {
context.actorOf(Props(new FakeChild(probe)))
}
}
}
// Fake Child forewarding messages to TestProbe
class FakeChild(probeRef :ActorRef) extends Actor {
def receive = {
case msg => probeRef ! (msg)
}
}
"trigger actions of it's children" in {
val probe = TestProbe()
// Replace logic to spawn Child by logic to spawn FakeChild
val actorRef = TestActorRef(
new Parent(FakeChildSpawner.spawn(probe.ref))
)
val expectedForewardedMessage = "expected message to child"
actorRef ! "message to parent"
probe.expectMsg("expected message to child")
}
通过这样做,您可以将父级的繁殖操作提取到一个匿名函数中,该匿名函数可以在测试中被完全掌握在您手中的FakeChild参与者所取代。将来自FakeChild的消息预先通知TestProbe可以解决您的测试问题
我希望这会有所帮助。也许这个解决方案可以帮助任何人解决这个问题 我有一个父actor类,它创建了一些子actor。父参与者的行为类似于转发器,它根据提供的id检查子级是否存在,如果存在,则向其发送消息。在parentactor中,我使用
context.child(actorId)
检查子级是否已经存在。如果我想测试父演员的行为,以及他将向其孩子发送什么,我使用以下代码:
"ParentActor " should " send XXX message to child actor if he receives YYY message" in {
val parentActor = createParentActor(testActor, "child_id")
parentActor ! YYY("test_id")
expectMsg( XXX )
}
def createParentActor(mockedChild: ActorRef, mockedChildId: String): ParentActor = {
TestActorRef( new ParentActor(){
override def preStart(): Unit = {
context.actorOf( Props(new Forwarder(mockedChild)), mockedChildId)
}
} )
}
class Forwarder(target: ActorRef) extends Actor {
def receive = {
case msg => target forward msg
}
}
因此,不建议使用TestActorRef
。这里有一些你可以使用的替代品。其中一个就是去
您需要更改TopActor代码,以便它使用creator函数,而不是直接实例化另一个Actor:
class TopActor(anotherActorMaker: ActorRefFactory ⇒ ActorRef) extends Actor {
val anotherActor = anotherActorMaker(context)
def receive = {
case "call another actor" => anotherActor ! "hello"
}
}
其他参与者应保持不变:
class AnotherActor extends Actor {
override def receive = {
case "hello" => // do some stuff
}
}
现在,在您的测试中,您将使用TestProbe测试应该发送给另一个参与者的消息,即TestProbe将作为TopActors透视图中的另一个操作:
class TopActorSpec extends MyActorTestSuiteTrait {
it should "say hello to AnotherActor when receive 'call another actor'" {
val testProbe = TestProbe()
// test maker function
val maker = (_: ActorRefFactory) ⇒ testProbe.ref
val testTopActor = system.actorOf(Props(new TopActor(maker)))
testProbe.send(testTopActor, "call another actor")
testProbe.expectMsg("hello")
}
}
当然,在实际应用中,我们将使用maker函数,它将为我们提供另一个actor引用,而不是TestProbe:
val maker = (f: ActorRefFactory) ⇒ f.actorOf(Props(new AnotherActor))
val parent = system.actorOf(Props(new TopActor(maker)))
由于这个解决方案没有其他答案,我得到了一张赞成票,我想我会接受我自己的答案:)你的
testTopActor.underlyingActor
将同时拥有另一个和TopActor.anotherActor
。如果您的另一个参与者在其构造函数或任何生命周期函数中没有做任何事情,但在构造函数中有运行的东西,例如网络/数据库连接,则可以(我知道这很糟糕,但只是为了说明这一点)然后,当您创建testTopActor
时,将运行2个这样的操作。也许要小心这样的事情是好的。