Domanda

(titolo era: "Come scrivere uno unit test per un servizio DBUS scritto in Python")

Ho iniziato a scrivere un servizio DBUS utilizzando dbus-python, ma sto avendo problemi a scrivere un banco di prova per esso.

Ecco un esempio del test che sto cercando di creare. Si noti che ho messo un ciclo di eventi GLib nel setup (), questo è dove il problema colpisce:

import unittest

import gobject
import dbus
import dbus.service
import dbus.glib

class MyDBUSService(dbus.service.Object):
    def __init__(self):
        bus_name = dbus.service.BusName('test.helloservice', bus = dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, '/test/helloservice')

    @dbus.service.method('test.helloservice')
    def hello(self):
        return "Hello World!"


class BaseTestCase(unittest.TestCase):

    def setUp(self):
        myservice = MyDBUSService()
        loop = gobject.MainLoop()
        loop.run()
        # === Test blocks here ===

    def testHelloService(self):
        bus = dbus.SessionBus()
        helloservice = bus.get_object('test.helloservice', '/test/helloservice')
        hello = helloservice.get_dbus_method('hello', 'test.helloservice')
        assert hello() == "Hello World!"

if __name__ == '__main__':
    unittest.main()

Il mio problema è che l'attuazione DBUS richiede di avviare un ciclo di eventi in modo che possa iniziare a eventi di dispacciamento. L'approccio comune è quello di utilizzare gobject.MainLoop di GLib (). Start () (anche se io non sono sposato a questo approccio, se qualcuno ha un suggerimento migliore). Se non si avvia un ciclo di eventi, il servizio continua a blocchi, e anche voi non si può eseguire una query.

Se comincio il mio servizio nel test, i blocchi ciclo di eventi i test da completare. So che il servizio sta funzionando perché posso interrogare il servizio esternamente utilizzando lo strumento qdbus, ma non posso automatizzare questo all'interno del test che lo avvia.

Sto pensando di fare una sorta di processo di fork all'interno del test per gestire questo, ma speravo che qualcuno potrebbe avere una soluzione più ordinato, o almeno un buon punto di partenza per come vorrei scrivere un test come questo.

È stato utile?

Soluzione

Con un po 'di aiuto da post di Ali A, sono riuscito a risolvere il mio problema. Il ciclo di eventi di blocco doveva essere lanciato in un processo separato, in modo che possa ascoltare gli eventi senza bloccare il test.

Si prega di notare il mio titolo questione conteneva alcuni termini errati, stavo cercando di scrivere un test funzionale, al contrario di una prova di unità. Ero consapevole della distinzione, ma non mi rendevo conto mio errore fino al più tardi.

Ho modificato l'esempio nella mia interrogazione. Assomiglia vagamente l'esempio "test_pidavim.py", ma utilizza un'importazione per "dbus.glib" per gestire le dipendenze ciclo glib invece di codifica in tutta la roba DBusGMainLoop:

import unittest

import os
import sys
import subprocess
import time

import dbus
import dbus.service
import dbus.glib
import gobject

class MyDBUSService(dbus.service.Object):

    def __init__(self):
        bus_name = dbus.service.BusName('test.helloservice', bus = dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, '/test/helloservice')

    def listen(self):
        loop = gobject.MainLoop()
        loop.run()

    @dbus.service.method('test.helloservice')
    def hello(self):
        return "Hello World!"


class BaseTestCase(unittest.TestCase):

    def setUp(self):
        env = os.environ.copy()
        self.p = subprocess.Popen(['python', './dbus_practice.py', 'server'], env=env)
        # Wait for the service to become available
        time.sleep(1)
        assert self.p.stdout == None
        assert self.p.stderr == None

    def testHelloService(self):
        bus = dbus.SessionBus()
        helloservice = bus.get_object('test.helloservice', '/test/helloservice')
        hello = helloservice.get_dbus_method('hello', 'test.helloservice')
        assert hello() == "Hello World!"

    def tearDown(self):
        # terminate() not supported in Python 2.5
        #self.p.terminate()
        os.kill(self.p.pid, 15)

if __name__ == '__main__':

    arg = ""
    if len(sys.argv) > 1:
        arg = sys.argv[1]

    if arg == "server":
        myservice = MyDBUSService()
        myservice.listen()

    else:
        unittest.main()

Altri suggerimenti

Soluzione semplice:. Non unità di prova attraverso dbus

Invece scrivere i test di unità per chiamare direttamente i vostri metodi. Che si adatta in modo più naturale con la natura del test di unità.

Si potrebbe anche voler alcuni test di integrazione automatizzati, che controllano che attraversa dbus, ma non hanno bisogno di essere così completa, né correre in isolamento. Si può avere di setup che inizia una vera e propria istanza del server, in un processo separato.

Potrei essere un po 'fuori dalla mia portata qui, dal momento che non so python e solo un po' a capire che cosa questo magico "dbus" è, ma se ho capito bene, si richiede di creare un ambiente di test piuttosto insolito con runloops, esteso setup / teardown, e così via.

La risposta al vostro problema è quello di utilizzare beffardo . Creare una classe astratta che definisce l'interfaccia, e poi costruire un oggetto da quello da utilizzare nel codice vero e proprio. Ai fini del test, si crea un oggetto fittizio comunica attraverso quella stessa interfaccia, ma ha un comportamento che si definirebbe ai fini del test. È possibile utilizzare questo approccio per "simulare" l'oggetto dbus che attraversa un ciclo di eventi, fare un po 'di lavoro, ecc, e poi semplicemente concentrarsi su test come la classe dovrebbe reagire al risultato del "lavoro" fatto da quell'oggetto.

Hai solo bisogno di assicurarsi che si sta gestendo il vostro ciclo principale in modo corretto.

def refresh_ui():
    while gtk.events_pending():
       gtk.main_iteration_do(False)

Questo verrà eseguito il ciclo principale gtk fino a quando non ha terminato l'elaborazione di tutto, piuttosto che eseguirlo ed il blocco.

Per un esempio completo di esso, in pratica, unità di test di un interfaccia dbus, andate qui: http://pida.co.uk/trac/browser/pida/editors/vim/test_pidavim.py

Si potrebbe anche avviare il ciclo principale in un thread separato in modo molto semplice all'interno del metodo di impostazione.

Qualcosa di simile a questo:

import threading
class BaseTestCase(unittest.TestCase):
    def setUp(self):
        myservice = MyDBUSService()
        self.loop = gobject.MainLoop()
        threading.Thread(name='glib mainloop', target=self.loop.run)
    def tearDown(self):
        self.loop.quit()

libreria python-dbusmock .

Si nasconde la logica brutto sottoprocesso dietro gli occhi, in modo da non dovete preoccuparvi di questo nei test.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top