是否可以使用Spock存根或模拟SocketChannel?

时间:2016-12-16 13:20:16

标签: java unit-testing spock socketchannel

基本上我有一个Java类,它在套接字通道上执行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,这样我就不必创建一个真正的客户端进行测试。

我也知道它比仅仅存根SocketChannel更复杂:似乎我需要拦截Selector.open()或以某种方式提供自定义系统默认的SelectorProvider。如果我只是存根SocketChannel,当我尝试通过Selection.open()注册通过我的存根获取的选择器时,我得到一个IllegalSelectorException,并且基础AbstractSelectableChannel#register方法很遗憾是最终的。

但是我找不到任何关于如何或是否可以使用Spock Mocks的有用指针,而且看起来它可能是非常普遍的想法,所以这里要问一个很好的问题。有人可以帮忙吗?

2 个答案:

答案 0 :(得分:1)

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

public final SelectableChannel configureBlocking(boolean block) throws IOException { synchronized (regLock) { if (!isOpen()) throw new ClosedChannelException(); if (blocking == block) return this; if (block && haveValidKeys()) throw new IllegalBlockingModeException(); implConfigureBlocking(block); blocking = block; } return this; }

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

问题是如何处理它? 好吧,我要说,首先,尝试使用接口而不是类。 如果不可能,请尽量不要调用最终方法。 如果它仍然不可能你必须查看课堂内部并找出应该嘲笑的内容。 我看到的最后一个选项是进行完整的集成测试:创建两个套接字并连接它们。

答案 1 :(得分:0)

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

因此?FuelEconomy无法直接拦截 - 但它只是调用data(FuelEconomy) dim(cars2010) [1] 1107 14 ,而Selector.open()SocketProvider.provider().openSelector()字段的惰性静态访问器。 (至少在我的例子中,Java 7)

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

详细信息取决于您当时要执行的操作,但如下所示,您可以返回其他类的存根(例如AbstractSelectableChannel)。

工作示例如下。

provider