Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><strong>edit: this answer is getting long :), but some of my original answer still applies, so I leave it in :)</strong></p> <p>Your code is not so different from my original answer. Some of my ideas still apply.</p> <p>When you are writing Unit Test, you want to only test <em>your</em> logic. When you use code that interacts with the operating system, you usually want to mock that part out. The reason being that you don't have much control over the output of those libraries, as you found out. So it's easier to mock those calls. </p> <p>In this case, there are two libraries that are interacting with the sytem: <code>os.listdir</code> and <code>EnumProcesses</code>. Since you didn't write them, we can easily fake them to return what we need. Which in this case is a list. </p> <p>But wait, in your comment you mentioned: </p> <blockquote> <p>"The issue I'm having with it however is that it really doesn't test that my code is seeing new processes on the system but rather that the code is correctly monitoring new items in a list."</p> </blockquote> <p>The thing is, we don't <em>need</em> to test the code that actually <strong>monitors the processes on the system</strong>, because it's a third party code. What we <em>need</em> to test is that your <strong>code logic handles the returned processes</strong>. Because that's the code you wrote. The reason why we are testing over a list, is because that's what your logic is doing. <code>os.listir</code> and <code>EniumProcesses</code> return a list of pids (numeric strings and integers, respectively) and your code acts on that list. </p> <p>I'm assuming your code is inside a Class (you are using <code>self</code> in your code). I'm also assuming that they are isolated inside their own methods (you are using <code>return</code>). So this will be sort of what I suggested originally, except with actual code :) Idk if they are in the same class or different classes, but it doesn't really matter.</p> <h2>Linux method</h2> <p>Now, testing your Linux process function is not that difficult. You can patch <code>os.listdir</code> to return a list of pids.</p> <pre><code>def getLinuxProcess(self): try: processDirectories = os.listdir(self.PROCESS_DIRECTORY) except IOError: return [] return [pid for pid in processDirectories if pid.isdigit()] </code></pre> <p>Now for the test.</p> <pre><code>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'] ..... </code></pre> <h2>Windows method</h2> <p>Testing your Window's method is a little trickier. What I would do is the following:</p> <pre><code>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 [] </code></pre> <p>I separated the code into two methods to make it easier to read (I believe you are already doing this). Here is the tricky part, <code>EnumProcesses</code> is using pointers and they are not easy to play with. Another thing is, that I don't know how to work with pointers in Python, so I couldn't tell you of an easy way to mock that out =P</p> <p>What I <em>can</em> tell you is to simply not test it. Your logic there is very minimal. Besides increasing the size of <code>count</code>, everything else in that function is creating the space <code>EnumProcesses</code> pointers will use. Maybe you can add a limit to the count size but other than that, this method is short and sweet. It returns the windows processes and nothing more. Just what I was asking for in my original comment :)</p> <p>So leave that method alone. Don't test it. Make sure though, that anything that uses <code>getWindowsProcess</code> and <code>getLinuxProcess</code> get's mocked out as per my original suggestion.</p> <p>Hopefully this makes more sense :) If it doesn't let me know and maybe we can have a chat session or do a video call or something.</p> <p><strong>original answer</strong></p> <p>I'm not exactly sure how to do what you are asking, but whenever I need to test code that depends on some outside force (external libraries, popen or in this case processes) I mock out those parts. </p> <p>Now, I don't know how your code is structured, but maybe you can do something like this:</p> <pre><code>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 </code></pre> <p>These two methods <strong>only do one thing</strong>, get the list of processes. For Windows, it might just be a call to that API and for Linux just reading the /proc dir. That's all, nothing more. The logic for handling the processes will go somewhere else. This makes these methods extremely easy to mock out since their implementations are just API calls that return a list. </p> <p>Your code can then easy call them:</p> <pre><code>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 ... </code></pre> <p>In your test, you can then use a mocking library such as <a href="http://farmdev.com/projects/fudge/api/fudge.html" rel="nofollow">Fudge</a>. You mock out these two methods to return what you <strong>expect</strong> them to return.</p> <p>This way you'll be testing <em>your</em> logic since you can control what the result will be.</p> <pre><code>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' ... </code></pre> <p>Keep in mind that if you test your code this way, you won't get 100% code coverage (since your mocked methods won't be run), but this is fine. You're testing your code and not third party's, which is what matters.</p> <p>Hopefully this might be able to help you. I know it doesn't answer your question, but maybe you can use this to figure out the best way to test your code.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload