編集:この答えは長くなっています:)、しかし私の元の答えのいくつかはまだ当てはまるので、私はそれを残します:)
あなたのコードは私の元の答えとそれほど変わりません。私の考えのいくつかはまだ当てはまります。
単体テストを作成するときは、ロジックのみをテストする必要があります。オペレーティングシステムと相互作用するコードを使用する場合、通常はその部分をモックアウトする必要があります。その理由は、ご存知のように、これらのライブラリの出力をあまり制御できないためです。したがって、これらの呼び出しをモックする方が簡単です。
この場合、システムと相互作用している2つのライブラリがあります:os.listdir
とEnumProcesses
。あなたがそれらを書いていなかったので、私たちは簡単にそれらを偽造して必要なものを返すことができます。この場合はリストです。
しかし、待ってください、あなたが言及したあなたのコメントで:
「しかし、私が抱えている問題は、コードがシステム上で新しいプロセスを認識していることを実際にテストするのではなく、コードがリスト内の新しいアイテムを正しく監視していることです。」
重要なのは、システム上のプロセスを実際に監視するコードはサードパーティのコードであるため、テストする必要がないということです。テストする必要があるのは、コードロジックが返されたプロセスを処理することです。それがあなたが書いたコードだからです。リストをテストしている理由は、それがロジックが実行しているためです。pidのリスト(それぞれ数値文字列と整数)を返し、コードはそのリストに作用します。os.listir
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 []
読みやすくするために、コードを2つのメソッドに分けました(すでにこれを行っていると思います)。ここで注意が必要なのEnumProcesses
は、ポインターを使用することであり、それらを操作するのは簡単ではありません。もう1つは、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
これらの2つのメソッドは、プロセスのリストを取得するという1つのことだけを実行します。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などのモックライブラリを使用できます。これらの2つのメソッドをモックアウトして、期待どおりの結果を返します。
このようにして、結果がどうなるかを制御できるため、ロジックをテストします。
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%のコードカバレッジが得られないことに注意してください。ただし、これは問題ありません。重要なのは、サードパーティではなくコードをテストしていることです。
うまくいけば、これはあなたを助けることができるかもしれません。私はそれがあなたの質問に答えないことを知っています、しかし多分あなたはあなたのコードをテストするための最良の方法を理解するためにこれを使うことができます。