I prefer to have output functions explicitly accept a file handle (or file-like object), rather than accept a file name and opening the file themselves. This way, I can pass a StringIO.StringIO
(or more usually a cStringIO.StringIO
) object to the output function in my unit test, then .read()
the contents back from that StringIO
object (after a .seek(0)
call) and compare with my expected output.
For example:
##File:lamb.py
import sys
def write_lamb(filename):
outfile = open(filename, 'w')
outfile.write("Mary had a little lamb.\n")
outfile.close()
if __name__ == '__main__':
write_lamb(sys.argv[1])
##File test_lamb.py
import unittest
import tempfile
import lamb
class LambTests(unittest.TestCase):
def test_lamb_output(self):
tempfile_path = tempfile.mkstemp()[1]
lamb.write_lamb(tempfile_path)
expected = "Mary had a little lamb.\n"
result = open(tempfile_path).read()
try:
# NOTE: You could replace this with a string-comparison
# method like assertMultiLineEqual
self.assertEqual(result, expected)
finally:
# NOTE: To retain the tempfile if the test fails, remove
# the try-finally clause.
os.remove(tempfile_path)
Goes to this
##File:lamb2.py
import sys
def write_lamb(outfileh):
outfileh.write("Mary had a little lamb.\n")
if __name__ == '__main__':
outfile = open(sys.argv[1])
write_lamb(outfile)
outfile.close()
##File test_lamb2.py
import unittest
#import tempfile
import cStringIO
import lamb2
class LambTests(unittest.TestCase):
def test_lamb_output(self):
tempfile = cStringIO.StringIO()
# NOTE: Alternatively, for Python 2.6+, you can use
# tempfile.SpooledTemporaryFile, e.g.,
#tempfile = tempfile.SpooledTemporaryFile(10 ** 9)
lamb.write_lamb(tempfile)
expected = "Mary had a little lamb.\n"
tempfile.seek(0)
result = tempfile.read()
self.assertEqual(result, expected)
This approach has the added benefit of making your output function more flexible if, for instance, you decide you don't want to write to a file, but some other buffer, since it will accept all file-like objects.
Note that using StringIO
assumes the contents of the test output can fit into main memory. For very large output, use a temporary file approach (e.g., tempfile.SpooledTemporaryFile).