Twisted Trial:如何使用客户端测试MultiService - Reactor是不洁净的

时间:2014-08-20 23:14:40

标签: python unit-testing twisted trial

我继承了我正在尝试添加测试的Twisted MultiService,但无论我做什么,我最终都会遇到DirtyReactorAggregateError。该服务连接到具有twisted.application.internet.TCPClient的服务器。我认为错误是因为TCPClient没有断开连接,但我不确定我应该如何断开连接。使用客户端测试Twisted服务的正确方法是什么?

以下是测试用例:

from labrad.node import *
from twisted.trial import unittest
import os
from socket import gethostname

class NodeTestCase(unittest.TestCase):

    def setUp(self):
        import labrad
        name = os.environ.get('LABRADNODE', gethostname()) + '_test'
        self.node = Node(name,
            labrad.constants.MANAGER_HOST,
            labrad.constants.MANAGER_PORT)
        self.node.startService()
        #self.addCleanup(self.node.stopService)

    def test_nothing(self):
        self.assertEqual(3, 3)

    def tearDown(self):
        return self.node.stopService()

这是节点服务本身:

class Node(MultiService):
    """Parent Service that keeps the node running.

    If the manager is stopped or we lose the network connection,
    this service attempts to restart it so that we will come
    back online when the manager is back up.
    """
    reconnectDelay = 10

    def __init__(self, name, host, port):
        MultiService.__init__(self)
        self.name = name
        self.host = host
        self.port = port

    def startService(self):
        MultiService.startService(self)
        self.startConnection()

    def startConnection(self):
        """Attempt to start the node and connect to LabRAD."""
        print 'Connecting to %s:%d...' % (self.host, self.port)
        self.node = NodeServer(self.name, self.host, self.port)
        self.node.onStartup().addErrback(self._error)
        self.node.onShutdown().addCallbacks(self._disconnected, self._error)
        self.cxn = TCPClient(self.host, self.port, self.node)
        self.addService(self.cxn)

    def stopService(self):
        if hasattr(self, 'cxn'):
            d = defer.maybeDeferred(self.cxn.stopService)
            self.removeService(self.cxn)
            del self.cxn
            return defer.gatherResults([MultiService.stopService(self), d])
        else:
            return MultiService.stopService(self)

    def _disconnected(self, data):
        print 'Node disconnected from manager.'
        return self._reconnect()

    def _error(self, failure):
        r = failure.trap(UserError)
        if r == UserError:
            print "UserError found!"
            return None
        print failure.getErrorMessage()
        return self._reconnect()

    def _reconnect(self):
        """Clean up from the last run and reconnect."""
        ## hack: manually clearing the dispatcher...
        dispatcher.connections.clear()
        dispatcher.senders.clear()
        dispatcher._boundMethods.clear()
        ## end hack
        if hasattr(self, 'cxn'):
            self.removeService(self.cxn)
            del self.cxn
        reactor.callLater(self.reconnectDelay, self.startConnection)
        print 'Will try to reconnect in %d seconds...' % self.reconnectDelay

2 个答案:

答案 0 :(得分:2)

您应该重构您的服务,以便它可以使用来自MemoryReactor的{​​{1}}(twisted.test.proto_helpers包中的一个公共模块,但希望它会移出{{1}最终)。

您使用twisted.test的方式是将其作为要使用的反应器传递到您的代码中。如果您想查看连接成功后会发生什么,请查看其中的一些公共属性 - twisted.testMemoryReactortcpClientsconnectTCP等。您的测试可以然后拉出传递给tcpServers / listenTCP等的工厂实例,并在其上调用connectTCP并在结果上调用listenTCP。要获得buildProtocol实施,您可以使用twisted.test.proto_helpers.StringTransportWithDisconnection。您甚至可以查看(私有API!小心!它会在没有警告的情况下中断!虽然它really should be publicmakeConnection可以在客户端和服务器之间中继流量。

如果你真的需要进行全系统的非确定性实际测试,那么所有的复杂性和随机无关的失败意味着here's an article on actually shutting down a client and server all the way

答案 1 :(得分:0)

我在尝试测试应用程序实例时遇到了类似的问题。我最终创建了一个Python基类,它使用setUp和tearDown方法来启动/停止应用程序。

from twisted.application.app import startApplication
from twisted.application.service import IServiceCollection
from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest

class MyTest(unittest.TestCase):

    def setUp(self):
        startApplication(self.app, save=False)

    @inlineCallbacks
    def tearDown(self):
        sc = self.app.getComponent(IServiceCollection)
        yield sc.stopService()