TDD, Unit Test and architectural changes

I am writing RPC middleware in C ++. I have a class called RPCClientProxy that contains an internal socket client:

class RPCClientProxy { ... private: Socket* pSocket; ... } 

Constructor:

 RPCClientProxy::RPCClientProxy(host, port) { pSocket = new Socket(host, port); } 

As you can see, I do not need to tell the user that I have a socket inside.

Although to conduct unit tests for my proxies it would be necessary to create mocks for sockets and pass them to proxies, and for this I have to use setter or pass factory to sockets in proxy constructors.

My question is: according to TDD, is it acceptable to do it ONLY because the tests? As you can see, these changes will change the way the programmer uses the library.

+4
source share
4 answers

What you are describing is a completely normal situation, and there are installed templates that can help you implement your tests in a way that does not affect your production code.

One way to solve this problem is to use Test Specific Subclass , where you can add an installer for the socket member and use the socket layout in case of a test. Of course, you would need to make the variable secure, not private, but probably no big one. For instance:

 class RPCClientProxy { ... protected: Socket* pSocket; ... }; class TestableClientProxy : public RPCClientProxy { TestableClientProxy(Socket *pSocket) { this->pSocket = pSocket; } }; void SomeTest() { MockSocket *pMockSocket = new MockSocket(); // or however you do this in your world. TestableClientProxy proxy(pMockSocket); .... assert pMockSocket->foo; } 

In the end, it comes down to the fact that you often (most often not in C ++) should design your code in such a way that it is accessible for verification, and there is nothing wrong with that. If you can avoid these decisions flowing in public interfaces, which may be better sometimes, but in other cases it is better to choose, for example, nesting dependencies through the constructor options above, say, using singleton to provide access to a specific instance.

Side note. Itโ€™s probably worth taking a look at the rest of xunitpatterns.com website: there is a whole load of well-established unit tests to understand and, I hope, you can get knowledge about those who were there before you :)

+3
source

I donโ€™t adhere to a specific canon, I would say if you think testing through a mock socket is useful to you, do it, you can implement a parallel constructor

 RPCClientProxy::RPCClientProxy(Socket* socket) { pSocket = socket } 

Another option is to implement a host for connecting to testing, which you can configure to wait for specific messages

+3
source

Your problem is more of a design problem.

If you ever had to perform different behavior for Socket , you were fried since it involves rewriting all the code that created the sockets.

The usual idea is to use the abstract base class (interface) of the Socket , and then use the abstract Factory to create the desired socket, depending on the circumstances. Factory itself can either be Singleton (although I prefer Monoid), or passed as arguments (according to the Injection Dependency tenants). Note that the latter means that there is no global variable, which, of course, is much better for testing.

So I would advise something like:

 int main(int argc, char* argv[]) { SocketsFactoryMock sf; std::string host, port; // initialize them std::unique_ptr<Socket> socket = sf.create(host,port); RPCClientProxy rpc(socket); } 

This affects the client: you no longer hide the fact that you are using sockets behind the scenes. On the other hand, it gives control to the client, who may wish to develop some user sockets (for registration, triggering actions, etc.).

So this is a design change, but it is not caused by TDD itself. TDD simply takes advantage of a higher degree of control.

Also note the explicit ownership of the resource expressed by using unique_ptr .

+3
source

As others noted, in this situation are good options for a factory architecture or a test-specific subclass. For completeness, another possibility is to use the default argument:

 RGCClientProxy::RPCClientProxy(Socket *socket = NULL) { if(socket == NULL) { socket = new Socket(); } //... } 

This is probably somewhere between the factory paradigm (which is ultimately the most flexible, but more painful for the user) and creating a socket inside your constructor. This has the advantage that the existing client code does not need to be changed.

0
source

Source: https://habr.com/ru/post/1310863/


All Articles