Using Test Doubles in Python
When I first start doing test-first programming I immediately started looking for libraries that would help me create test doubles. One such library I've used is fudge. Another approach is to build a fake class that knows how to keep state for the specific object that you'd like to replace.
A New Object
I rarely create a test double by subclassing the real thing. If we create a new class that implements only the method calls we use, the implementation can be kept simple and invocations of methods that we don't expect use will raise an exception. The following example can be used in place of smtplib.SMTP
import smtplib class fake_SMTP: calls = [] def __init__(self, _server): self.calls.append("__init__('%s')" % _server) def sendmail(self, _from, _to, _msg): self.calls.append("sendmail('%s', %s, <msg>" % (_from, _to)) def quit(self): self.calls.append("quit()")
Now replace the class we're going to test in smtplib
setattr(smtplib, 'SMTP', fake_SMTP)
fake_SMTP is instantiated and called in the same way as the real SMTP and it simply uses a list to record the signature of each method call, allowing me to test concretely and precisely.
# system-under-test: alert.py import alert def test_send_message(): message = "hi" alert.send_message(['eradman@eradman.com'], message) assert_equals(fake_SMTP.calls, ["__init__('localhost')", "sendmail('user', ['eradman@eradman.com'], <msg>", "quit()"])
This method is powerful; I'm free to specify the order in which method calls are recorded, and with what details. In this case I'm not interested in what the message body is, so I only record a token <msg>. It would be just as easy to record the frist two lines or to track the method calls with a dictionary instead of a list.
Annoying: Built-Classes
Unfortunately Python does not allow you to modify some of it's core modules written in C. One way around this is to use a method the simply calls the real thing.
import datetime def now(): return datetime.now()
This method can be mocked using setattr() provided the rest of the codebase uses this method.
Test Discovery and Invocation
Python's unittest package includes a discovery method, but it's rarely useful because every test file is loaded into the same process, making namespace pollution nearly impossible to avoid. Instead prefer using a simple shell script to discover and load tests.
#!/bin/ksh # Find tests starting with ut_ and run them. Each module must invoke unittest # on it's own using # # if __name__ == '__main__': # unittest.main() # case "$1" in -h) shift; echo "usage: `basename $0` [search_pattern]" exit;; esac WD=`dirname $0` PATTERN=$1 PYTHONPATH=$PYTHONPATH:$WD find tests/ -name "${PATTERN:=ut_*.py}" | xargs -P 2 -n 1 python
Notice the use of xargs gave us a concurrent test runner at the cost of the additional RAM required for multiple intances of the Python interpreter. Experiment with the -P option to find out what's optimal.