簡體   English   中英

用於單元測試的Python模擬過程

[英]Python Mock Process for Unit Testing

背景:
我目前正在用Python編寫過程監視工具(Windows和Linux)並實現單元測試范圍。 進程監視器掛接到Windows上的Windows API函數EnumProcesses上,並監視Linux上的/ proc目錄以查找當前進程。 然后,將過程名稱和過程ID寫入到可供單元測試訪問的日志中。

題:
當我對監視行為進行單元測試時,我需要一個過程來啟動和終止。 我希望能有一種(跨平台的)方式來啟動和終止一個我可以唯一命名的虛假系統進程(並在單元測試中跟蹤其創建)。

最初的想法:

  • 我可以使用subprocess.Popen()打開任何系統進程,但這會遇到一些問題。 如果我要測試的過程也由系統運行,則單元測試可能會錯誤地通過。 另外,單元測試是從命令行運行的,任何我能想到的Linux進程都會掛起終端(nano等)。
  • 我可以啟動一個進程並通過其進程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 &lt 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()

Windows代碼從這里

編輯:這個答案越來越長:),但是我的某些原始答案仍然適用,所以我將其留在:)

您的代碼與我的原始答案沒什么不同。 我的一些想法仍然適用。

當你寫單元測試,你只想測試你的邏輯。 當您使用與操作系統交互的代碼時,通常希望將其模擬出來。 原因是,您發現對這些庫的輸出沒有太多控制權。 因此,模擬這些調用更加容易。

在這種情況下,有兩個與系統交互的庫: os.listdirEnumProcesses 由於您沒有編寫它們,因此我們可以輕松地偽造它們以返回我們所需的內容。 在這種情況下是列表。

但是,等等,您在評論中提到:

“但是,我遇到的問題是,它確實不能測試我的代碼是否正在系統上看到新的進程,而是可以正確地監視列表中的新項目。”

事實是,我們不需要測試實際上監視系統上進程的代碼,因為它是第三方代碼。 我們需要測試的是,您的代碼邏輯處理了返回的進程 因為那是您編寫的代碼。 我們測試列表的原因是因為這就是您的邏輯。 os.listirEniumProcesses返回EniumProcesses列表(分別為數字字符串和整數),並且您的代碼在該列表上起作用。

我假設您的代碼在類內(您在代碼中使用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進程,僅此而已。 正是我在原始評論中要求的內容:)

因此,請不要使用該方法。 不要測試。 但是請確保,按照我最初的建議,所有使用getWindowsProcessgetLinuxProcess東西getLinuxProcessgetLinuxProcess掉。

希望這更有意義:)如果沒有讓我知道,也許我們可以進行聊天或進行視頻通話。

原始答案

我不確定要怎么做,但是每當我需要測試依賴於外部力量(外部庫,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%的代碼覆蓋率(因為不會運行模擬方法),但這很好。 您要測試的是代碼而不是第三方的代碼,這很重要。

希望這可以為您提供幫助。 我知道它不能回答您的問題,但是也許您可以使用它來找出測試代碼的最佳方法。

您最初使用子流程的想法是一個好主意。 只需創建您自己的可執行文件,並將其命名為可識別為測試對象的名稱即可。 也許讓它做一些類似睡眠的事情。

或者,您實際上可以使用多處理模塊。 我在Windows中很少使用python,但是您應該能夠從所創建的Process對象中獲取過程標識數據:

p = multiprocessing.Process(target=time.sleep, args=(30,))
p.start()
pid = p.getpid()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM