Python 为异步tornado web套接字服务器编写同步测试套件

Python 为异步tornado web套接字服务器编写同步测试套件,python,asynchronous,websocket,tornado,python-unittest,Python,Asynchronous,Websocket,Tornado,Python Unittest,我正试图为我的TornadoWebSocket服务器设计一个测试套件 我使用客户机来实现这一点——通过websocket连接到服务器,发送请求并期望得到某种响应 我使用python的unittest来运行我的测试,所以我不能(也不想)强制执行测试运行的顺序 这就是我的基本测试类(所有测试用例继承之后)的组织方式。(此处不相关的测井和某些部分被剥离) 我提出的方案是让这个基类在所有测试中初始化一次连接(尽管不一定是这样-如果它解决了我的问题,我很乐意为每个测试用例设置并拆除连接)。重要的是,我不希

我正试图为我的TornadoWebSocket服务器设计一个测试套件

我使用客户机来实现这一点——通过websocket连接到服务器,发送请求并期望得到某种响应

我使用python的unittest来运行我的测试,所以我不能(也不想)强制执行测试运行的顺序

这就是我的基本测试类(所有测试用例继承之后)的组织方式。(此处不相关的测井和某些部分被剥离)

我提出的方案是让这个基类在所有测试中初始化一次连接(尽管不一定是这样-如果它解决了我的问题,我很乐意为每个测试用例设置并拆除连接)。重要的是,我不希望同时打开多个连接

简单的测试用例是这样的

class ATest(BaseTest):
    @classmethod
    def setUpClass(cls):
        super(ATest, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        super(ATest, cls).tearDownClass()

    def test_a(self):
        saved_stdout = sys.stdout
        try:
            out = StringIO()
            sys.stdout = out
            message_sent = self.websocket.write_message(
                str({'opcode': 'a_message'}})
            )

            output = out.getvalue().strip()
            # the code below is useless
            while (output is None or not len(output)):
                self.log.debug("%s waiting for response." % str(inspect.stack()[0][3]))
                output = out.getvalue().strip()

            self.assertIn(
                'a_response', output,
                "Server didn't send source not a_response. Instead sent: %s" % output
            )
        finally:
            sys.stdout = saved_stdout
它在大多数情况下运行良好,但它不是完全确定的(因此是可靠的)。由于websocket通信是异步执行的,而unittest同步执行测试,因此服务器响应(在同一websocket上接收)会与请求混淆,测试偶尔会失败

我知道它应该是基于回调的,但这并不能解决响应混合的问题。除非,否则所有测试都是在一系列回调中人工排序的(如test_1_回调中的start test_2)

Tornado提供了一种帮助同步测试的方法,但我似乎无法让它与WebSocket一起工作(Tornado.ioloop有自己的线程,您无法阻止)

我找不到一个python websocket同步客户端库,它可以与tornado服务器一起工作,并且兼容RFC6455。Pypi未能满足第二个需求

我的问题是:

  • 是否有可靠的python同步websocket客户端库满足上述要求

  • 如果不是,组织这样的测试套件的最佳方式是什么(测试不能真正并行运行)

  • 据我所知,由于我们使用的是一个websocket,测试用例的IOStream无法分离,因此无法确定响应来自哪个请求(我对具有不同参数的相同类型的请求进行了多个测试)。我错了吗

  • 你有没有看过《龙卷风》中包括的那本书?它们向您展示了如何做到这一点:

    from tornado.testing import AsyncHTTPTestCase, gen_test
    from tornado.websocket import WebSocketHandler, websocket_connect
    
    class MyHandler(WebSocketHandler):
        """ This is the server code you're testing."""
    
        def on_message(self, message):
            # Put whatever response you want in here.
            self.write_message("a_response\n")
    
    
    class WebSocketTest(AsyncHTTPTestCase):
        def get_app(self):
            return Application([
                ('/', MyHandler, dict(close_future=self.close_future)),
            ])  
    
        @gen_test
        def test_a(self):
            ws = yield websocket_connect(
                'ws://localhost:%d/' % self.get_http_port(),
                io_loop=self.io_loop)
            ws.write_message(str({'opcode': 'a_message'}}))
            response = yield ws.read_message()
            self.assertIn(
                    'a_response', response,
                    "Server didn't send source not a_response. Instead sent: %s" % response
                )v
    

    decorator允许您以协同程序的形式运行异步测试用例,当在tornado的ioloop中运行时,可以有效地使它们同步运行以进行测试。

    同时,我设法找到了一个解决方案。[ws4py](ws4py.readthedocs.org)websocket客户端为我完成了这个任务。但你的解决方案更优雅。谢谢你,先生。
    from tornado.testing import AsyncHTTPTestCase, gen_test
    from tornado.websocket import WebSocketHandler, websocket_connect
    
    class MyHandler(WebSocketHandler):
        """ This is the server code you're testing."""
    
        def on_message(self, message):
            # Put whatever response you want in here.
            self.write_message("a_response\n")
    
    
    class WebSocketTest(AsyncHTTPTestCase):
        def get_app(self):
            return Application([
                ('/', MyHandler, dict(close_future=self.close_future)),
            ])  
    
        @gen_test
        def test_a(self):
            ws = yield websocket_connect(
                'ws://localhost:%d/' % self.get_http_port(),
                io_loop=self.io_loop)
            ws.write_message(str({'opcode': 'a_message'}}))
            response = yield ws.read_message()
            self.assertIn(
                    'a_response', response,
                    "Server didn't send source not a_response. Instead sent: %s" % response
                )v