用Spock测试AKKA应用程序

本文介绍了如何在多语言JVM项目中使用Spock测试AKKA应用程序,特别是测试参与者和AKKA扩展。通过AKKA TestKit,可以方便地与actor交互并编写测试。文章还讨论了模拟AKKA扩展以实现确定性的测试,并展示了如何使用Groovy扩展模块增强ActorSystem的功能。
摘要由CSDN通过智能技术生成

AKKA是基于消息驱动和基于AKKA模型的并发工具包。 尽管AKKA是用Scala编写的, AKKA可以在任何基于JVM的语言项目中使用。 这篇文章试图填补关于在利用AKKA框架的多语言JVM项目中编写好的测试所缺少的信息的空白。 在多语言JVM项目中,我显而易见的测试工具选择是Spock 。 由GroovyJUnit提供支持,此工具使编写测试变得更加有趣。

本文不适用于AKKASpock教程。 假定读者了解GroovySpock基础知识,以及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添加

翻译自: https://www.javacodegeeks.com/2015/11/testing-akka-application-with-spock.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值