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

Yar*_*kee 19 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
Run Code Online (Sandbox Code Playgroud)

ayc*_*dee 10

正如另一个提到的答案,您可以使用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")
Run Code Online (Sandbox Code Playgroud)

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

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

  • 你知道……我真的不知道?看起来我在 6 年前犯了一个错误:-) 它应该是这样的:`self.assertEqual(mock_bar.call_args[0][0], "SOME STRING")` 我会更新答案。 (2认同)

Jon*_*yJD 9

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

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

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

您可以将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()
Run Code Online (Sandbox Code Playgroud)

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


Jor*_*son 8

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

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

  • 好吧,如果他使用的是Python 3以下的任何东西,那么模拟打印并不是那么简单.他可以模拟sys.stdout,但他必须改变吧 (2认同)

Yar*_*kee 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()
Run Code Online (Sandbox Code Playgroud)


Zan*_*tsu 5

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

class HelloTest(object):

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

    def bar(self):
        print self.msg
Run Code Online (Sandbox Code Playgroud)

单元测试是:

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