Unittesting - замена путей к файлам объектами StringIO

Я пытаюсь выполнить unittest функции синтаксического анализа, которые принимают путь к файлу и возвращают часть содержимого файла. Я хотел бы иметь возможность передавать этим функциям строки данных для целей тестирования.

Я понимаю, что могу передать csv.reader () либо StringIO, либо file_handle (например, csv.reader (StringIO ("my, data") или csv.reader (open (file))), но я не вижу способа что я могу передать объект StringIO вместо filepath, поскольку open (StringIO ("my, data")) не работает. Точно так же я хочу иметь логику открытия / закрытия файла в этих методах синтаксического анализа, а не в основной части моего кода, так как это загромождает мой основной код, а также означает, что мне придется переписать все интерфейсы ввода-вывода файлов!

Кажется, мой выбор:

  1. Перепишите весь существующий код так, чтобы он передавал дескрипторы файлов функциям синтаксического анализа - это настоящая боль!
  2. Используйте mock.patch () для замены метода open () - это должно работать, но кажется более сложным, чем того требует эта задача!
  3. Сделайте то, о чем я еще не думал, но убежден, что должно существовать!

    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)


person David258    schedule 04.12.2016    source источник
comment
почему бы просто не записать тестовые примеры на жесткий диск и не передать к ним пути?   -  person kmaork    schedule 04.12.2016
comment
Вот как работает код в настоящее время - я пытаюсь избежать этого, потому что: A Я не хочу отслеживать множество очень маленьких текстовых файлов B Синтаксический анализ имеет множество параметров конфигурации - мне легко манипулировать строками в коде, чтобы имитировать их, но для этого с файлом потребуются десятки, что делает мою сборку беспорядочной   -  person David258    schedule 04.12.2016


Ответы (3)


Вы можете использовать временные файлы.

Если вы действительно предпочитаете не использовать жесткий диск, вы можете использовать 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']]
person kmaork    schedule 04.12.2016
comment
Спасибо за указатели - теперь у меня это работает, используя макет :) - person David258; 06.12.2016


Для других, ищущих это в будущем, я смог использовать 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)
person David258    schedule 05.12.2016