Python: 如何单元测试基于套接字的代码?

22 投票
3 回答
17612 浏览
提问于 2025-04-16 06:15

我正在写一个使用 gevent.socket 进行通信的 Python 客户端和服务器。有没有什么好的方法可以测试代码的套接字操作(比如,验证无效证书的 SSL 连接会被拒绝)?还是说最简单的办法就是 spawn 一个真实的服务器?

编辑:我觉得“简单”的模拟测试不足以测试 SSL 组件,因为涉及的交互比较复杂。我这样想对吗?还是说有更好的方法来测试 SSL 的东西?

3 个回答

8

还有一种(我觉得更好的)方法:你可以模拟你正在使用的库。一个适合Python的模拟工具是mox

你不需要一堆服务器来测试,比如一个有有效证书的,一个有无效证书的,还有一个根本不支持SSL的,或者一个根本不响应任何请求的等等。你可以用一个“虚拟”的客户端套接字来模拟它们的行为。使用Mox的方式是,你首先“教”它应该期待什么,以及它应该如何反应,然后你在这个模拟的环境中运行你的真实代码,同时把真实的gevent.socket换成模拟的那个。掌握这个方法需要一些练习,但绝对值得。

10

模拟和替代在测试中很有用,但有时候你需要更深入的整合测试。因为启动一个服务器,即使是个假的,也可能需要一些时间,所以可以考虑单独做一套测试(我们叫它整合测试)。

我的原则是“像你会用它那样去测试”,如果你模拟和替代得太多,导致测试变得毫无意义,那就没什么用处了(不过,任何测试总比没有好)。如果你担心处理不好的SSL证书,那就自己做一些不好的证书,然后写个测试来用它们。如果这意味着要启动一个服务器,那就启动吧。也许如果这让你感到困扰,最终会促使你重新设计代码,让它可以用其他方式进行测试。

19

你可以很简单地启动一个服务器,然后在测试案例中访问它。gevent的自带测试套件就是用来测试gevent的内置服务器的。

举个例子:

class SimpleServer(gevent.server.StreamServer):

    def handle(self, socket, address):
        socket.sendall('hello and goodbye!')

class Test(unittest.TestCase):      

    def test(self):
        server = SimpleServer(('127.0.0.1', 0))
        server.start()
        client = gevent.socket.create_connection(('127.0.0.1', server.server_port))
        response = client.makefile().read()
        assert response == 'hello and goodbye!'
        server.stop()

在端口值中使用0意味着服务器会使用任何可用的端口。服务器启动后,bind选择的实际端口值会作为server_port属性提供。

StreamServer也支持SSL,你只需要在构造函数中传入keyfilecertfile参数,它就会在把每个套接字传给你的处理程序之前,用SSLObject包装它。

如果你不使用StreamServer,而是基于Greenlet来搭建服务器,那么确实应该生成一个。别忘了在测试结束时结束它。

gevent中,启动服务器和生成greenlet都是很快的操作,比创建新线程或进程要快得多,你可以为每个测试案例轻松创建一个新的服务器。只要记得在不需要服务器时及时清理。

我认为没有必要模拟任何gevent的API,直接使用它会更简单,因为服务器和客户端可以在同一个进程中愉快地共存。

撰写回答