简体   繁体   English

如何为用Python编写的DBUS服务编写功能测试?

[英]How to write a functional test for a DBUS service written in Python?

(Title was: "How to write a unit test for a DBUS service written in Python?") (标题是:“如何为用Python编写的DBUS服务编写单元测试?”)

I've started to write a DBUS service using dbus-python, but I'm having trouble writing a test case for it. 我已经开始使用dbus-python编写DBUS服务,但是我在编写测试用例时遇到了麻烦。

Here is an example of the test I am trying to create. 这是我正在尝试创建的测试示例。 Notice that I have put a GLib event loop in the setUp(), this is where the problem hits: 请注意,我在setUp()中放置了一个GLib事件循环,这就是问题所在:

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

My problem is that the DBUS implementation requires you to start an event loop so that it can start dispatching events. 我的问题是DBUS实现要求您启动一个事件循环,以便它可以开始调度事件。 The common approach is to use GLib's gobject.MainLoop().start() (although I'm not married to this approach, if someone has a better suggestion). 常见的方法是使用GLib的gobject.MainLoop()。start()(虽然我没有嫁给这种方法,如果有人有更好的建议)。 If you don't start an event loop, the service still blocks, and you also cannot query it. 如果您没有启动事件循环,该服务仍会阻止,您也无法查询它。

If I start my service in the test, the event loop blocks the test from completing. 如果我在测试中启动我的服务,事件循环会阻止测试完成。 I know the service is working because I can query the service externally using the qdbus tool, but I can't automate this inside the test that starts it. 我知道该服务正在运行,因为我可以使用qdbus工具从外部查询服务,但我无法在启动它的测试中自动执行此操作。

I'm considering doing some kind of process forking inside the test to handle this, but I was hoping someone might have a neater solution, or at least a good starting place for how I would write a test like this. 我正在考虑在测试中进行某种处理以处理这个问题,但我希望有人可能有一个更整洁的解决方案,或者至少是一个很好的起点,我将如何编写这样的测试。

With some help from Ali A's post, I have managed to solve my problem. 在Ali A的帖子的帮助下,我设法解决了我的问题。 The blocking event loop needed to be launched into a separate process, so that it can listen for events without blocking the test. 阻塞事件循环需要启动到一个单独的进程中,以便它可以在不阻塞测试的情况下侦听事件。

Please be aware my question title contained some incorrect terminology, I was trying to write a functional test, as opposed to a unit test. 请注意我的问题标题包含一些不正确的术语,我试图编写功能测试,而不是单元测试。 I was aware of the distinction, but didn't realise my mistake until later. 我知道这种区别,但直到后来才意识到我的错误。

I've adjusted the example in my question. 我在我的问题中调整了这个例子。 It loosely resembles the "test_pidavim.py" example, but uses an import for "dbus.glib" to handle the glib loop dependencies instead of coding in all the DBusGMainLoop stuff: 它松散地类似于“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()

Simple solution: don't unit test through dbus. 简单的解决方案:不要通过dbus进行单元测试。

Instead write your unit tests to call your methods directly. 而是编写单元测试以直接调用您的方法。 That fits in more naturally with the nature of unit tests. 这更符合单元测试的本质。

You might also want some automated integration tests, that check running through dbus, but they don't need to be so complete, nor run in isolation. 您可能还需要一些自动集成测试,它们检查通过dbus运行,但它们不需要如此完整,也不需要单独运行。 You can have setup that starts a real instance of your server, in a separate process. 您可以在单独的进程中进行设置以启动服务器的实际实例。

I might be a bit out of my league here, since I don't know python and only somewhat understand what this magical "dbus" is, but if I understand correctly, it requires you to create a rather unusual testing environment with runloops, extended setup/teardown, and so on. 我可能有点离开我的联盟,因为我不知道python并且只知道这个神奇的“dbus”是什么,但如果我理解正确,它需要你用runloops创建一个相当不寻常的测试环境,扩展设置/拆卸,等等。

The answer to your problem is to use mocking . 你的问题的答案是使用模拟 Create an abstract class which defines your interface, and then build an object from that to use in your actual code. 创建一个定义接口的抽象类,然后构建一个用于实际代码的对象。 For the purposes of testing, you build a mock object communicates through that same interface, but has behavior which you would define for the purposes of testing. 出于测试目的,您构建一个模拟对象通过相同的接口进行通信,但具有为测试目的定义的行为。 You can use this approach to "simulate" the dbus object running through an event loop, doing some work, etc., and then simply concentrate on testing how your class ought to react to the result of the "work" done by that object. 您可以使用此方法“模拟”通过事件循环运行的dbus对象,执行某些工作等,然后只关注测试您的类应该如何对该对象完成的“工作”结果做出反应。

You just need to make sure you are handling your main loop properly. 您只需要确保正确处理主循环。

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

This will run the gtk main loop until it has finished processing everything, rather than just run it and block. 这将运行gtk主循环,直到它完成处理所有内容,而不是仅运行它并阻止。

For a complete example of it in practise, unit testing a dbus interface, go here: http://pida.co.uk/trac/browser/pida/editors/vim/test_pidavim.py 有关它在实践中的完整示例,请对dbus接口进行单元测试,请访问: http//pida.co.uk/trac/browser/pida/editors/vim/test_pidavim.py

You could also start the mainloop in a separate thread very simply inside your setUp method. 你也可以在setUp方法中非常简单地在一个单独的线程中启动mainloop。

Something like this: 像这样的东西:

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

Check out python-dbusmock library. 看看python-dbusmock库。

It hides the ugly subprocess logic behind your eyes, so you don't have to worry about it in your tests. 它隐藏了丑陋的子进程逻辑,因此您不必在测试中担心它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 Python 中为使用 Java 编写的 gRPC 服务编写 gRPC 客户端 - How to write a gRPC client in Python for a gRPC service written in Java 我们如何测试写在python中的功能测试文件列表中的特定方法 - How do we test a specific method written in a list of files for functional testing in python 如何使用 pipetools 为以函数范式编写的函数编写文档字符串 - How to write docstrings for functions written in functional paradigm using pipetools 函数式编程:如何将用python编写的fibonacci实现转换为haskell - Functional programming: How to convert an implementation of fibonacci written in python to haskell 如何以函数式编程风格编写此 python 片段? - How to write this python snippet in functional programming style? 如何在Selenium Python中编写功能/集成测试 - How to write functional/integration tests in Selenium Python dbus无法通过python找到compiz服务 - dbus cannot find compiz service through python 通过 python 和 dbus 启动用户 systemd 服务 - Starting a users systemd service via python and dbus 如何断开python DBus连接? - How to Disconnect a python DBus connection? 如何为两个单独的 Dbus Python 程序创建 Dbus Mainloop - How to create for Dbus Mainloop for two separate Dbus Python programs
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM