3
\$\begingroup\$

I want to create a class ClientSocket with PHP, which is an adapter of the fsockopen, fread, and fwrite.

Class ClientSocket extends SenderAdapterAbstract
{
    private $s = null;

    public function __construct($host, $port)
    {
        $this->s = fsockopen($host, $port);
    }

    // Abstract method concrete implementation
    public function send($message)
    {
        fwrite($this->s, $message);

        $result = fgets($this->s);
        fclose($this->s);
    }
}

Should I test this class? A unit test is not really one if it talks to the network.

Here is some rules about unit testing (I totally agree with them):

A test is not a unit test if:

  • It talks to the database
  • It communicates across the network
  • It touches the file system
  • It can't run at the same time as any of your other unit tests
  • You have to do special things to your environment (such as editing config files) to run it.
\$\endgroup\$
  • \$\begingroup\$ As explained here: stackoverflow.com/questions/21021916/… I don't have to UNIT TEST these kind of adapter pattern... Maybe "integration tests" but not in the same test suite \$\endgroup\$ – nemenems Jan 14 '14 at 13:44
3
\$\begingroup\$

By definition adapters implement an interface defined by you, in order to prevent strong coupling between dependencies like frameworks, drivers, etc... and the other part of the code. The adapter is strong coupled to those dependencies, but the other part of the code, which uses the adapter, is strong coupled only to the interface the adapter is implementing...

Unit testing means mocking out all the dependencies of a class and testing only the code the class contains, but that assumes that the class is loose coupled to its dependencies. By adapter pattern this is not the case, so you wont write unit tests for adapters. You can write integrations tests, which means you test if the adapter works well with its dependencies. Making integration tests is similar to unit tests except of mocking out all the dependencies. You can set up a scenario and define assertions. That's all. Be aware that integration tests are much slower than unit tests, so they are not for frequent usage.

Your code is confusing, you set value of a result variable, but you don't return it, so I don't know what to test. If you want to test file write, you have to read the file you wrote in. If you want to test file read, you can read the content of a manually created file and compare the result what you wrote in the file by hand. If you want to check persistence, you have to clear your storage, save some content in it, read the content of your storage and compare with what you just saved. So writing integration tests is not hard, you can learn it in 10 minutes...

\$\endgroup\$
  • \$\begingroup\$ Yep I know integration testing. But It wasn't clear for me while practicing TDD and adding an adapter \$\endgroup\$ – nemenems Mar 25 '14 at 9:17
3
\$\begingroup\$

a unit test is not really one if it talks to the network...

I am not so sure what you meant by that. It seems like you are trying to follow exactly what someone may have said in a book or a talk. Unit testing is all about testing a 'Model'. And the assumptions are that the Model is isolated from any other models in the overall project.

In your case the Unit Test for the ClientSocket would be called ClientSocketTest.

Class ClientSocketTest extends PHPUnit_Testcase
{
    private $s = null;

    public function testSend()
    {
        $cs = new ClientSocket();
        $cs->send("My Message");
        $result = $cs->result;
        $this->assert("Everything's Good", $result);
    }
}

I noticed that your send method has the variable $result. For Unit Testing to be successful a method should return something. The purpose of Unit Testing is to see whether the given input gives you the expected output. Don't overcomplicate it!

Start with PHPUnit... and stick to it! :)

\$\endgroup\$
  • 1
    \$\begingroup\$ I think what he is saying is that when the unit test runs, it will try to open the socket to some host with a dependency on a network of some sort. If, while running the test, the network is unavailable, the test will fail \$\endgroup\$ – Rob Apodaca Jan 13 '14 at 0:15
  • \$\begingroup\$ Hmm... in that case it might be useful to create a local server or a hostname to simulate the request. This will at least tell you whether your method is doing what it was designed to do. But if you need an input from the host you are connecting to then if the test fails, it will bring it to your attention that something went wrong. \$\endgroup\$ – roosevelt Jan 13 '14 at 0:37
  • \$\begingroup\$ That could work if the unit test set up the local server in setUp() or something. What's important is that the test controls the responses from the service or whatever he is connecting to. See my post where I show using dependency injection and mocks to solve the problem. \$\endgroup\$ – Rob Apodaca Jan 13 '14 at 0:49
  • \$\begingroup\$ A good unit tests suite should : allow parallel execution of the test suite, be independant of the environment, always execute the same way (if no code is added/changed/deleted ...) Adding a new local server is against all these requirements. It seems this code is simply NOT unit testable... but maybe another kind of test would be needed here (integration test?) Any idea? \$\endgroup\$ – nemenems Jan 13 '14 at 9:35
1
\$\begingroup\$

I think that what you are getting at is that given the way the class is currently written, you've got a dependency and no way to replace it with a stand in for testing. If you don't replace it, 1) you would need to figure out what a suitable host name would be for the test, 2) if the network failed during the test, the test would fail, and 3) no way to test failures.

To solve this problem, you would need to inject the dependency. In order to do that, you'd have to use some oop wrapper for the php fsock functions.

Sometimes, I do go through the extra effort of creating injectable dependent wrappers and sometimes I do not.

Here is an example of your modified class:

class SenderSocketAdapter extends SenderAdapterAbstract
{
    public function __construct(Socket $socket)
    {
        $this->socket = $socket;
    }

    public function send($message)
    {
        $this->socket->fwrite($message);
        //as other poster said, you should probably 
        //return a status or something here.
        //I'll assume that the parent function send is said to
        //return true on success and false on error. And I'll
        //assume that the called service returns "0" or "-1"
        return ($this->socket->fgets() === "0");
    }
}

For the unit test:

class SenderSocketAdapterTest extends PHPUnit_TestCase
{
    public function testSendSuccess()
    {
        $socket = $this->getMock('Socket');
        $sender = new SenderSocketAdapter($socket);

        $message = "the message";
        $socket->expects($this->once())
               ->method('write')
               ->with($this->equalTo($message))
               ->will($this->returnValue("0");
        $result = $sender->send($message);
        $this->assertTrue($result);
    }

    public function testSendFailure()
    {
        $socket = $this->getMock('Socket');
        $sender = new SenderSocketAdapter($socket);

        $message = "the message";
        $socket->expects($this->once())
               ->method('write')
               ->with($this->equalTo($message))
               ->will($this->returnValue("-1");
        $result = $sender->send($message);
        $this->assertFalse($result);
    }
}

In the tests above, we are ensuring that the message being passed into the send method is being properly passed to the socket. And, we check to make sure that we return the correct boolean when the Socket operation is successful and when it fails. We do these things by directly controlling the "Socket" for the test conditions.

Your calling code would then look something like:

$socket = new Socket($host, $port);
$sender = new SenderSocketAdapter($socket);
if ($sender->send('hello') === true) {
    echo "success!";
} else {
    echo "failure!";
}

You might want to look at this socket wrapper as a tool to use for your injected dependency.

\$\endgroup\$
  • \$\begingroup\$ Thx for your suggestion but, the original class ClientSocket wich extends SenderAdapterAbstract is already an adapter pattern, it will be injected to other classes. So I don't understand why should I add another Socket adapter since this is already done! And with your new Socket adapter, I would have the same problem: how to unit test this adapter \$\endgroup\$ – nemenems Jan 13 '14 at 9:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.