32

我是蟒蛇。这些天来,我正在推动自己对项目中的一些核心模块进行更完整的单元测试。由于我们总是使用“assertEqual”、“assertTrue”等方法进行单元测试,这些方法都需要被测试函数的返回值,我想知道如何在没有返回值的情况下对某些函数进行简单的单元测试。

我想在这里展示一个小例子,如何在 HelloTest 中测试函数 def foo(self, msg)?

class HelloTest(object):
    def foo(self, msg):
        MSG = msg.upper()
        self.bar(MSG)

    def bar(self, MSG):
        print MSG
4

6 回答 6

15

正如提到的另一个答案,您可以使用 Python 模拟库对函数/方法的调用进行断言

from mock import patch
from my_module import HelloTest
import unittest

class TestFoo(unittest.TestCase):

    @patch('hello.HelloTest.bar')
    def test_foo_case(self, mock_bar):

        ht = HelloTest()

        ht.foo("some string")
        self.assertTrue(mock_bar.called)
        self.assertEqual(mock_bar.call_args[0][0], "SOME STRING")

这修补了barHelloTest 上的方法,并将其替换为记录对它的调用的模拟对象。

嘲笑有点像兔子洞。仅在绝对必要时才这样做,因为它确实会使您的测试变得脆弱。例如,您永远不会注意到模拟对象的 API 更改。

于 2013-04-11T08:10:40.620 回答
13

我不太明白为什么每个人都想检查 foo 调用 bar。

Foo 有一些功能,这个功能需要测试。如果 foo 使用 bar 来执行此操作,我不应该担心。

期望的结果是在foo(msg)调用之后,将其msg.upper()发送到标准输出。

您可以将 stdout 重定向到字符串缓冲区并检查此字符串缓冲区的内容是否符合您的预期。

例子:

import sys
import unittest
from io import TextIOWrapper, BytesIO

class TestScript(unittest.TestCase):
    def setUp(self):
        self._old_stdout = sys.stdout
        sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding)

    def _output(self):
        self._stdout.seek(0)
        return self._stdout.read()

    def test_foo(self):
        hello_test = HelloTest()
        hello_test.foo("blub")
        self.assertEqual(self._output(), "BLUB")

    def tearDown(self):
        sys.stdout = self._old_stdout
        self._stdout.close()

您也可以对标准输入执行此操作(并写入标准输入以模拟某些输入),如果您需要做任何特殊的事情,您可以继承 TestIOWrapper,例如允许在sys.stdout不使用的情况下发送非 unicode 文本sys.stdout.buffer(Python 2 与 Python 3 )。在这个 SO answer中有一个例子。当您(仍然)仅使用 Python 2 时,使用StringIO可能比使用 io 模块更好。

于 2013-10-14T10:51:22.830 回答
10

在这种特殊情况下,我会模拟打印,然后在我的断言中使用模拟。

在 Python 中,您将使用Mock 包进行模拟。

于 2013-04-11T03:50:57.323 回答
7

感谢@Jordan 的介绍,我编写了这个代码并认为它​​是 HelloTest.foo 的一个可行的单元测试

from mock import Mock
import unittest


class HelloTestTestCase(unittest.TestCase):
    def setUp(self):
        self.hello_test = HelloTest()

    def tearDown(self):
        pass

    def test_foo(self):
        msg = 'hello'
        expected_bar_arg = 'HELLO'
        self.hello_test.bar = Mock()

        self.hello_test.foo(msg)
        self.hello_test.bar.assert_called_once_with(expected_bar_arg)


if __name__ == '__main__':
    unittest.main()
于 2013-04-11T08:16:22.413 回答
5

您的代码可以如下所示,其执行与上述相同的任务:

class HelloTest(object):

    def foo(self, msg):
        self.msg = msg.upper()
        self.bar()

    def bar(self):
        print self.msg

单元测试是:

from hello import HelloTest
import unittest

class TestFoo(unittest.TestCase):
    def test_foo_case(self):
        msg = "test"
        ob = HelloTest()
        ob.foo(msg)
        expected = "TEST"
        self.assertEqual(ob.msg, expected)

if __name__ == '__main__':
    unittest.main(exit=False)
于 2013-04-11T04:30:29.207 回答
3

在 Python 3 中,您可以告诉print打印到哪里

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

所以添加一个可选参数:

def bar(self, MSG, file=sys.stdout):
    print(MSG, file=file)

在正常使用中,它将打印到标准输出,但对于单元测试,您可以传递自己的文件。

在 Python 2 中它有点混乱,但您可以将 stdout 重定向到 StringIO 缓冲区

import StringIO
import sys

out = StringIO.StringIO()
sys.stdout = out

# run unit tests

sys.stdout = sys.__stdout__

# check the contents of `out`
于 2013-04-11T03:53:57.370 回答