编辑:这个答案有点长 :)
你的代码和我的原始答案并没有太大区别。我的一些想法仍然适用。
在编写单元测试时,您希望仅测试您的逻辑。当您使用与操作系统交互的代码时,通常要模拟该部分。原因是您对这些库的输出控制不大,就像您发现的那样。因此,更容易模拟这些调用。
在这种情况下,有两个库正在与系统交互:os.listdir
和EnumProcesses
。由于您没有编写它们,我们可以轻松地伪造它们以返回我们需要的内容。在这种情况下是一个列表。
但是,等等,在您的评论中提到:
"我遇到的问题是它确实没有测试
我的代码是否看到了系统上的新进程,而是代码是否正确监视了列表中的新项。"
事实是,我们不需要测试实际监视系统进程的代码,因为它是第三方代码。我们需要测试的是您的代码逻辑如何处理返回的进程。因为那是你写的代码。我们测试列表的原因是因为那是您的逻辑所做的。 os.listdir
和EniumProcesses
返回一个pid列表(数字字符串和整数,分别),您的代码对该列表进行操作。
我假设您的代码在类中(您的代码中使用了self
)。我还假设它们隔离在自己的方法中(您使用了return
)。这将与我最初建议的有些类似,只是具有实际的代码:)我不知道它们是否在同一个类中或不同的类中,但这并不重要。
Linux方法
现在,测试您的Linux进程函数并不那么困难。您可以修补os.listdir
以返回pid列表。
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
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).
"""
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)
with patched_context(os, 'listdir', lamba x: raise IOError):
myClass = LinuxProcessClass()
....
result = myClass.getLinuxProcess()
expected = []
self.assertEqual(result, expected)
fakeProcessIds = ['1', '2', '3', 'do', 'not', 'parse']
.....
Windows 方法
测试 Windows 方法有点棘手。我的建议如下:
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指针将使用的空间。也许您可以为count大小添加限制,但除此之外,该方法简短而简洁。它仅返回窗口进程,没有其他信息。这正是我在原始评论中请求的内容:)
因此,请不要修改该方法。不要测试它。请确保使用getWindowsProcess和getLinuxProcess的任何内容都按照我的最初建议进行模拟。
希望这更有意义:)如果不是,请告诉我,也许我们可以进行聊天或视频通话。
def getWindowsProcesses(self, ...):
'''Call Windows API function EnumProcesses and
return the list of processes
'''
return listOfProcesses
def getLinuxProcesses(self, ...):
'''Look in /proc dir and return list of processes'''
return listOfProcessses
这两种方法只做一件事情,就是获取进程列表。对于Windows系统,可能只是调用API,而对于Linux系统,只需要读取/proc目录。仅此而已,没有更多的操作。处理进程逻辑将在其他地方进行。这使得这些方法非常容易被模拟,因为它们的实现只是返回一个进程列表的API调用。
你的代码可以很容易地调用它们:
def getProcesses(...):
'''Get the processes running.'''
isLinux =
if isLinux:
processes = getLinuxProcesses(...)
else:
processes = getWindowsProcesses(...)
在你的测试中,你可以使用像
Fudge这样的模拟库。你可以模拟这两个方法返回你
期望的结果。
这样,你就可以测试
你自己的逻辑,因为你可以控制结果会是什么。
from fudge import patched_context
...
def test_getProcesses(self, ...):
monitor = MonitorTool(..)
originalProcesses = [....pids...]
with patched_context(monitor, "getLinuxProcesses", lamba x: originalProcesses):
monitor.getProcesses()
newProcesses = [...]
updatedProcesses = originalProcessses + (newProcesses)
with patched_context(monitor, "getLinuxProcesses", lamba x: updatedProcesses):
monitor.getProcesses()
with patched_context(monitor, "getLinuxProcesses", lamba x: originalProcesses):
monitor.getProcesses()
请记住,如果您以这种方式测试您的代码,您将无法获得100%的代码覆盖率(因为您的模拟方法不会被执行),但这没关系。重要的是您正在测试自己的代码而不是第三方的代码。
希望这能帮助您。我知道这并没有回答您的问题,但也许您可以使用它来找出最好的测试代码的方法。