【问题标题】:Unittesting - replacing filepaths with StringIO objects单元测试 - 用 StringIO 对象替换文件路径
【发布时间】:2017-04-19 04:32:41
【问题描述】:

我正在尝试对采用文件路径并返回一些文件内容的解析函数进行单元测试。我希望能够将这些函数的数据字符串传递给测试目的。

我知道我可以将 csv.reader() 传递给 StringIO 或 file_handle(例如 csv.reader(StringIO("my,data") 或 csv.reader(open(file)))),但我不能看到一种方法,我可以通过 StringIO 对象代替 filepath 因为 open(StringIO("my, data")) 失败。同样我希望在这些解析中具有文件打开/关闭逻辑方法而不是在我的主要代码中,因为这会使我的主要代码混乱,也意味着我必须重新编写所有文件 IO 接口!

看来我的选择是:

  1. 重写所有现有代码,以便将文件句柄传递给解析函数 - 这真的很痛苦!
  2. 使用 mock.patch() 替换 open() 方法 - 这应该可以工作,但似乎比这项任务需要的更复杂!
  3. 做一些我还没有想到但我确信它一定存在的事情!

导入 csv def parse_file(输入): 使用 open(input, 'r') 作为 f: 阅读器 = csv.reader(f) 输出 = [] 对于阅读器中的行: #做一些复杂的事情 输出.追加(行) 返回输出 import unittest class TestImport(unittest.TestCase): def test_read_string(self): string_input = u"a,b\nc,d\n" output = read_file(string_input) self.assertEqual([['a', 'b'], ['c', 'd']], output) def test_read_file(self): filename = "sample_data.csv" output = read_file(filename) self.assertEqual([['a', 'b'],['c', 'd']], output)

【问题讨论】:

  • 为什么不直接将测试用例写入硬盘并传递路径?
  • 这就是代码当前的工作方式 - 我试图避免这种情况,因为:A 我不想跟踪大量非常小的文本文件B 解析有很多配置选项 - 我很容易在代码中操纵字符串来模拟这些,但是使用文件需要几十个,这使得我的构建看起来“凌乱”

标签: python unit-testing csv stream stringio


【解决方案1】:

对于将来寻找此功能的其他人,我可以使用 Mock 非常有效地做到这一点。

---- module: import_data.py -----

import csv

def read_file(input):
    with open(input, 'r') as f:
        reader = csv.reader(f)
        output = []
        for row in reader:
            #Do something complicated
            output.append(row)
        return output

---- Unittests ----

import unittest
from io import StringIO
from mock import patch
from import_data import read_file

class TestImport(unittest.TestCase):

    @patch('import_data.open')
    def test_read_string(self, mock_file):
        mock_file.return_value = StringIO(u"a,b\nc,d")
        output = read_file(None)
        self.assertEqual([['a', 'b'], ['c', 'd']], output)


    def test_read_file(self):
        filename = "sample_data.csv"
        output = read_file(filename)
        self.assertEqual([['a', 'b', 'c'],['d', 'e', 'f']], output)

【讨论】:

    【解决方案2】:

    如果您不想更改接口以接受像StringIO 这样的打开文件对象,请查看testfixtures module。我用它来管理单元测试的文件和目录,虽然我通常更喜欢传入StringIO 对象。

    如果您不喜欢这样,那么修补open() 听起来是一个合理的策略。我自己没试过。

    【讨论】:

      【解决方案3】:

      您可以使用temporary files

      如果你真的不想用硬盘,你可以用StringIO来替换你的文件,重新定义内置的open函数,像这样:

      import StringIO
      import csv
      
      #this function is all you need to make your code work with StringIO objects
      def replaceOpen():
          #the next line redefines the open function
          oldopen, __builtins__.open = __builtins__.open, lambda *args, **kwargs: args[0] if isinstance(args[0], StringIO.StringIO) else oldopen(*args, **kwargs)
      
          #these methods below have to be added to the StringIO class
          #in order for the with statement to work
          StringIO.StringIO.__enter__ = lambda self: self
          StringIO.StringIO.__exit__ = lambda self, a, b, c: None
      
      replaceOpen()
      
      #after the re-definition of open, it still works with normal paths
      with open(__file__, 'rb') as f:
          print f.read(16)
      
      #and it also works with StringIO objects
      sio = StringIO.StringIO('1,2\n3,4')
      with open(sio, 'rb') as f:
          reader = csv.reader(f)
          output = []
          for row in reader:
              output.append(row)
          print output
      

      这个输出:

      import StringIO
      [['1', '2'], ['3', '4']]
      

      【讨论】:

      • 感谢您的指点 - 我现在可以使用 mock 进行这项工作了 :)
      猜你喜欢
      • 1970-01-01
      • 2013-04-05
      • 1970-01-01
      • 1970-01-01
      • 2020-04-16
      • 2018-03-31
      • 1970-01-01
      • 1970-01-01
      • 2011-07-02
      相关资源
      最近更新 更多