How to unit test BSD sockets [closed]
Asked Answered
K

1

6

I am writing a server/client based C++ application in Ubuntu with BSD socket. I am using Google C++ Test Framework as my unit test framework.

I wonder is there a way that I can create a server and client in my unit test, so I can test listen/accept for the server, and send/receive for both sides.

The question is, if I am going to test socket accept (after listening to a port) for the server, how can I have some client connecting to it in this test? Can I use multi-threading to have a client connect to the server being tested in the same TEST() (or TEST_F()) scope?

I can write a client and manually connect to the server under test for sure, but it defeat the purpose of automated unit test.

I read something about the Google Mock, it looks more like a sanity check for me (seeing which functions are being called, how many times, what got returned, etc.).

Please help, thanks.

Kalman answered 19/1, 2017 at 23:22 Comment(3)
Create an abstraction for socket and inject it to the class. Then you can mock it.Binder
@BartoszPrzybylski Can you please show me some example? I am new to test driven development, and I am not sure how I can do injection. Thanks a lot.Kalman
Create an abstract base class that represents a TCP connection and have methods to read/write data, etc. Then your production code can derive a class that implements the logic using real sockets, and your test unit can derive another class that implements the logic using fake data. Write the rest of your unit code using the base class interface for everything, and then you can instantiate the appropriate derived class when needed. This way, you don't need to use real sockets during testing unless you are testing real network behavior.Learnt
B
7

Ok, lets start with creating abstraction for network socket. We need this to be able to mock the calls to system functions. Start with something like this:

class Socket {
public:
    virtual bool connect(const struct sockaddr *address, socklen_t address_len) = 0;
    virtual Socket* accept(struct sockaddr *restrict address, socklen_t *restrict address_len) = 0;
    /* more functions */
}

The above code doesn't abstract a lot of stuff and its bind to unix classes, because it is using sockaddr and socklen_t. You should also create abstraction for those two types to have platform independent code, but this depends on your design.

Not you need to create a concrete TCP/UDP class for using it in real application.

class TCPSocket : public Socket { 
public:
    TCPSocket() {
        socket_ = socket(PF_INET, SOCK_STREAM, 0);
        if (socket_ == -1) {
            /* handle errors */
        } 
    }
    TCPSocket(int sock) : socket_(sock) {}
    bool connect(const struct sockaddr *address, socklen_t address_len) override {
        return connect(socket_, address, address_len) == 0;
    }
    Socket* accept(struct sockaddr *restrict address, socklen_t *restrict address_len) override {
        int s = accept(socket_, address, address_len);
        if (s == -1) {
            /* handle errors */
        }
        return new TCPSocket(s);
    }
private:
    int socket_;
} 

Phew :) let's move on to your class.

Lets assume your class is named A and has method for testing method. Your class should either take Socket* in constructor, or method should take Socket* as a parameter. Then in your testing code you can specify a mock.

class MockSock : public Socket {
public:        
    MOCK_METHOD2(connect, bool(const struct sockaddr*, socklen_t));
    MOCK_METHOD2(accept, Socket*(struct sockaddr*, socklen_t*));
}

then just instantiate MockSock and pass it to A or method with proper EXPECT_CALL values.

Binder answered 20/1, 2017 at 8:35 Comment(4)
Thank you very much for your answer, but the thing is when I call accept, I need a client to connect to it, otherwise accept will block the test if there are no timeout or a client connect to it. How can I achieve that using unit test?Kalman
@Kalman you don't need a client to connect to the socket. Thats the whole point of mocking. GMock will do what you will tell him to do, you just need to instruct it to do so. That's why the abstraction is introduce, to avoid calling system functions.Binder
So it means I am not testing the real socket accept function, but other logic inside this subroutine (if I added to the accept), and the system behavior (i.e. whether this function is called when it needed to be, etc.)Kalman
Yes, testing real system function isn't particularly useful, there is too many things that can go wrong which might alter the test result.Binder

© 2022 - 2024 — McMap. All rights reserved.