如何使用python unittest对写入文件的函数进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3942820/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 13:27:20  来源:igfitidea点击:

How to do unit testing of functions writing files using python unittest

pythonunit-testingfile

提问by jan

I have a Python function that writes an output file to disk.

我有一个将输出文件写入磁盘的 Python 函数。

I want to write a unit test for it using Python unittest module.

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

How should I assert equality of files? I would like to get an error if the file content differs from the expected one + list of differences. As in the output of unix diff command.

我应该如何断言文件的平等?如果文件内容与预期的一个 + 差异列表不同,我想得到一个错误。就像在 unix diff 命令的输出中一样。

Is there any official/recommended way of doing that?

有没有官方/推荐的方法来做到这一点?

采纳答案by Ned Batchelder

The simplest thing is to write the output file, then read its contents, read the contents of the gold (expected) file, and compare them with simple string equality. If they are the same, delete the output file. If they are different, raise an assertion.

最简单的就是写输出文件,然后读取它的内容,读取黄金(预期)文件的内容,并用简单的字符串相等来比较它们。如果它们相同,请删除输出文件。如果它们不同,则提出断言。

This way, when the tests are done, every failed test will be represented with an output file, and you can use a 3rd-party tool to diff them against the gold files (Beyond Compare is wonderful for this).

这样,当测试完成时,每个失败的测试都将用一个输出文件表示,您可以使用第 3 方工具将它们与黄金文件进行比较(Beyond Compare 对此非常有用)。

If you really want to provide your own diff output, remember that the Python stdlib has the difflib module. The new unittest support in Python 3.1 includes an assertMultiLineEqualmethod that uses it to show diffs, similar to this:

如果您真的想提供自己的 diff 输出,请记住 Python stdlib 具有 difflib 模块。Python 3.1 中新的 unittest 支持包括一个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)

回答by Don Kirkby

You could separate the content generation from the file handling. That way, you can test that the content is correct without having to mess around with temporary files and cleaning them up afterward.

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

If you write a generator methodthat yields each line of content, then you can have a file handling method that opens a file and calls file.writelines()with the sequence of lines. The two methods could even be on the same class: test code would call the generator, and production code would call the file handler.

如果您编写一个生成每一行内容的生成器方法,那么您可以拥有一个文件处理方法,该方法可以打开一个文件并file.writelines()使用行序列进行调用。这两个方法甚至可以在同一个类中:测试代码将调用生成器,生产代码将调用文件处理程序。

Here's an example that shows all three ways to test. Usually, you would just pick one, depending on what methods are available on the class to test.

这是一个示例,显示了所有三种测试方法。通常,您会选择一个,这取决于类上可以测试的方法。

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)

回答by gotgenes

I prefer to have output functions explicitly accept a file handle(or file-like object), rather than accept a file nameand opening the file themselves. This way, I can pass a StringIOobject to the output function in my unit test, then .read()the contents back from that StringIOobject (after a .seek(0)call) and compare with my expected output.

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

For example, we would transition code like this

例如,我们会像这样转换代码

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

to code like this

像这样编码

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

This approach has the added benefit of making your output function more flexible if, for instance, you decide you don't want to write to a file, but some other buffer, since it will accept all file-like objects.

这种方法的额外好处是使您的输出函数更加灵活,例如,如果您决定不想写入文件,而是一些其他缓冲区,因为它将接受所有类似文件的对象。

Note that using StringIOassumes the contents of the test output can fit into main memory. For very large output, you can use a temporary fileapproach (e.g., tempfile.SpooledTemporaryFile).

请注意, usingStringIO假设测试输出的内容可以放入主内存。对于非常大的输出,您可以使用临时文件方法(例如,tempfile.SpooledTemporaryFile)。

回答by jan

Based on suggestions I did the following.

根据建议,我做了以下工作。

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)

I created a subclass MyTestCase as I have lots of functions that need to read/write files so I really need to have re-usable assert method. Now in my tests, I would subclass MyTestCase instead of unittest.TestCase.

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

What do you think about it?

你怎么看待这件事?

回答by tbc0

import filecmp

Then

然后

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

回答by Enrico M.

I always try to avoid writing files to disk, even if it's a temporary folder dedicated to my tests: not actually touching the disk makes your tests much faster, especially if you interact with files a lot in your code.

我总是尽量避免将文件写入磁盘,即使它是专用于我的测试的临时文件夹:实际上不接触磁盘会使您的测试更快,尤其是当您在代码中与文件进行大量交互时。

Suppose you have this "amazing" piece of software in a file called main.py:

假设您在名为 的文件中有这个“惊人”的软件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.")

To test the write_to_filemethod, you can write something like this in a file in the same folder called test_main.py:

要测试该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")