Как написать функциональный тест для службы DBUS, написанной на Python? - PullRequest
12 голосов
/ 04 февраля 2009

(Название было: «Как написать модульный тест для службы 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 требует, чтобы вы запустили цикл обработки событий, чтобы он мог начать диспетчеризацию событий. Обычный подход заключается в использовании gobject.MainLoop (). Start () GLib (хотя я не женат на этом подходе, если у кого-то есть лучшее предложение). Если вы не запускаете цикл обработки событий, сервис все еще блокируется, и вы также не можете запросить его.

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

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

Ответы [ 6 ]

6 голосов
/ 04 февраля 2009

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

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

Я исправил пример в своем вопросе. Он напоминает пример «test_pidavim.py», но использует импорт для «dbus.glib» для обработки зависимостей цикла glib вместо кодирования во всех элементах 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()
3 голосов
/ 04 февраля 2009

Простое решение: не тестировать модуль через dbus.

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

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

2 голосов
/ 18 апреля 2009

Вы также можете очень просто запустить 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()
2 голосов
/ 04 февраля 2009

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

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

2 голосов
/ 04 февраля 2009

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

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

0 голосов
/ 01 октября 2013

Извлечение python-dbusmock библиотека.

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

...