Unit testing a TCP server and client can be tricky at first. Let me share what I've come up with for unit testing them. I will not provide full test class code just invidual test methods. If you don't know how to use unittest with Python and get your tests running refer to the unittest documentation. GameServer.Server and GameClient.Client are the classes that are being tested and which should be implemented elsewhere.
Testing the server
To test the server with a real connection we need to put the server in the background. The thread library in Python makes this easy. Start the real server in a thread and then attempt a connection with a fake client. We need a short sleep after starting the server thread to make sure it has time to bind and listen. Setting a low timeout on the client helps ensure the test will run fast. In this case the server binds locally so a low timeout is just fine. Joining the server thread at the end lets us make sure the server thread is finished. If the server thread does not finish then it is possible for the unit tests to hang. In this example, the server will just close the connection after accepting it. That will be enough to let us know it is working.
def test_game_server_starts_tcp_server(self):
# Start game server in a background thread
game = GameServer.Server('127.0.0.1', 7777)
server_thread = threading.Thread(target=game.start_listening)
server_thread.start()
# On my computer, 0.0000001 is the minimum sleep time or the
# client might connect before server thread binds and listens
# Other computers will differ. I wanted a low number to make tests fast
time.sleep(0.000001)
# This is our fake test client that is just going to attempt a connect and disconnect
fake_client = socket.socket()
fake_client.settimeout(1)
fake_client.connect(('127.0.0.1', 7777))
fake_client.close()
# Make sure server thread finishes
server_thread.join()
Example Real Server
This is a short example of the real server code that we can test with the code above.
class Server:
def __init__(self, host, port):
self.host = host
self.port = port
def handle_client(self, client):
# Server will just close the connection after it opens it
client.close()
return
def start_listening(self):
sock = socket.socket()
sock.bind((self.host, self.port))
sock.listen(5)
client, addr = sock.accept()
client_handler = threading.Thread(target=self.handle_client,args=(client,))
client_handler.start()
Testing the client
Testing the client will require a fake server. The fake server will simply listen for a connection and then close it. It will run in a thread in the background so the real client can be tested.
def run_fake_server(self):
# Run a server to listen for a connection and then close it
server_sock = socket.socket()
server_sock.bind(('127.0.0.1', 7777))
server_sock.listen(0)
server_sock.accept()
server_sock.close()
def test_game_client_connects_and_disconnects_to_default_server(self):
# Start fake server in background thread
server_thread = threading.Thread(target=self.run_fake_server)
server_thread.start()
# Test the clients basic connection and disconnection
game_client = GameClient.Client()
game_client.connect('127.0.0.1', 7777)
game_client.disconnect()
# Ensure server thread ends
server_thread.join()
Things to Consider
- Other tests can be made to ensure the client and server accept non-standard host and port configurations. Those tests will not need to test the connections just test that the host and port variables are set before starting the connection.
- Expanding functionality - In this example some people might consider it unnecessary to test that the built in Python TCP classes work as they should because all it does is connect and disconnect, but once your client and server start sending handshakes and data, then the unit tests will need to have a good fake server/client to use. This is the foundation. You might want to break out the fake server and fake client in to their own clases for easy re-use if you need them in a lot of tests.
- An alternative to testing that the sockets are actually functioning is just to assert that the socket functions bind, listen, and accept are called in a mocked server.