有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java可以用Spock存根或模拟SocketChannel吗?

本质上,我有一个Java类,它在socket通道上执行select,我想存根该通道,以便测试select是否按预期工作

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

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,这样我就不必创建真正的客户机进行测试

我也知道这比简单的stub SocketChannel更复杂:似乎我需要拦截Selector.open()或者以某种方式提供一个自定义的系统默认选择器提供程序。如果我只是stub SocketChannel,当我试图将通过Selection.open()获得的选择器注册到我的stub时,我会得到一个非法的SelectorException,不幸的是,base AbstractSelectableChannel#register方法是最终的

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


共 (1) 个答案

  1. # 1 楼答案

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

    所以Selector.open()不能直接被拦截,但它只是调用SocketProvider.provider().openSelector(),而SocketProvider.provider()SocketProvider.provider字段的惰性静态访问器。(至少在我的例子中是Java 7)

    因此,我们可以简单地设置这个provider字段,即使它是私有的,因为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()",
            ]
        }
    }