My current approach is to mock socket.socket
, listen and run my code for one connection, then stop the service.
But socket.socket.accept()
is a blocking method.
I tried to simulate socket.socket.accept()
by returning a (socket, address)
mockup tuple on the first call, then raise a socket.error
Exception
, so I can break the loop on my app that invokes socket.socket.accept()
Oddly enough all my unit tests pass but py.test
process remains active (doing what?? The debugger is not helping: It doesn't stop on any breakpoints after the second call that exits the loop as expected...) hogging resources on an endless loop until it crashes the whole system.
So, what's the right approach here?
TcpService.py
:
import socket
import threading
class TcpService:
_bind_ip = '127.0.0.1'
_bind_port = 0
_max_tcp_conn = 20
def _handle_client_connection(self):
pass
def listen_tcp(self):
self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._server.bind((self._bind_ip, self._tcp_bind_port))
self._server.listen(self._max_tcp_conn)
self._tcp_listen = True
while self._tcp_listen:
client_sock, address = self._server.accept() # socket.error is supposed to be raised on the second iteration during the unit test, breaking the loop
client_handler = threading.Thread(
target=self._handle_client_connection,
args=(client_sock, address)
)
client_handler.start()
test_TcpService.py
:
import unittest
import socket
from time import sleep
import mock
import TcpService
def accept_gen():
for i in range(1):
mock_socket = mock.MagicMock(name='socket.socket', spec=socket.socket)
sleep(1)
yield (mock_socket, ['0.0.0.0', 1234])
while True:
sleep(1) # so I have a chance to kill the process before the OS becomes unresponsive
yield socket.error()
class test_TcpService(unittest.TestCase):
@mock.patch('socket.socket', autospec=True)
def test_listen_tcp(self, mock_socket):
mocked_socket = mock_socket.return_value
mocked_socket.accept.side_effect = accept_gen()
sts = TcpService()
with self.assertRaises(socket.error):
sts.listen_tcp()
mock_socket.assert_called_once() # trivial POC check, final test would be more thorough...
sts.close_tcp()
[EDIT]: The test was OK. The issue was that the serving threads were non-daemonic, so the parent Thread was waiting indefinitely for them to end before shutting down the listening socket. Just setting the client_handler
Thread as a daemon before starting it fixes it:
client_handler = threading.Thread(
target=self._handle_client_connection,
args=(client_sock, address)
)
client_handler.setDaemon(True) # There. Fixed.
client_handler.start()
Before realizing that, the best approach I found was: Do not mock the socket and use localhost and port 0 so you can deploy and connect to your server on a random available local port on any environment you end up running your test suite:
Python: unit testing socket-based code?
But, if the socket is not mocked and actually establishing a TCP connection is required, I would argue that it is not a unit test any more, but an integration test, and the results can depend on the sockets library implementation and the underlying platform...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.