Подтвердить что ты не робот

Как написать функциональный тест для службы DBUS, написанной на Python?

(Название было: "Как написать unit test для службы DBUS, написанной на Python?" )

Я начал писать службу DBUS с помощью dbus-python, но мне трудно написать тестовый пример.

Вот пример теста, который я пытаюсь создать. Обратите внимание, что я поставил цикл событий GLib в setUp(), в этом проблема:

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()

Моя проблема заключается в том, что для реализации DBUS вам необходимо запустить цикл событий, чтобы он мог запускать диспетчерские события. Общий подход заключается в использовании GLib gobject.MainLoop(). Start() (хотя я не женат на этом подходе, если у кого-то есть лучшее предложение). Если вы не запускаете цикл событий, служба по-прежнему блокируется, и вы также не можете запросить его.

Если я запустил свою службу в тесте, цикл события блокирует выполнение теста. Я знаю, что служба работает, потому что я могу запросить внешнюю службу с помощью qdbus-инструмента, но я не могу автоматизировать это внутри теста, который запускает его.

Я рассматриваю возможность выполнения какого-то процесса, выполняющегося внутри теста, чтобы справиться с этим, но я надеялся, что у кого-то может быть более аккуратное решение или, по крайней мере, хорошее начало для того, как я напишу такой тест.

4b9b3361

Ответ 1

С некоторой помощью от почты Али, мне удалось решить мою проблему. Цикл событий блокировки должен быть запущен в отдельный процесс, чтобы он мог прослушивать события без блокировки теста.

Помните, что мой заголовок вопроса содержал некорректную терминологию, я пытался написать функциональный тест, а не unit test. Я знал о различии, но до конца не осознавал свою ошибку.

Я скорректировал пример в своем вопросе. Он слабо напоминает пример "test_pidavim.py", но использует импорт для "dbus.glib" для обработки зависимостей glib loop вместо кодирования во всех материалах 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()

Ответ 2

Простое решение: не unit test через dbus.

Вместо этого напишите свои модульные тесты, чтобы напрямую обращаться к вашим методам. Это более естественно соответствует природе модульных тестов.

Вам также могут потребоваться некоторые автоматические тесты интеграции, которые проверяют работу через dbus, но они не должны быть настолько полными и не изолированы. У вас может быть настройка, которая запускает реальный экземпляр вашего сервера в отдельном процессе.

Ответ 3

Я мог бы быть немного из моей лиги здесь, так как я не знаю питона и лишь немного понимаю, что это за волшебный "dbus", но если я правильно понимаю, это требует от вас создания довольно необычной тестовой среды с runloops, расширенная настройка/отключение и т.д.

Ответ на вашу проблему заключается в использовании насмешки. Создайте абстрактный класс, который определяет ваш интерфейс, а затем создайте объект, который будет использоваться в вашем фактическом коде. В целях тестирования вы создаете макет объекта, обмениваетесь этим интерфейсом, но имеет поведение, которое вы определяете для целей тестирования. Вы можете использовать этот подход для "имитации" объекта dbus, проходящего через цикл событий, выполнения некоторой работы и т.д., А затем просто сосредоточиться на тестировании того, как ваш класс должен реагировать на результат "работы", выполняемой этим объектом.

Ответ 4

Вам просто нужно убедиться, что вы правильно управляете своим основным контуром.

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

Это запустит основной цикл gtk, пока он не завершит обработку всего, а не просто запустит его и заблокирует.

Для полного примера этого на практике, тестируя интерфейс dbus, перейдите сюда: http://pida.co.uk/trac/browser/pida/editors/vim/test_pidavim.py

Ответ 5

Вы также можете запустить mainloop в отдельном потоке очень просто внутри вашего метода setUp.

Что-то вроде этого:

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()

Ответ 6

Откроется python-dbusmock библиотека.

Он скрывает уродливую логику подпроцесса за вашими глазами, поэтому вам не нужно беспокоиться об этом в своих тестах.