You are not logged in.

#1 2025-05-18 06:45:25

siya
Member
Registered: 2024-11-20
Posts: 8

Coupling Client and Server Testing?

Hello all.

I am writing a TFTP client and server. It's just for fun and learning. I just (April) finished a university course where we "built" an FTP client, then server. Scare quotes around built, because there was a lot of hand holding. The prof laid out all the modules, provided automated unit tests, and even some pretty detailed descriptions/pseudocode for what each function should do.

Anyways, I want to expand on what I learned, and I wanted to implement something from an RFC for the first time, so I went with TFTP, which is described RFC 1350.

Started by writing the client, doing mostly TDD (occasionally writing a function before its test, but trying to stop) and a very rough first pass is done now. The following unit test names give an idea of what i'm testing for.

.venv[alex@Alex trivial]$ python test.py -v
test_bad_block_number (__main__.AckPacketCreationTests.test_bad_block_number) ... ok
test_ok_block_number (__main__.AckPacketCreationTests.test_ok_block_number) ... ok
test_ack_wrong_source_port (__main__.ClientBehaviourTests.test_ack_wrong_source_port)
Sets up a file transfer from client to server (WRQ). Tests that the ... ok
test_client_ports_random (__main__.ClientBehaviourTests.test_client_ports_random)
Tests that client ports are selected randomly, by making N clients ... ok
test_create_bind (__main__.ClientBehaviourTests.test_create_bind) ... ok
test_read_request (__main__.ClientBehaviourTests.test_read_request)
Tests client sending RRQ and server replying with first packet. ... ok
test_receive (__main__.ClientBehaviourTests.test_receive)
Tests ability to receive packets and send acknowledgement. ... ok
test_send (__main__.ClientBehaviourTests.test_send)
Tests that client can send a multi-block buffer to the server properly ... ok
test_send_file (__main__.ClientBehaviourTests.test_send_file)
Send a *large* multi block file from the client using the proper entry point. ... ok
test_send_no_ack (__main__.ClientBehaviourTests.test_send_no_ack)
The client should return false if, when attempting to send a file, ... ok
test_sent_handle_diskfull_last_packet (__main__.ClientBehaviourTests.test_sent_handle_diskfull_last_packet)
The client should return false if a disk full error is received during transfer of a file ... ok
test_write_request (__main__.ClientBehaviourTests.test_write_request)
Tests client sending WRQ and server replying with ACK. ... ok
test_write_request_timeout (__main__.ClientBehaviourTests.test_write_request_timeout)
Tests triggering a client to timeout by not sending ACK 0 to WRQ ... ok
test_bad_block_number (__main__.DataPacketCreationTests.test_bad_block_number) ... ok
test_encoded_bytes (__main__.DataPacketCreationTests.test_encoded_bytes) ... ok
test_no_data_ok (__main__.DataPacketCreationTests.test_no_data_ok) ... ok
test_ok_sizes (__main__.DataPacketCreationTests.test_ok_sizes) ... ok
test_bad_code (__main__.ErrorPacketCreationTests.test_bad_code) ... ok
test_valid_codes (__main__.ErrorPacketCreationTests.test_valid_codes) ... ok
test_bad_request_mode (__main__.RequestPacketCreationTests.test_bad_request_mode) ... ok
test_bad_request_type (__main__.RequestPacketCreationTests.test_bad_request_type) ... ok
test_good_read_requests (__main__.RequestPacketCreationTests.test_good_read_requests) ... ok
test_good_write_requests (__main__.RequestPacketCreationTests.test_good_write_requests) ... ok

In any case, now that I want to start writing server tests, I've got an interesting choice to make. When writing client tests, I wrote out all the server behaviour the client was to respond to by manually telling the socket what to do, with no server class, like I do here with srv.

    def test_write_request(self):
        """
        Tests client sending WRQ and server replying with ACK.
        """
        client = tftp.Client()
        # Set up socket to stand in for server
        srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        srv.bind(("0.0.0.0", 11111))
        # Make a write request, put it in a thread so it can block for our response
        t = Thread(target=client.request_write, args=["0.0.0.0", "doc.txt"])
        t.start()
        payload, (client_address, client_port) = srv.recvfrom(1024)
        # Verify what was received by the server
        self.assertEqual(tftp.create_connection_packet("w", "doc.txt"), payload)

        # Send the ACK from a different socket
        srv.close()
        srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        srv.bind(("0.0.0.0", 12222))
        srv.sendto(tftp.create_ack_packet(0), (client_address, client_port))

        # Wait for client thread to finish, so we can check state of client
        t.join(0.5)
        self.assertEqual(client.destination_port, 12222)
        srv.close()

(some may notice I am not using port 69 like the RFC says... I can't, though.)

Now that I've got a client, I could use it in the tests I write for the server, and I wouldn't have to write out the client behaviour by fidgeting with a socket representing the code. After that, I could even go back and rewrite the client tests to use the server class.

But my instinct is that that seems like a bad idea even though it would reduce lines of code. If the client tests do not use the server and the server tests do not use the client, they are tested in isolation. And the ugliness and duplication of manually twiddling the opposite socket in each test is a necessary evil. But anyways, maybe I am just digging a hole here. What do you think? And is this coupling that I am considering here or is that misusing the term?


一只小鸭
思量考查

Offline

Board footer

Powered by FluxBB