单元测试 - 用StringIO对象替换文件路径

时间:2016-12-04 20:14:00

标签: python unit-testing csv stream stringio

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

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

似乎我的选择是:

  1. 重写所有现有代码,以便将文件句柄传递给解析函数 - 这真是太痛苦了!
  2. 使用mock.patch()替换open()方法 - 这应该有效,但似乎比这个任务要求的更复杂!
  3. 做一些我尚未想到的事情,但我确信必须存在!
  4.     import csv
        def parse_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

    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)

3 个答案:

答案 0 :(得分: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']]

答案 1 :(得分:1)

如果您不想更改接口以接受#include "stdafx.h" #include <iostream> #include <string> using namespace std; int main(string name) { cout << "What is your name?" << endl; cin >> name; cout << endl << "Your name is " << name << "." << endl; return 0; } 等开放文件对象,请查看testfixtures module。我已经用它来管理单元测试的文件和目录,虽然我通常更喜欢传入StringIO个对象。

如果您不喜欢这样,那么修补StringIO听起来像是一个合理的策略。我自己还没有尝试过。

答案 2 :(得分:0)

对于将来寻找此事的其他人,我能够使用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)