[英]Mock a "blocking" method call with Spock?
I'm learning to use Spock for unit testing and I've come across an issue I can't seem to get my head around:我正在学习使用Spock进行单元测试,但遇到了一个我似乎无法理解的问题:
Note: This example is very simplified, but it gets the idea of what I'd like to achieve across.
注意:这个例子非常简化,但它了解我想要实现的目标。
I have a class (call it Listener
) which accepts a java.net.ServerSocket
as a constructor parameter;我有一个类(称为
Listener
),它接受java.net.ServerSocket
作为构造函数参数; it has a startListening
method, which spawns a new thread which does the following (heavily reduced for brevity):它有一个
startListening
方法,它产生一个新线程,它执行以下操作(为简洁起见,大量减少):
while(listening) {
try {
Socket socket = serverSocket.accept();
doSomethingWithSocket(socket);
} catch(IOException ex) {
ex.printStackTrace();
listening = false;
}
}
In normal operation, the serverSocket.accept()
call blocks until a connection is made to the ServerSocket
.在正常操作中,
serverSocket.accept()
调用会阻塞,直到与ServerSocket
建立连接。
I'd like to be able to test the interactions on the Socket
returned by serverSocket.accept()
.我希望能够测试
serverSocket.accept()
返回的Socket
上的交互。 I can do this with Spock in the following way:我可以通过以下方式使用 Spock 做到这一点:
given: "A ServerSocket, Socket, and Listener"
def serverSocket = Mock(ServerSocket)
def socket = Mock(Socket)
serverSocket.accept() >> socket
def listener = new Listener(serverSocket)
when: "Listener begins listening"
listener.startListening()
then: "Something should be done with the socket"
// Verify some behavior on socket
At first glance, this works fine, except that every call to serverSocket.accept()
will return the mocked Socket
.乍一看,这很好用,只是每次调用
serverSocket.accept()
都会返回serverSocket.accept()
的Socket
。 Since this call is (intentionally) being invoked an indefinite number of times (because I want to accept an indefinite number of inbound connections) all of the interactions on the mock Socket
occur an indefinite number of times (depending on how fast the machine is, how long it takes to run, etc...)由于此调用(有意)被无限次调用(因为我想接受无限数量的入站连接),因此模拟
Socket
上的所有交互都发生了无限次(取决于机器的速度,运行需要多长时间,等等...)
I could use the cardinality of the interaction to specify at least one interaction, like so:我可以使用交互的基数来指定至少一个交互,如下所示:
1.._ * socket.someMethod()
But something about that rubs me the wrong way;但是这件事让我觉得不妥; I'm not really looking for at least one interaction, I'm really looking for one interaction.
我不是真的在寻找至少一种互动,我真的在寻找一种互动。
null
null
I could do something like this (to return the Mocked socket once and then null):我可以做这样的事情(返回模拟套接字一次然后为空):
serverSocket.accept() >>> [socket, null]
But then I still have tons of calls to doSomethingWithSocket
that pass a null
parameter, which I then have to check for and ignore (or report).但是后来我仍然有大量对
doSomethingWithSocket
的调用传递了一个null
参数,然后我必须检查并忽略(或报告)。 If I ignore it, I might miss reporting a legitimate issue (I don't think ServerSocket#accept
can ever return null
, but since the class isn't final maybe someone implements their own version which can ) but if I report it, my test logs get polluted with the log message that's reporting an expected outcome as an unexpected one.如果我忽略它,我可能会错过报告一个合法的问题(我不认为
ServerSocket#accept
可以返回null
,但由于该类不是最终的,也许有人实现了他们自己的版本可以)但是如果我报告它,我的测试日志被将预期结果报告为意外结果的日志消息所污染。
I'm admittedly not a Groovy programmer by trade and this is the first time I've worked with Spock, so my apologies if this is a just plain wrong thing to do or if I'm misunderstanding something about how Spock does mocking诚然,我不是一个 Groovy 程序员,这是我第一次与 Spock 合作,所以如果这是一个完全错误的做法,或者我误解了 Spock 是如何嘲笑的,我深表歉意
I tried this:我试过这个:
serverSocket.accept() >> socket >> {while(true) {}; null }
This loops forever before the accept
method is even called;在调用
accept
方法之前,这将永远循环; I'm not 100% sure why, as I didn't think the closure would be evaluated until the accept method was called a second time?我不是 100% 确定为什么,因为我认为在第二次调用 accept 方法之前不会评估闭包?
I also tried this:我也试过这个:
serverSocket.accept() >>> [socket, { while(true){}; null }]
It's my understanding that when the accept
method is called the first time, socket
will be returned.我的理解是当第一次调用
accept
方法时,会返回socket
。 Further calls will invoke the closure, which loops infinitely and should therefore block.进一步的调用将调用闭包,它无限循环,因此应该阻塞。
Locally, this appears to work, but when the build is run by a CI service (specifically, Travis CI) I still see test output indicating that the accept
method is retuning null
, which is a little bit confusing.在本地,这似乎有效,但是当构建由 CI 服务(特别是 Travis CI)运行时,我仍然看到测试输出表明
accept
方法正在重新调整null
,这有点令人困惑。
Am I just trying to do something that can't be done?我只是想做一些无法完成的事情吗? This isn't a deal breaker for me or anything (I can live with noisy test logs) but I'd really like to know if this is possible.
这对我或任何事情都不是一个交易破坏者(我可以忍受嘈杂的测试日志),但我真的很想知道这是否可能。
It's not the blocking itself I'm attempting to verify;我试图验证的不是阻塞本身; I'd like to verify one behavior per feature method.
我想验证每个功能方法的一种行为。 I could use the technique provided by David W below to test many behaviors in a single method, but some behaviors change depending on the number of accepted connections, whether an exception has been encountered, whether the
Listener
has been told to stop accepting connections, etc... which would make that single feature method much more complicated and difficult to troubleshoot and document.我可以使用下面 David W 提供的技术在单个方法中测试许多行为,但某些行为会根据接受的连接数、是否遇到异常、是否已告知
Listener
停止接受连接等而改变...这将使该单一特征方法更加复杂且难以进行故障排除和记录。
If I can return a defined number of Socket
mocks from the accept
method and then make the method block, I can verify all of these behaviors individually and deterministically, one per feature method.如果我可以从
accept
方法返回定义数量的Socket
模拟,然后使方法块,我可以单独和确定性地验证所有这些行为,每个功能方法一个。
A unit test should only test one class no dependencies on other classes.单元测试应该只测试一个类,而不依赖于其他类。 From the code you pasted, you only need to verify two things
从你粘贴的代码来看,你只需要验证两件事
Let's say the doSomething is just passed to a delegate class DoSomethingDelegate假设 doSomething 只是传递给委托类 DoSomethingDelegate
setup:
def delegate = Mock(DoSomethingDelegate)
List actualExceptions = [null,null,new IOException()]
int index = 0
// other setup as in your question
when:
new Listener(serverSocket).startListening()
then:
noExceptionThrown()
3 * delegate.doSomethingWithSocket(socket) {
if(actualExceptions[index]) {
throw actualExceptions[index]
}
index++
}
This will verify that all lines and conditions of the sample code you provided are tested.这将验证您提供的示例代码的所有行和条件都经过测试。 I used the delegate because without the other code in the class, I can't see what other conditions (different mock sockets are required)
我使用委托是因为没有类中的其他代码,我看不到其他条件(需要不同的模拟套接字)
You could do another test to test the behavior different sockets.您可以进行另一项测试来测试不同套接字的行为。
setup:
List sockets = []
sockets << Mock(Socket)
sockets << Mock(Socket)
// repeat as needed
socket[3].isClosed() >> {Thread.sleep(1000);false}
serverSocket.accept() >> {sockets[socketNumber]}
// add different mock behavior
// when block
// then
where:
socketNumber << [1,2,3]
If you need a last socket to hold the main thread, you can make it sleep as I have done above.如果你需要一个最后一个套接字来保存主线程,你可以像我上面所做的那样让它休眠。 You can add more complex behavior by making the method calls interact with each other.
您可以通过使方法调用相互交互来添加更复杂的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.