背景
我目前正在用Python编写流程监控工具(Windows和Linux)并实现单元测试覆盖。进程监视器挂钩到Windows上的Windows API函数EnumProcesses,并监视Linux上的/ proc目录以查找当前进程。然后将进程名称和进程ID写入可供单元测试访问的日志。
问题:
当我对监控行为进行单元测试时,我需要一个启动和终止的过程。如果有一种(跨平台?)方式来启动和终止我可以唯一命名的假系统进程(并在单元测试中跟踪其创建),我会很高兴。
初步想法:
这些只是初步测试的想法和观察结果,如果有人能证明我在这两点上的错误,我会很高兴。
我正在使用Python 2.6.6。
修改
获取所有Linux进程ID:
try:
processDirectories = os.listdir(self.PROCESS_DIRECTORY)
except IOError:
return []
return [pid for pid in processDirectories if pid.isdigit()]
获取所有Windows进程ID:
import ctypes, ctypes.wintypes
Psapi = ctypes.WinDLL('Psapi.dll')
EnumProcesses = self.Psapi.EnumProcesses
EnumProcesses.restype = ctypes.wintypes.BOOL
count = 50
while True:
# Build arguments to EnumProcesses
processIds = (ctypes.wintypes.DWORD*count)()
size = ctypes.sizeof(processIds)
bytes_returned = ctypes.wintypes.DWORD()
# Call enum processes to find all processes
if self.EnumProcesses(ctypes.byref(processIds), size, ctypes.byref(bytes_returned)):
if bytes_returned.value < size:
return processIds
else:
# We weren't able to get all the processes so double our size and try again
count *= 2
else:
print "EnumProcesses failed"
sys.exit()
答案 0 :(得分:5)
编辑:这个答案变得越来越长了:),但我的一些原始答案仍然适用,所以我将其留在:)
您的代码与我原来的答案没有太大区别。我的一些想法仍然适用。
在编写单元测试时,您只想测试您的逻辑。当您使用与操作系统交互的代码时,通常需要模拟该部分。正如您所发现的那样,原因是您无法控制这些库的输出。因此,模拟这些调用会更容易。
在这种情况下,有两个与系统交互的库:os.listdir
和EnumProcesses
。由于你没有写它们,我们可以很容易地伪造它们以返回我们需要的东西。在这种情况下是一个列表。
但是等等,你在评论中提到:
“我遇到的问题是它确实没有测试 我的代码正在看到系统上的新进程,而不是 代码正在监视列表中的新项目。“
问题是,我们不需要来测试实际上监视系统上的进程的代码,因为它是第三方代码。我们需要测试的是代码逻辑处理返回的进程。因为那是你写的代码。我们测试列表的原因是因为这就是你的逻辑所做的。 os.listir
和EniumProcesses
返回一个pid列表(分别为数字字符串和整数),您的代码将作用于该列表。
我假设你的代码在一个类中(你在代码中使用self
)。我也假设他们在自己的方法中被隔离(你使用return
)。所以这将是我最初的建议,除了实际的代码:) Idk如果它们在同一个类或不同的类中,但它并不重要。
现在,测试Linux进程函数并不困难。您可以修补os.listdir
以返回pids列表。
def getLinuxProcess(self):
try:
processDirectories = os.listdir(self.PROCESS_DIRECTORY)
except IOError:
return []
return [pid for pid in processDirectories if pid.isdigit()]
现在进行测试。
import unittest
from fudge import patched_context
import os
import LinuxProcessClass # class that contains getLinuxProcess method
def test_LinuxProcess(self):
"""Test the logic of our getLinuxProcess.
We patch os.listdir and return our own list, because os.listdir
returns a list. We do this so that we can control the output
(we test *our* logic, not a built-in library's functionality).
"""
# Test we can parse our pdis
fakeProcessIds = ['1', '2', '3']
with patched_context(os, 'listdir', lamba x: fakeProcessIds):
myClass = LinuxProcessClass()
....
result = myClass.getLinuxProcess()
expected = [1, 2, 3]
self.assertEqual(result, expected)
# Test we can handle IOERROR
with patched_context(os, 'listdir', lamba x: raise IOError):
myClass = LinuxProcessClass()
....
result = myClass.getLinuxProcess()
expected = []
self.assertEqual(result, expected)
# Test we only get pids
fakeProcessIds = ['1', '2', '3', 'do', 'not', 'parse']
.....
测试Window的方法有点棘手。我要做的是以下几点:
def prepareWindowsObjects(self):
"""Create and set up objects needed to get the windows process"
...
Psapi = ctypes.WinDLL('Psapi.dll')
EnumProcesses = self.Psapi.EnumProcesses
EnumProcesses.restype = ctypes.wintypes.BOOL
self.EnumProcessses = EnumProcess
...
def getWindowsProcess(self):
count = 50
while True:
.... # Build arguments to EnumProcesses and call enun process
if self.EnumProcesses(ctypes.byref(processIds),...
..
else:
return []
我将代码分成两种方法,以便于阅读(我相信你已经这样做了)。这是一个棘手的部分,EnumProcesses
正在使用指针,它们不容易使用。另一件事是,我不知道如何使用Python中的指针,所以我无法告诉你一个简单的方法来模拟出来= P
我可以告诉你的是不测试它。你的逻辑非常小。除了增加count
的大小之外,该函数中的所有其他内容都是创建指针将使用的空间EnumProcesses
。也许你可以添加一个限制计数大小,但除此之外,这种方法简短而且甜蜜。它返回Windows进程,仅此而已。正是我在原评论中所要求的:)
所以请保持这种方法。不要测试它。但请确保使用getWindowsProcess
和getLinuxProcess
的任何内容都按照我的原始建议进行模拟。
希望这更有意义:)如果它不让我知道,也许我们可以进行聊天会话或进行视频通话等。
原始回答
我不确定如何做你要问的问题,但每当我需要测试依赖于某些外力(外部库,popen或在这种情况下的进程)的代码时,我会模拟掉那些部分。
现在,我不知道你的代码是如何构建的,但也许你可以这样做:
def getWindowsProcesses(self, ...):
'''Call Windows API function EnumProcesses and
return the list of processes
'''
# ... call EnumProcesses ...
return listOfProcesses
def getLinuxProcesses(self, ...):
'''Look in /proc dir and return list of processes'''
# ... look in /proc ...
return listOfProcessses
这两种方法只做一件事,获取进程列表。对于Windows,它可能只是对该API的调用,对于Linux只是读取/ proc目录。这就是全部,仅此而已。处理流程的逻辑将转移到其他地方。这使得这些方法非常容易模拟,因为它们的实现只是返回列表的API调用。
您的代码可以轻松调用它们:
def getProcesses(...):
'''Get the processes running.'''
isLinux = # ... logic for determining OS ...
if isLinux:
processes = getLinuxProcesses(...)
else:
processes = getWindowsProcesses(...)
# ... do something with processes, write to log file, etc ...
在测试中,您可以使用模拟库,例如Fudge。你嘲笑这两种方法,以返回你期望返回的内容。
这样您就可以测试您的逻辑,因为您可以控制结果。
from fudge import patched_context
...
def test_getProcesses(self, ...):
monitor = MonitorTool(..)
# Patch the method that gets the processes. Whenever it gets called, return
# our predetermined list.
originalProcesses = [....pids...]
with patched_context(monitor, "getLinuxProcesses", lamba x: originalProcesses):
monitor.getProcesses()
# ... assert logic is right ...
# Let's "add" some new processes and test that our logic realizes new
# processes were added.
newProcesses = [...]
updatedProcesses = originalProcessses + (newProcesses)
with patched_context(monitor, "getLinuxProcesses", lamba x: updatedProcesses):
monitor.getProcesses()
# ... assert logic caught new processes ...
# Let's "kill" our new processes and test that our logic can handle it
with patched_context(monitor, "getLinuxProcesses", lamba x: originalProcesses):
monitor.getProcesses()
# ... assert logic caught processes were 'killed' ...
请记住,如果您以这种方式测试代码,则不会获得100%的代码覆盖率(因为您的模拟方法不会运行),但这很好。你正在测试你的代码,而不是第三方,这才是最重要的。
希望这可以帮到你。我知道它没有回答你的问题,但也许你可以用它来找出测试代码的最佳方法。
答案 1 :(得分:0)
您最初使用子流程的想法很好。只需创建自己的可执行文件,并将其命名为将其标识为测试内容。也许让它做一些像睡眠一样的事情。
或者,您实际上可以使用多处理模块。我没有在windows中使用python,但你应该能够从你创建的Process对象中获取进程识别数据:
p = multiprocessing.Process(target=time.sleep, args=(30,))
p.start()
pid = p.getpid()