如何使用python unittest对函数进行单元测试

jan*_*jan 64 python unit-testing file

我有一个Python函数,可以将输出文件写入磁盘.

我想使用Python unittest模块为它编写单元测试.

我该如何断言文件的相等性?如果文件内容与预期的一个+差异列表不同,我想得到一个错误.与unix diff命令的输出一样.

有没有官方/推荐的方式呢?

got*_*nes 54

我更喜欢让输出函数显式接受文件句柄(或类文件对象),而不是接受文件并自己打开文件.这样,我可以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")
Run Code Online (Sandbox Code Playgroud)

像这样的代码

##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")
Run Code Online (Sandbox Code Playgroud)

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

请注意,使用StringIO假定测试输出的内容可以适合主存储器.对于非常大的输出,您可以使用临时文件方法(例如,tempfile.SpooledTemporaryFile).

  • 这比将文件写入磁盘更好.如果您运行大量的单元测试,IO到磁盘会导致各种问题,尤其是尝试清理它们.我有测试写入磁盘,tearDown删除写入的文件.测试可以一次正常工作,然后在全部运行时失败.至少在Win机器上使用Visual Studio和PyTools.还有,速度. (2认同)
  • 虽然这是测试单独功能的一个很好的解决方案,但在测试您的程序提供的实际接口(例如 CLI 工具)时仍然很麻烦。 (2认同)

Ned*_*der 45

最简单的方法是编写输出文件,然后读取其内容,读取gold(预期)文件的内容,并将它们与简单的字符串相等进行比较.如果它们相同,请删除输出文件.如果它们不同,请提出断言.

这样,当测试完成后,每个失败的测试都将用输出文件表示,你可以使用第三方工具将它们与黄金文件区分开来(Beyond Compare非常适合这个).

如果你真的想提供自己的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)
Run Code Online (Sandbox Code Playgroud)

  • 不,总体上最好的方法不是写入一个可能很慢且容易出错的文件(prod env 可能与 test/CI env 完全不同,就像 Windows 与 OSX 一样),而是模拟对 `open 的调用` 如本页其他答案中所述,使用 `unittest.mock` (参见 Enrico M 的答案) (2认同)

Enr*_* M. 18

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

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

要测试该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")
Run Code Online (Sandbox Code Playgroud)

  • 这个答案就是我一直在寻找的;不是如何测试与文件中某些内容的相等性,而是如何测试“open”是否被成功调用,而不需要搞乱真实的文件 I/O。这个问题问的是不同的问题,但搜索我的问题首先把我带到了这里。 (2认同)

tbc*_*bc0 16

import filecmp
Run Code Online (Sandbox Code Playgroud)

然后

self.assertTrue(filecmp.cmp(path1, path2))
Run Code Online (Sandbox Code Playgroud)

  • 默认情况下,(https://docs.python.org/3/library/filecmp.html#filecmp.cmp) 会进行“浅层”比较,仅检查文件元数据(运行时间、大小等)。请在您的示例中添加 `shallow=False`。 (3认同)
  • 此外,结果被[缓存](https://docs.python.org/3/library/filecmp.html#filecmp.clear_cache)。 (3认同)