如何使用python unittest编写文件的函数unit testing
我有一个Python输出文件写入磁盘的function。
我想用Pythonunit testing模块为它编写一个unit testing。
我应该如何声明文件的平等? 如果文件内容不同于预期的差异列表,我希望得到一个错误。 与unix diff命令的输出一样。
有没有官方/推荐的方法呢?
最简单的事情是编写输出文件,然后读取其内容,读取黄金(预期)文件的内容,并将它们与简单的string相等进行比较。 如果它们相同,请删除输出文件。 如果不同,请提出一个断言。
这样,当testing完成时,每个失败的testing都将用一个输出文件来表示,并且可以使用第三方工具来区分它们与黄金文件(Beyond Compare对此很精彩)。
如果你真的想提供你自己的差异输出,请记住Python stdlib具有difflib模块。 Python 3.1中的新unit testing支持包括一个assertMultiLineEqual
方法,用它来显示差异,类似如下:
def assertMultiLineEqual(self, first, second, msg=None): """Assert that two multi-line strings are equal. If they aren't, show a nice diff. """ self.assertTrue(isinstance(first, str), 'First argument is not a string') self.assertTrue(isinstance(second, str), 'Second argument is not a string') if first != second: message = ''.join(difflib.ndiff(first.splitlines(True), second.splitlines(True))) if msg: message += " : " + msg self.fail("Multi-line strings are unequal:\n" + message)
我更喜欢使输出函数明确接受文件句柄 (或文件类对象 ),而不是接受文件名并自行打开文件。 这样,我可以在我的unit testingStringIO
对象传递给输出函数,然后从该StringIO
对象(在.seek(0)
调用之后.read()
内容并将其与我期望的输出进行比较。
例如,我们将像这样转换代码
##File:lamb.py import sys def write_lamb(outfile_path): with open(outfile_path, 'w') as outfile: outfile.write("Mary had a little lamb.\n") 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): outfile_path = tempfile.mkstemp()[1] try: lamb.write_lamb(outfile_path) contents = open(tempfile_path).read() finally: # NOTE: To retain the tempfile if the test fails, remove # the try-finally clauses os.remove(outfile_path) self.assertEqual(result, "Mary had a little lamb.\n")
像这样编码
##File:lamb.py import sys def write_lamb(outfile): outfile.write("Mary had a little lamb.\n") if __name__ == '__main__': with open(sys.argv[1], 'w') as outfile: write_lamb(outfile) ##File test_lamb.py import unittest from io import StringIO import lamb class LambTests(unittest.TestCase): def test_lamb_output(self): outfile = StringIO() # NOTE: Alternatively, for Python 2.6+, you can use # tempfile.SpooledTemporaryFile, eg, #outfile = tempfile.SpooledTemporaryFile(10 ** 9) lamb.write_lamb(outfile) outfile.seek(0) content = outfile.read() self.assertEqual(content, "Mary had a little lamb.\n")
如果您决定不想写入文件,而是使用其他缓冲区,因为它将接受所有类似文件的对象,此方法还有一个额外的好处,即使输出函数更加灵活。
请注意,使用StringIO
假定testing输出的内容可以放入主内存。 对于非常大的输出,可以使用临时文件方法(例如tempfile.SpooledTemporaryFile )。
import filecmp
然后
self.assertTrue(filecmp.cmp(path1, path2))
您可以将内容生成与文件处理分开。 这样,你可以testing内容是正确的,而不必乱搞临时文件,然后清理。
如果你编写一个产生每行内容的生成器方法 ,那么你可以有一个文件处理方法来打开一个文件,并用行序列调用file.writelines()
。 这两种方法甚至可以在同一个类上:testing代码会调用生成器,生产代码会调用文件处理器。
下面是一个显示所有三种testing方式的示例。 通常,您只需select一个,具体取决于要testing的课程有哪些方法。
import os from io import StringIO from unittest.case import TestCase class Foo(object): def save_content(self, filename): with open(filename, 'w') as f: self.write_content(f) def write_content(self, f): f.writelines(self.generate_content()) def generate_content(self): for i in range(3): yield u"line {}\n".format(i) class FooTest(TestCase): def test_generate(self): expected_lines = ['line 0\n', 'line 1\n', 'line 2\n'] foo = Foo() lines = list(foo.generate_content()) self.assertEqual(expected_lines, lines) def test_write(self): expected_text = u"""\ line 0 line 1 line 2 """ f = StringIO() foo = Foo() foo.write_content(f) self.assertEqual(expected_text, f.getvalue()) def test_save(self): expected_text = u"""\ line 0 line 1 line 2 """ foo = Foo() filename = 'foo_test.txt' try: foo.save_content(filename) with open(filename, 'rU') as f: text = f.read() finally: os.remove(filename) self.assertEqual(expected_text, text)
根据build议,我做了以下。
class MyTestCase(unittest.TestCase): def assertFilesEqual(self, first, second, msg=None): first_f = open(first) first_str = first_f.read() second_f = open(second) second_str = second_f.read() first_f.close() second_f.close() if first_str != second_str: first_lines = first_str.splitlines(True) second_lines = second_str.splitlines(True) delta = difflib.unified_diff(first_lines, second_lines, fromfile=first, tofile=second) message = ''.join(delta) if msg: message += " : " + msg self.fail("Multi-line strings are unequal:\n" + message)
我创build了一个MyTestCase子类,因为我有很多需要读/写文件的函数,所以我真的需要有可重用的断言方法。 现在在我的testing中,我将inheritanceMyTestCase而不是unittest.TestCase。
你怎么看待这件事?