如何使用 PHPUnit 对套接字代码进行单元测试?

2022-01-25 00:00:00 sockets unit-testing php phpunit

我目前有一个 Socket 类,它基本上只是 PHP 的 socket_* 函数的 OO 包装类:

I currently have a Socket class, which is basically just an OO wrapper class for PHP's socket_* functions:

class Socket {
    public function __construct(...) {
        $this->_resource = socket_create(...);
    }

    public function send($data) {
        socket_send($this->_resource, $data, ...);
    }

    ...
}

我认为我不能模拟套接字资源,因为我使用的是 PHP 的套接字函数,所以现在我不知道如何可靠地对这个类进行单元测试.

I don't think I can mock the socket resource since I'm using PHP's socket functions, so right now I'm stuck as to how to reliably unit test this class.

推荐答案

您似乎遗漏了单元测试思维的一小部分.

You appear to be missing a small piece to the unit test mindset.

您的问题很容易通过创建一个存根对象来解决.奇怪的是,我一次又一次地给出这个答案,所以它一定被很多人错过了.

Your problem is easily solvable by creating a Stub object. Strangely, I give this answer time and time again, so it must be largely missed by a lot of people.

因为我看到如此对存根和模拟之间的区别感到困惑,所以让我在这里也列出来......

Because I see so much confusion as to the differences between stubs and mocks, let me lay it out here, as well...

  • 模拟是一个类扩展另一个类,测试直接依赖,以改变该类的行为让测试更容易.
  • 存根是一个*实现 API 或接口**的类,测试无法直接对其进行测试,以便使测试成为可能.
  • A mock is a class that extends another class that the test is directly dependent on, in order to change behaviors of that class to make testing easier.
  • A stub is a class that *implements an API or interface** that a test cannot test easily directly on its own, in order to make testing possible.

^-- 这是我读过的对这两个最清晰的描述;我应该把它放在我的网站上.

^-- That is the clearest description of the two I've ever read; I should put it on my site.

套接字有一个很好的功能,您可以绑定到端口 0 以进行测试(说真的,它被称为临时端口").

Sockets have this nice feature where you can bind to port 0 for testing purposes (seriously, it's called the "ephemeral port").

所以试试这个:

class ListeningServerStub
{
    protected $client;

    public function listen()
    {
        $sock = socket_create(AF_INET, SOCK_STREAM, 0);

        // Bind the socket to an address/port
        socket_bind($sock, 'localhost', 0) or throw new RuntimeException('Could not bind to address');

        // Start listening for connections
        socket_listen($sock);

        // Accept incoming requests and handle them as child processes.
        $this->client = socket_accept($sock);
    }

    public function read()
    {
        // Read the input from the client – 1024 bytes
        $input = socket_read($this->client, 1024);
        return $input;
    }
}

创建此对象并将其设置为在您的测试的 setUp() 中侦听,并在 tearDown() 中停止侦听并销毁它.然后,在您的测试中,连接到您的假服务器,通过 read() 函数取回数据,然后进行测试.

Create this object and set it to listen in your test's setUp() and stop listening and destroy it in the tearDown(). Then, in your test, connect to your fake server, get the data back via the read() function, and test that.

如果这对您有很大帮助,请考虑给我一个赏金,让我跳出传统框框思考;-)

If this helps you out a lot, consider giving me a bounty for thinking outside the traditional box ;-)

相关文章