【问题标题】:Trial unittests using Autobahn WebSocket使用 Autobahn WebSocket 进行单元测试
【发布时间】:2013-01-23 07:16:23
【问题描述】:

我正在尝试为使用 Autobahn 的应用程序编写单元测试。

我想测试我的控制器,它从协议中获取接收到的数据,对其进行解析并对其做出反应。

但是当我的测试到了应该断开协议(self.sendClose)时,它会引发错误

exceptions.AttributeError: 'MyProtocol' object has no attribute 'state'.

我尝试使用proto_helpers.StringTransportmakeConnection,但后来我也遇到了错误

exceptions.AttributeError: StringTransport instance has no attribute 'setTcpNoDelay'`

我正在使用trial,我不想仅出于测试目的运行虚拟服务器/客户端,因为不建议这样做。

我应该如何编写测试,以便我可以使用假连接和试用来测试发送数据、读取数据、断开连接等功能?

【问题讨论】:

标签: python websocket twisted autobahn


【解决方案1】:

如果不看MyProtocol 类,很难准确地说出发生了什么。这个问题听起来很像是因为您直接使用低级函数以及WebSocket 类的state 属性,这是WebSocket 内部状态的表示。联系。

根据the autobahn reference doc,您可以直接使用和覆盖的WebSicketProtocol 中的API 是:

  • 打开
  • onMessage
  • 关闭
  • 发送消息
  • 发送关闭

您使用StringTransport 测试您的协议的方法并不理想。问题在于 MyProtocol 是 autobahn 提供的 WebSocketProtocol 框架之上的一个小层,无论好坏,它隐藏了有关管理连接、传输和内部协议状态的细节。

如果您考虑一下,您想测试您的东西,而不是 WebSocketProtocol,因此如果您不想嵌入虚拟服务器或客户端,最好的办法是直接测试 MyProtocol 覆盖的方法。

下面是我所说的一个例子

class MyPublisher(object):
    cbk=None

    def publish(self, msg):
        if self.cbk:
            self.cbk(msg)

class MyProtocol(WebSocketServerProtocol):

    def __init__(self, publisher):
        WebSocketServerProtocol.__init__(self)
        #Defining callback for publisher
        publisher.cbk = self.sendMessage

    def onMessage(self, msg, binary)
        #Stupid echo
        self.sendMessage(msg)

class NotificationTest(unittest.TestCase):    

    class MyProtocolFactory(WebSocketServerFactory):
        def __init__(self, publisher):
            WebSocketServerFactory.__init__(self, "ws://127.0.0.1:8081")
            self.publisher = publisher
            self.openHandshakeTimeout = None

        def buildProtocol(self, addr):
            protocol =  MyProtocol(self.listener)
            protocol.factory = self
            protocol.websocket_version = 13 #Hybi version 13 is supported by pretty much everyone (apart from IE <8 and android browsers)
            return protocol

    def setUp(self):
        publisher = task.LoopingCall(self.send_stuff, "Hi there")        
        factory = NotificationTest.MyProtocolFactory(listener)
        protocol = factory.buildProtocol(None)
        transport = proto_helpers.StringTransport()
        def play_dumb(*args): pass
        setattr(transport, "setTcpNoDelay", play_dumb)
        protocol.makeConnection(transport)
        self.protocol, self.transport, self.publisher, self.fingerprint_handler =  protocol, transport, publisher, fingerprint_handler

    def test_onMessage(self):
        #Following 2 lines are the problematic part. Here you are manipulating explicitly a hidden state which your implementation should not be concerned with!
        self.protocol.state = WebSocketProtocol.STATE_OPEN
        self.protocol.websocket_version = 13
        self.protocol.onMessage("Whatever")
        self.assertEqual(self.transport.value()[2:], 'Whatever')

    def test_push(self):              
        #Following 2 lines are the problematic part. Here you are manipulating explicitly a hidden state which your implementation should not be concerned with!
        self.protocol.state = WebSocketProtocol.STATE_OPEN
        self.protocol.websocket_version = 13
        self.publisher.publish("Hi there")
        self.assertEqual(self.transport.value()[2:], 'Hi There')

您可能已经注意到,在这里使用StringTransport 非常麻烦。您必须了解下划线框架并绕过它的状态管理,这是您并不想做的事情。不幸的是,高速公路没有提供可以轻松进行状态操作的即用型测试对象,因此我使用虚拟服务器和客户端的建议仍然有效


用网络测试你的服务器

提供的测试展示了如何测试服务器推送,断言您得到的是您所期望的,并且还使用了一个关于如何确定何时完成的钩子。

服务器协议

from twisted.trial.unittest import TestCase as TrialTest
from autobahn.websocket import WebSocketServerProtocol, WebSocketServerFactory, WebSocketClientProtocol, WebSocketClientFactory, connectWS, listenWS
from twisted.internet.defer import Deferred
from twisted.internet import task 

START="START"            

class TestServerProtocol(WebSocketServerProtocol):

    def __init__(self):
        #The publisher task simulates an event that triggers a message push
        self.publisher = task.LoopingCall(self.send_stuff, "Hi there")

    def send_stuff(self, msg):
        #this method sends a message to the client
        self.sendMessage(msg)

    def _on_start(self):
        #here we trigger the task to execute every second
        self.publisher.start(1.0)

    def onMessage(self, message, binary):
        #According to this stupid protocol, the server starts sending stuff when the client sends a "START" message
        #You can plug other commands in here
        {
           START : self._on_start
           #Put other keys here
        }[message]()

    def onClose(self, wasClean, code, reason):
        #After closing the connection, we tell the task to stop sending messages
        self.publisher.stop()

客户端协议和工厂

下一个类是客户端协议。它基本上告诉服务器开始推送消息。它在它们上调用close_condition 以查看是否该关闭连接,最后,它在收到的消息上调用assertion 函数以查看测试是否成功

class TestClientProtocol(WebSocketClientProtocol):
    def __init__(self, assertion, close_condition, timeout, *args, **kwargs):
        self.assertion = assertion
        self.close_condition = close_condition
        self._received_msgs = [] 
        from twisted.internet import reactor
        #This is a way to set a timeout for your test 
        #in case you never meet the conditions dictated by close_condition
        self.damocle_sword = reactor.callLater(timeout, self.sendClose)

    def onOpen(self):
        #After the connection has been established, 
        #you can tell the server to send its stuff
        self.sendMessage(START)

    def onMessage(self, msg, binary):
        #Here you get the messages pushed from the server
        self._received_msgs.append(msg)
        #If it is time to close the connection
        if self.close_condition(msg):
            self.damocle_sword.cancel()
            self.sendClose()

    def onClose(self, wasClean, code, reason):
        #Now it is the right time to check our test assertions
        self.assertion.callback(self._received_msgs)

class TestClientProtocolFactory(WebSocketClientFactory):
    def __init__(self, assertion, close_condition, timeout, **kwargs):
        WebSocketClientFactory.__init__(self, **kwargs)
        self.assertion = assertion
        self.close_condition = close_condition
        self.timeout = timeout
        #This parameter needs to be forced to None to not leave the reactor dirty
        self.openHandshakeTimeout = None

    def buildProtocol(self, addr):
        protocol = TestClientProtocol(self.assertion, self.close_condition, self.timeout)
        protocol.factory = self
        return protocol

基于试验的测试

class WebSocketTest(TrialTest):

    def setUp(self):
        port = 8088
        factory = WebSocketServerFactory("ws://localhost:{}".format(port))
        factory.protocol = TestServerProtocol
        self.listening_port = listenWS(factory)
        self.factory, self.port = factory, port

    def tearDown(self):
        #cleaning up stuff otherwise the reactor complains
        self.listening_port.stopListening()

    def test_message_reception(self): 
        #This is the test assertion, we are testing that the messages received were 3
        def assertion(msgs):
            self.assertEquals(len(msgs), 3)

        #This class says when the connection with the server should be finalized. 
        #In this case the condition to close the connectionis for the client to get 3 messages
        class CommunicationHandler(object):
            msg_count = 0

            def close_condition(self, msg):
                self.msg_count += 1
                return self.msg_count == 3

        d = Deferred()
        d.addCallback(assertion)
        #Here we create the client...
        client_factory = TestClientProtocolFactory(d, CommunicationHandler().close_condition, 5, url="ws://localhost:{}".format(self.port))
        #...and we connect it to the server
        connectWS(client_factory)
        #returning the assertion as a deferred purely for demonstration
        return d

这显然只是一个示例,但正如您所见,我不必明确地搞乱makeConnection 或任何transport

【讨论】:

  • 根据试用文档,这是一种不好的方法,我写道我不想创建虚拟服务器。问题是我使用的 (StringTransport) 是一种用于测试目的的特殊传输。在实际应用中,我使用二进制帧。阅读此twistedmatrix.com/documents/11.1.0/core/howto/trial.html#auto6。尤其是这部分在不使用真实网络连接的情况下测试协议既简单又推荐在测试 Twisted 代码时。尽管 Twisted 中有许多使用网络的测试,但大多数好的测试并不
  • 我回复的重点是向您展示您在使用高速公路库时可能会做错什么,而不是为了获得编写虚拟服务器或试用测试策略的灵感。您以错误的方式使用低级函数的事实使您弄乱了 WebSocket 状态,而我的只是一个示例(除了愚蠢的 echo 方法),向您展示了如何使用该库。如果您愿意分享有关实际协议的更多详细信息,我可能会给您一个更有用的答案。
  • 没有更多细节。我想要的只是创建虚假的 WebSocket 连接并通过它发送数据,就像 proto_helpers 中的 StringTransport 一样。目前我还没有看到这样的选项。
  • 我明白你关于避免网络的观点。使用完全可控的堆栈和传输确实更好,但是如果您在以这种方式测试协议时遇到问题,您可能想要做最简单的工作,即在测试中使用网络。
猜你喜欢
  • 1970-01-01
  • 2015-05-25
  • 2015-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
相关资源
最近更新 更多