如何使用python单元测试函数写文件unittest

问题描述:

我有一个Python函数将输出文件写入磁盘。如何使用python单元测试函数写文件unittest

我想使用Python unittest模块为它编写单元测试。

我该如何声明文件的平等?如果文件内容与预期的差异+列表不同,我希望得到一个错误。与unix diff命令的输出一样。

有没有官方/推荐的方法呢?

最简单的事情是编写输出文件,然后读取其内容,读取黄金(预期)文件的内容,并将它们与简单的字符串相等进行比较。如果它们相同,请删除输出文件。如果他们不同,请提出断言。

通过这种方式,当测试完成后,每个失败的测试都将用输出文件表示,您可以使用第三方工具来区分它们与黄金文件(Beyond Compare对此很精彩)。

如果您确实想提供自己的差异输出,请记住Python stdlib具有difflib模块。在Python 3.1的新单元测试支持,包括使用它来显示差异列表,类似这样的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) 

您可以在内容生成与文件处理分开。这样,您就可以测试内容是否正确,而不必乱用临时文件并在以后清理它们。

如果您编写了产生每行内容的generator method,则可以使用文件处理方法打开文件并使用行序列调用file.writelines()。这两种方法甚至可以在同一个类上:测试代码会调用生成器,生产代码会调用文件处理器。

下面是一个显示所有三种测试方式的示例。通常,您只需选择一个,具体取决于要在课程中测试哪些方法。

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) 
+0

能为你提供的示例代码?听起来很有趣。 – buhtz 2017-01-23 04:16:31

+1

我为所有三种方法添加了一个示例@buhtz。 – 2017-01-23 18:50:03

我更喜欢有输出功能明确接受一个文件手柄(或类似文件的对象),而不是接受一个文件名打开文件本身。这样,我可以将StringIO对象传递给我单元测试中的输出函数,然后.read()StringIO对象返回的内容(在调用.seek(0)之后)并与我的预期输出进行比较。

例如,我们会过渡这样

##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, 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.\n") 

这种方法让你的输出功能,更灵活,例如如果额外的好处,你决定你不想写入文件,而是写入其他缓冲区,因为它会接受所有类似文件的对象。

请注意,使用StringIO假定测试输出的内容可以放入主内存。对于非常大的输出,您可以使用temporary file方法(例如,tempfile.SpooledTemporaryFile)。

+2

这比将文件写入磁盘更好。如果你正在运行大量的unittests,IO到磁盘会导致各种问题,尤其是试图清理它们。我有测试写入磁盘,tearDown删除书面文件。测试会一次正常工作,然后在全部运行时失败。至少在Win机器上使用Visual Studio和PyTools。另外,速度。 – srock 2015-07-30 17:47:03

+1

虽然这是一个很好的解决方案来测试单独的函数,但在测试程序提供的实际接口(例如CLI工具)时仍然很麻烦.. – Joost 2015-10-27 13:39:10

+0

您可以使用''seek''替换'seek()'和'的GetValue()'。 – 2017-01-23 18:27:03

根据建议,我做了以下。

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) 

我创建了一个子类MyTestCase的,因为我有很多需要读/写文件,所以我真的需要重新使用的断言方法的功能。现在在我的测试中,我将继承MyTestCase而不是unittest.TestCase。

您对此有何看法?

+2

请参阅http://stackoverflow.com/questions/4617034/python-open-multiple-files-using-with-open – 2014-09-23 20:24:59

import filecmp 

然后

self.assertTrue(filecmp.cmp(path1, path2))