在python中,如何在没有返回值的函数上进行单元测试?

时间:2013-04-11 03:46:31

标签: python unit-testing

我是一名蟒蛇。在这些日子里,我正在驾驶自己对我项目中的一些核心模块进行更完整的单元测试。 因为我们总是用方法'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

6 个答案:

答案 0 :(得分:9)

如上所述,您可以使用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")

这会在HelloTest上修补bar方法,并将其替换为记录针对它的调用的模拟对象。

嘲弄是一个兔子洞。只有在你必须这样做时才这样做,因为它确实使你的测试变得脆弱。例如,您永远不会注意到模拟对象的API更改。

答案 1 :(得分:8)

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

在Python中,您将使用Mock package来模拟。

答案 2 :(得分:8)

我不太明白为什么每个人都想检查foo是否会调用吧。

Foo具有一些功能,需要测试此功能。如果foo使用bar来做这件事,那不应该是我关注的问题。

期望的结果是,在调用foo(msg)之后,是msg.upper()被发送到stdout。

您可以redirect stdout to a string buffer并检查此字符串缓冲区的内容是否符合您的预期。

示例:

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()

您也可以为stdin执行此操作(并写入stdin以模拟某些输入)并且如果您需要执行任何特殊操作,则可以继承TestIOWrapper,例如允许将非unicode文本发送到sys.stdout使用sys.stdout.buffer(Python 2与Python 3)。 在this SO answer中有一个例子。 当您(仍然)仅使用Python 2时,使用StringIO可能比使用io模块更好。

答案 3 :(得分:3)

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

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)

答案 4 :(得分:2)

在Python 3中,您可以tell print where to print to

  

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

所以添加一个可选参数:

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

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

在Python 2中,它有点麻烦,但你可以redirect stdout to a StringIO buffer

import StringIO
import sys

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

# run unit tests

sys.stdout = sys.__stdout__

# check the contents of `out`

答案 5 :(得分:2)

感谢@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()