如何为用Python编写的DBUS服务编写功能测试?
(标题是:“如何为用Python编写的DBUS服务编写单元测试?”) 我已经开始使用dbuspython编写DBUS服务,但是我在为它编写测试用例时遇到了困难 下面是我试图创建的测试的一个示例。请注意,我在setUp()中放置了一个GLib事件循环,这就是问题所在:如何为用Python编写的DBUS服务编写功能测试?,python,unit-testing,dbus,Python,Unit Testing,Dbus,(标题是:“如何为用Python编写的DBUS服务编写单元测试?”) 我已经开始使用dbuspython编写DBUS服务,但是我在为它编写测试用例时遇到了困难 下面是我试图创建的测试的一个示例。请注意,我在setUp()中放置了一个GLib事件循环,这就是问题所在: import unittest import gobject import dbus import dbus.service import dbus.glib class MyDBUSService(dbus.service.O
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工具在外部查询该服务,但我无法在启动该服务的测试中实现自动化
我正在考虑在测试内部进行某种过程分叉来处理这个问题,但我希望有人能有一个更简洁的解决方案,或者至少是一个好的起点,来指导我如何编写这样的测试。简单的解决方案:不要通过dbus进行单元测试 而是编写单元测试来直接调用方法。这更符合单元测试的本质
您可能还需要一些自动化集成测试,这些测试通过DBU进行检查,但它们不需要如此完整,也不需要单独运行。您可以在单独的过程中启动服务器的真实实例。我在这里可能有点力不从心,因为我不懂python,只是稍微了解这个神奇的“dbus”是什么,但如果我理解正确,它需要您创建一个非常不寻常的测试环境,包括运行循环、扩展安装/拆卸,等等
你的问题的答案是使用。创建一个定义接口的抽象类,然后从该类构建一个对象以在实际代码中使用。出于测试的目的,您构建了一个模拟对象,该对象通过同一接口进行通信,但具有您将为测试目的定义的行为。您可以使用这种方法“模拟”运行在事件循环中的dbus对象,进行一些工作,等等,然后只需专注于测试您的类应该如何对该对象完成的“工作”的结果作出反应。您只需确保正确处理主循环即可
def refresh_ui():
while gtk.events_pending():
gtk.main_iteration_do(False)
这将运行gtk主循环,直到它完成所有处理,而不仅仅是运行它并阻塞
关于实践中的完整示例,单元测试dbus接口,请点击此处:在Ali a的帖子的帮助下,我成功地解决了我的问题。阻塞事件循环需要启动到一个单独的进程中,这样它就可以在不阻塞测试的情况下侦听事件 请注意,我的问题标题包含一些不正确的术语,我试图编写一个功能测试,而不是单元测试。我意识到了这种区别,但直到后来才意识到我的错误 我已经调整了我问题中的例子。它大致类似于“test_pidavm.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()
您也可以在安装方法内的单独线程中启动mainloop 大概是这样的:
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()
去图书馆看看
它将丑陋的子流程逻辑隐藏在你的眼睛后面,因此你不必在测试中担心它。我认为你是对的。反省一下,我认为不管怎样,只要直接测试服务方法,测试dbus接口就不会有什么好处。你必须这么做。不要仅仅因为有人告诉你就忽略单元测试,人们给出这种建议真的令人沮丧。是的,你应该在没有dbus的情况下测试你的东西,是的,你应该用dbus测试它。但是如果它们依赖于dbus的启动和工作,那么它们就不是单元测试。因此,为功能编写单元测试,并使用dbus编写具有更复杂设置(多进程等)的集成测试。我选择了更大的“单元”。根据定义,将DBU放在两者之间使其超越了单元测试。我喜欢这个概念。我认为如果我的DBUS接口类开始包含一些逻辑,我将使用mocking来模拟行为。您引用的测试似乎将DBUS服务器作为一个单独的进程(在Vim内部)进行分叉。我的事件循环还可以,但是这个例子证明整个过程不能在测试中完成。谢谢你指出。谢谢-非常方便。尽管我建议使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu