Как выполнить модульное тестирование функций записи файлов с помощью python unittest - PullRequest
60 голосов
/ 15 октября 2010

У меня есть функция Python, которая записывает выходной файл на диск.

Я хочу написать для него модульный тест с использованием модуля юнит-теста Python.

Как мне утверждать равенство файлов? Я хотел бы получить ошибку, если содержимое файла отличается от ожидаемого + список различий. Как и в выводе команды unix diff.

Есть ли официальный / рекомендуемый способ сделать это?

Ответы [ 6 ]

52 голосов
/ 15 октября 2010

Я предпочитаю, чтобы выходные функции явно принимали файл handle (или файлоподобный object ), а не принимали файл name и открывали файлсамих себя.Таким образом, я могу передать объект 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 предполагаетсодержимое тестового вывода может помещаться в основную память.Для очень больших выходных данных вы можете использовать подход временного файла (например, tempfile.SpooledTeilitaryFile ).

42 голосов
/ 15 октября 2010

Самое простое - написать выходной файл, затем прочитать его содержимое, прочитать содержимое золотого (ожидаемого) файла и сравнить его с простым равенством строк.Если они одинаковые, удалите выходной файл.Если они отличаются, выдвиньте утверждение.

Таким образом, когда тесты завершены, каждый неудачный тест будет представлен выходным файлом, и вы можете использовать сторонний инструмент для сравнения их с золотомфайлы (Beyond Compare отлично подходит для этого).

Если вы действительно хотите предоставить свой собственный вывод diff, помните, что в Python stdlib есть модуль difflib.Новая поддержка unittest в 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)
16 голосов
/ 14 ноября 2014
import filecmp

Тогда

self.assertTrue(filecmp.cmp(path1, path2))
2 голосов
/ 15 октября 2010

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

Если вы напишите метод генератора , который возвращает каждую строку содержимого, то вы можете использовать метод обработки файлов, который открывает файл и вызывает 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)
1 голос
/ 12 апреля 2019

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

Предположим, у вас есть этот "удивительный" фрагмент программного обеспечения в файле с именем main.py:

"""
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.")

Чтобы протестировать метод write_to_file, вы можете написать что-то подобное в файле в том же файле.папка с именем test_main.py:

"""
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")
0 голосов
/ 18 октября 2010

На основании предложений я сделал следующее.

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, так как у меня много функций, которые нужны для чтения / записи файлов, поэтому мне действительно нужен метод assert многократного использования. Теперь в моих тестах я бы создал подкласс MyTestCase вместо unittest.TestCase.

Что вы думаете об этом?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...