How to do unit testing of functions writing files using python unittest
我有一个python函数,它将输出文件写入磁盘。
我想使用python unit test模块为它编写一个单元测试。
我应该如何断言文件的相等性?如果文件内容与预期的一+差异列表不同,我希望得到一个错误。在unix diff命令的输出中。
是否有任何官方/推荐的方法?
我更喜欢让输出函数显式地接受文件句柄(或类似文件的对象),而不是接受文件名并自己打开文件。这样,我可以在单元测试中将一个
例如,我们将像这样转换代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ##File:lamb.py import sys def write_lamb(outfile_path): with open(outfile_path, 'w') as outfile: outfile.write("Mary had a little lamb. ") 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. ") |
这样编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ##File:lamb.py import sys def write_lamb(outfile): outfile.write("Mary had a little lamb. ") 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, e.g., #outfile = tempfile.SpooledTemporaryFile(10 ** 9) lamb.write_lamb(outfile) outfile.seek(0) content = outfile.read() self.assertEqual(content,"Mary had a little lamb. ") |
这种方法还有一个额外的好处,那就是,如果您决定不想写入文件,而是使用其他缓冲区,那么您的输出函数就更加灵活,因为它将接受所有类似文件的对象。
注意,使用
最简单的方法是编写输出文件,然后读取其内容,读取黄金(预期)文件的内容,并将其与简单字符串相等进行比较。如果它们相同,请删除输出文件。如果他们不同,提出一个主张。
这样,当测试完成时,每一个失败的测试都将用一个输出文件表示,并且您可以使用第三方工具将它们与黄金文件进行比较(Beyond Compare非常适合这样做)。
如果您真的想提供自己的diff输出,请记住python stdlib有difflib模块。python 3.1中新的unittest支持包括一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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: " + message) |
1 | import filecmp |
然后
1 | self.assertTrue(filecmp.cmp(path1, path2)) |
您可以将内容生成与文件处理分开。这样,您就可以测试内容是否正确,而不必乱弄临时文件,然后再清理它们。
如果编写一个生成每行内容的生成器方法,那么您可以有一个文件处理方法,它打开一个文件并按行的顺序调用
下面是一个例子,展示了所有三种测试方法。通常,您只需要选择一个方法,这取决于要测试的类中可用的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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 {} ".format(i) class FooTest(TestCase): def test_generate(self): expected_lines = ['line 0 ', 'line 1 ', 'line 2 '] 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) |
我总是尽量避免将文件写入磁盘,即使它是专门用于测试的临时文件夹:不实际接触磁盘会使测试更快,尤其是在代码中与文件进行大量交互时。
假设您在一个名为
1 2 3 4 5 6 7 8 9 10 | """ main.py """ def write_to_file(text): with open("output.txt","w") as h: h.write(text) if __name__ =="__main__": write_to_file("Every great dream begins with a dreamer.") |
要测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | """ test_main.py """ from unittest.mock import patch, mock_open import main def test_do_stuff_with_file(): open_mock = mock_open() with patch("main.open", open_mock, create=True): main.write_to_file("test-data") open_mock.assert_called_with("output.txt","w") open_mock.return_value.write.assert_called_once_with("test-data") |
根据我的建议,我做了以下工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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: " + message) |
我创建了一个子类mytestcase,因为我有很多函数需要读/写文件,所以我真的需要有可重用的assert方法。现在在我的测试中,我将把my testcase子类化,而不是unittest.testcase。
你觉得怎么样?