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

StackOverflow https://stackoverflow.com/questions/510821

  •  21-08-2019
  •  | 
  •  

Вопрос

(Название было:«Как написать модульный тест для службы 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, но я не могу автоматизировать это внутри теста, который ее запускает.

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

Это было полезно?

Решение

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

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

Я скорректировал пример в своем вопросе.Он во многом напоминает пример «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()

Другие советы

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

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

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

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

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

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

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

Вы также можете очень просто запустить основной цикл в отдельном потоке внутри вашего метода 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()

Проверить Python-dbusmock библиотека.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top