当前位置: 首页 > 知识库问答 >
问题:

是否有可能存根或模拟SocketChannel与Spock?

陆高峰
2023-03-14

本质上,我有一个Java类,它在套接字通道上执行选择,我想存根通道,这样我就可以按预期测试选择工作。

例如,这大致就是被测试的类所做的:

class TestedClass {
    TestedClass(SocketChannel socket) { this.socket = socket }

    // ...
    SocketChannel socket;
    // ...
    void foo() {
        // Wait a while for far end to close as it will report an error 
        // if we do it.  But don't wait forever! 
        // A -1 read means the socket was closed by the other end.
        // If we select on a read, we can check that, or time out 
        // after a reasonable delay.

        Selector selector = Selector.open();
        socket.configureBlocking(false);
        socket.register(selector, SelectionKey.OP_READ);
        while(selector.select(1000) == 0) {
            Log.debug("waiting for far end to close socket...")
        }

        ByteBuffer buffer = ByteBuffer.allocate(1);
        if (socket.read(buffer) >= 0) {
            Log.debug("far end didn't close");
            // The far end didn't close the connection, as we hoped
            abort(AbortCause.ServerClosed);
        }

        Log.debug("far end closed");
    }
}

我希望能够测试这样的东西:

def "test we don't shut prematurely" () {
    when:
    boolean wasClosedPrematurely
    SocketChannel socket = Stub(@SocketChannel) {
        // insert stub methods here ....
    }

    TestedClass tc = new TestedClass(socket)
    tc.foo();

    then:
    wasClosedPrematurely == false
}

这是基于一个真实的例子,但细节并不重要。总体目标是如何存根支持选择的SocketChannel,这样我就不必创建真正的客户机进行测试。

我也知道这比简单地截取SocketChannel更复杂:似乎我需要截取选择器。open()或以某种方式提供自定义系统默认选择器或提供程序。如果我只是存根SocketChannel,当我试图注册通过选择获得的选择器时,我会得到一个非法的SelectorException。用我的存根打开(),而baseAbstractSelectableChannel#register方法不幸是最终的。

但是我找不到任何有用的建议来说明Spock Mocks是如何或是否可能实现这一点的,而且这似乎是一件很常见的事情,所以在这里问一个好问题。有人能帮忙吗?

共有2个答案

屈翰飞
2023-03-14

我想我可能已经找到了我自己问题的答案。

所以选择器。open()。提供者()。openSelector(),和SocketProvider。provider()SocketProvider的惰性静态访问器。提供者字段。(至少在我的例子中是Java 7)

因此,我们可以简单地设置这个提供者字段,即使它是私有的,因为Groovy可以忽略正常的Java可见性限制。一旦设置为我们自己的存根实例,以后所有的Selector.open()调用都将使用它(有一个明显的警告,这是一个全局更改,可能会影响其他未测试的代码)。

细节取决于您当时想要做什么,但如下所示,您可以返回其他类的存根,例如AbstractSelectableChannel。

工作示例如下。

class SocketStubSpec extends Specification {

    SocketChannel makeSocketChannel(List events) {
        // Insert our stub SelectorProvider which stubs everything else
        // required, and records what happened in the events list.
        SelectorProvider.provider = Stub(SelectorProvider) {
            openSelector() >> {
                Map<SelectionKey, AbstractSelectableChannel> keys = [:]

                return Stub(AbstractSelector) {
                    register(_,_,_) >> { AbstractSelectableChannel c, int ops, Object att ->
                        events << "register($c, $ops, $att)"
                        SelectionKey key = Stub(SelectionKey) {
                            readyOps() >> { events << "readyOps()"; ops }
                            _ >> { throw new Exception() }
                        }
                        keys[key] = c
                        return key
                    }
                    select() >> {
                        events << "select()"
                        return keys.size()
                    }
                    selectedKeys() >> { keys.keySet() }
                    _ >> { throw new Exception() }
                }
            }
            _ >> { throw new Exception() }
        }

        return Stub(SocketChannel) {
            implConfigureBlocking(_ as Boolean) >> {  boolean state -> events << "implConfigureBlocking($state)" }
            _ >> { throw new Exception() }
        }
    }

    def "example of SocketChannel stub with Selector" () {
        given:
        List events = []

        // Create a stub socket
        SocketChannel channel = makeSocketChannel(events)

        Selector selector = Selector.open()
        channel.configureBlocking(false);
        SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

        expect:
        selector.select() == 1 // our implementation doesn't block
        List keys = selector.selectedKeys().asList()

        keys == [key] // we have the right key
        key.isReadable() // key is readable, etc.

        // Things happened in the right order
        events == [
            "implConfigureBlocking(false)",
            "register(Mock for type 'SocketChannel', 1, null)",
            "select()",
            "readyOps()",
        ]
    }
}
倪灿
2023-03-14

Spock使用CGLIB模拟/存根/间谍类。CGLIB不能覆盖final方法。SocketCannel有很多最终方法(例如configureBlocking),但是CGLIB不会失败,而是使用原始方法。由于configureBlocking是最终版本,因此将在测试中使用它。

public final SelectableChannel configureBlocking(布尔块)抛出IOException{synchronized(regLock){if(!isOpen())抛出新的ClosedChannelException();if(blocking==block)返回此值;if(block)

所以configureBlocking需要初始化regLock变量,但是当你为这个类创建存根时,这个变量没有初始化,你在这里得到了NPE。

问题是如何处理它?嗯,我会说,首先,试着使用接口,而不是类。如果不可能,试着不要调用最终方法。如果仍然不可能,你必须看看类的内部,找出什么应该被嘲笑。我看到的最后一个选择是做一个完全集成的测试:创建两个套接字并将它们连接起来。

 类似资料:
  • 我在micronaut中有以下接口来执行HTTP POST请求: 我有一个调用接口的类: 我想在我的spock测试中模拟/存根API调用,我尝试了以下方法: 然而,我得到的错误:

  • 问题内容: 那是测试的方法 我必须模拟三个CacheContext,Method和CacheEnable。有什么想法可以使测试用例更加简单吗? 问题答案: Mockito 可以处理链接的存根: AFAIK,链中的第一个方法返回一个模拟,该模拟被设置为在第二个链式方法调用中返回您的值。 Mockito的作者注意到,这 仅应用于遗留代码 。否则,更好的方法是将行为推送到CacheContext并提供完

  • 问题内容: 拿这个对象: 如果我这样做: 然后y将返回。通过stringify传递函数有什么能做的吗?使用“ ye goode olde eval()”可以创建具有附加功能的对象,但是打包该对象又是什么呢? 问题答案: 您不能打包函数,因为任何序列化程序都看不到函数关闭的数据。甚至Mozilla 也无法正确打包闭包。 最好的选择是使用复活器和替换器。 https://yuilibrary.com/

  • 我有一个复杂的变量在我的自定义块模板中。是当前网站的语言,但内容仅以当时的语言交付,当缓存建造。 我的渲染数组中确实有语言,它适用于twig模板中的命令: 有没有办法让Drupal根据页面的当前语言处理多个缓存条目? 太多了<安德烈亚斯

  • 虽然这个问题已经得到了回答,但我仍然不清楚在嘲弄中应该使用哪一个 当参考。我看不出它们之间有什么区别。 留档为是说 的文档中说 的留档是说 这清楚地表明,这两者之间没有区别。那么,我们为什么要采用这三种嘲弄策略,以及在当时和何时使用它们之间的具体区别。 如果它是一个带有示例代码的答案,那将非常有帮助。

  • 问题内容: 我需要运行python脚本,并确保它在终止后将重新启动。我知道有一个称为supervisor的UNIX解决方案。但是不幸的是,必须运行我的脚本的服务器在Windows上。您知道哪种工具有用吗?谢谢 问题答案: 尽管这里有大量的免责声明,但是您可以在Windows中使用Cygwin运行Supervisor 。事实证明,Cygwin在模拟Posix环境方面走了很长的路要走,以至于实际上Su