AKKA
是基于消息驱动和基于AKKA
模型的并发工具包。 尽管AKKA
是用Scala
编写的, AKKA
可以在任何基于JVM
的语言项目中使用。 这篇文章试图填补关于在利用AKKA
框架的多语言JVM项目中编写好的测试所缺少的信息的空白。 在多语言JVM项目中,我显而易见的测试工具选择是Spock
。 由Groovy
和JUnit
提供支持,此工具使编写测试变得更加有趣。
本文不适用于AKKA
或Spock
教程。 假定读者了解Groovy
和Spock
基础知识,以及actor模型并发性的基础知识。
使用AKKA TestKit框架来测试参与者
为了我们的目的,让我们创建一个简单的actor,该actor接收消息,为消息加上Hello
前缀,并将结果发送回原始发送者。
HelloActor.java
public class HelloActor extends UntypedActor {
@Override
public void onReceive(Object message) throws Exception {
sender().tell("Hello " + Objects.toString(message.toString()), self());
}
}
即使从非Scala
项目开始,测试AKKA演员也非常简单。 感谢Testing Actor Systems中描述的出色的TestKit
框架。 可以如下所示编写简单的测试。
HelloActorTest.groovy
class HelloActorTest extends Specification {
@AutoCleanup("shutdown") (1)
def actorSystem = ActorSystem.create()
def probe = new JavaTestKit(actorSystem) (2)
def "actor should say hello"() {
given:
def helloActor = actorSystem.actorOf(Props.create(HelloActor))
when:
helloActor.tell("world", probe.ref) (3)
then:
probe.expectMsgEquals("Hello world") (4)
}
}
(1)注释告诉Spock
测试结束后要清理变量,调用提到的方法,即shutdown
(2) JavaTestKit
是TestKit框架的核心,提供用于与参与者交互的工具
(3)发送world
字符串作为actor的消息,将JavaTestKit
实例作为消息发送者传递 (4)断言probe
收到了正确的消息,即以Hello
测试AKKA扩展
AKKA扩展是一种轻量级且功能强大的方法,可通过项目特定的功能扩展AKKA核心功能。 让我们通过使用任意问候代替硬编码的Hello
来增强我们的系统。 为此,我们可以使用公开的单个方法来创建名为GreetExtension
AKKA扩展。 调用该方法将返回预定义列表中的随机问候语 。
GreetExtension.java
public class GreetExtension implements Extension {
public static final ExtensionKey<GreetExtension> KEY = new ExtensionKey<GreetExtension>(GreetExtension.class) {}; (1)
private final Random random;
private final ExtendedActorSystem actorSystem;
public GreetExtension(ExtendedActorSystem actorSystem) {
this.actorSystem = actorSystem;
this.random = new Random();
}
public static final List<String> GREET_WORDS = Arrays.asList("Hello", "Nice to meet you", "What's up");
public String greetWord() {
return GREET_WORDS.get(random.nextInt(GREET_WORDS.size())); (2)
}
}
(1)唯一标识符,允许从ActorSystem
实例获取扩展
(2)随机选择任何可用的问候语
对于AKKA扩展使用的插图,让我们创建的修改版本HelloActor
-命名GreetExtensionActor
。 通过使用GreetExtension
生成响应,其行为将与原始行为不同。 Actor会要求问候语扩展名,在原始消息之前加上前缀,然后回复消息的发件人。
GreetExtensionActor.groovy
public class GreetExtensionActor extends UntypedActor {
@Override
public void onReceive(Object message) throws Exception {
GreetExtension greetExtension = GreetExtension.KEY.get(context().system()); (1)
sender().tell(greetExtension.greetWord() + " " + Objects.toString(message), self());
}
}
(1)通过标识符获取AKKA扩展名
使用AKKA TestKit测试AKKA扩展感知演员
我们可以修改HelloActorTest.java
测试套件GreetExtensionActor
以这样的方式。
GreetExtensionActorTest.groovy
def "actor should greet via AKKA extension"() {
given:
def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor))
when:
helloActor.tell("world", probe.ref)
then:
def msg = probe.expectMsgClass(String)
msg.endsWith("world") && GreetExtension.GREET_WORDS.any { msg.startsWith(it) } (1)
}
(1)由于前缀是随机生成的-我们无法检查完全匹配,而是检查响应消息是否以可能的值之一作为前缀
模拟AKKA扩展
上面测试用例的明显缺点是依赖于GreetExtension
后者的行为是不确定的。 GreetExtensionActor
不能单独进行测试,也不能使用一组定义的输入/输出值进行测试。 为了克服这个问题,最明显的选择是使用模拟GreetExtension
模拟注入到actor系统中。 Spock
本身提供了AKKA
和存根功能,但不幸的是, AKKA
没有提供用存根实例替换AKKA扩展的API。 幸运的是,由于Groovy
性质,可以访问ActorSystem
私有成员。 使用此技巧,我们可以用存根手动替换AKKA扩展实例,并能够编写具有定义的输入/输出的测试用例。
GreetExtensionActorTest.groovy
def "actor should greet via mocked AKKA extension"() {
given:
def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor))
and:
GreetExtension.KEY.get(actorSystem)
actorSystem.extensions[GreetExtension.KEY] = Stub(GreetExtension) { (1)
greetWord() >> "Bye"
}
when:
helloActor.tell("world", probe.ref)
then:
probe.expectMsgClass(String) == "Bye world"
}
(1) 魔术 ,访问actor系统内部,使用扩展存根调整其值
使用Groovy扩展模块扩展Actor系统功能
查看先前的测试,可以检测到这段代码,这些代码可能会在测试用例之间重复。 该代码用于用模拟代替实际的AKKA扩展。
GreetExtension.KEY.get(actorSystem)
actorSystem.extensions[GreetExtension.KEY] = Stub(GreetExtension) {
greetWord() >> "Bye"
}
如果我们可以将其提取为实用程序方法,然后在需要的地方使用它,那将是很好的。 一种可能性是使用Groovy
特征并将特征混合到每个Spock
规范类中。 似乎不太冗长的另一个选择是能够使用可以完成此工作的新方法来增强ActorSystem
。 幸运的是, Groovy
有一种使用扩展模块来做到这一点的方法。
我们可以在运行时将方法添加到仅对测试类可见的任何类,而不会影响生产代码。 要启用它,我们必须将名为org.codehaus.groovy.runtime.ExtensionModule
文件放入test/resources/META-INF/services
文件夹中。
org.codehaus.groovy.runtime.ExtensionModule
moduleName = akka-spock-module
moduleVersion = 1.0
extensionClasses = ua.eshepelyuk.blog.ActorSystemExtensionModule
然后,我们准备实现扩展模块功能。
ActorSystemExtensionModule.groovy
class ActorSystemExtensionModule {
static <T extends Extension> void mockAkkaExtension(ActorSystem actorSystem, ExtensionId<T> extId, T mock) {
extId.get(actorSystem)
actorSystem.extensions[extId] = mock
}
}
因此,拥有ActorSystem
加强与mockAkkaExtension
方法,我们终于可以重写测试情况如下。
GreetExtensionActorTest.groovy
def "actor should greet with mocked AKKA extension, using Groovy extension module"() {
given:
def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor))
and:
actorSystem.mockAkkaExtension(GreetExtension.KEY, Stub(GreetExtension) { (1)
greetWord() >> "Bye cruel"
})
when:
helloActor.tell("world", probe.ref)
then:
probe.expectMsgClass(String) == "Bye cruel world"
}
(1)在ActorSystem
实例上调用方法,该方法在Scala
代码中不存在,由我们的ActorSystemExtensionModule
添加
- 完整的项目代码可在My GitHub上获得
翻译自: https://www.javacodegeeks.com/2015/11/testing-akka-application-with-spock.html