编辑:这个答案越来越长:),但我原来的一些答案仍然适用,所以我把它留在:)
您的代码与我的原始答案没有太大不同。我的一些想法仍然适用。
当您编写单元测试时,您只想测试您的逻辑。当您使用与操作系统交互的代码时,您通常希望模拟该部分。正如您所发现的,原因是您对这些库的输出没有太多控制权。所以更容易模拟这些调用。
在这种情况下,有两个库与系统交互:os.listdir 和 EnumProcesses。由于不是你写的,我们可以很容易地伪造它们来返回我们需要的东西。在这种情况下是一个列表。
但是等等,你在评论中提到:
“但是我遇到的问题是它真的没有测试
我的代码在系统上看到了新进程,而不是
代码正在正确监控列表中的新项目。”
问题是,我们不需要测试实际监控系统进程的代码,因为它是第三方代码。我们需要测试的是您的代码逻辑是否处理返回的进程。因为那是你写的代码。我们测试列表的原因是因为这就是您的逻辑正在做的事情。 os.listir 和 EniumProcesses 返回一个 pid 列表(分别为数字字符串和整数),您的代码将作用于该列表。
我假设您的代码在一个类中(您在代码中使用self)。我还假设它们在自己的方法中被隔离(您使用的是return)。所以这将是我最初建议的,除了实际代码:) Idk 如果它们在同一个类或不同类中,但这并不重要。
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 # 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']
.....
Windows 方法
测试 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% 的代码覆盖率(因为您的模拟方法不会运行),但这很好。您正在测试您的代码而不是第三方的代码,这很重要。
希望这可能对您有所帮助。我知道它不能回答您的问题,但也许您可以使用它来找出测试代码的最佳方法。