代码之家  ›  专栏  ›  技术社区  ›  Mark Heath

断言在Python单元测试中调用了方法

  •  57
  • Mark Heath  · 技术社区  · 14 年前

    假设我在Python单元测试中有以下代码:

    aw = aps.Request("nv1")
    aw2 = aps.Request("nv2", aw)
    

    有没有一种简单的方法可以断言 aw.Clear() )是在第二行测试中调用的?例如,是否存在这样的情况:

    #pseudocode:
    assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))
    
    5 回复  |  直到 5 年前
        1
  •  104
  •   rdas    5 年前

    我用 Mock (现在) unittest.mock 在PY3.3+上)为此:

    from mock import patch
    from PyQt4 import Qt
    
    
    @patch.object(Qt.QMessageBox, 'aboutQt')
    def testShowAboutQt(self, mock):
        self.win.actionAboutQt.trigger()
        self.assertTrue(mock.called)
    

    对于您的案例,可能如下所示:

    import mock
    from mock import patch
    
    
    def testClearWasCalled(self):
       aw = aps.Request("nv1")
       with patch.object(aw, 'Clear') as mock:
           aw2 = aps.Request("nv2", aw)
    
       mock.assert_called_with(42) # or mock.assert_called_once_with(42)
    

    mock支持许多有用的特性,包括修补对象或模块的方法,以及检查是否调用了正确的东西等等。

    Caveat emptor! (买主当心!)

    如果你误入歧途 assert_called_with (到) assert_called_once assert_called_wiht )您的测试可能仍然在运行,因为mock会认为这是一个mocked函数,并且很高兴地继续进行,除非您使用 autospec=true . 欲了解更多信息,请阅读 assert_called_once: Threat or Menace .

        2
  •  17
  •   Stefan Collier    7 年前

    是的,如果您使用的是python 3.3+。您可以使用内置的 unittest.mock 调用了断言方法。对于python 2.6+,使用滚动后端端口 Mock 是一样的。

    下面是一个简单的例子:

    from unittest.mock import MagicMock
    aw = aps.Request("nv1")
    aw.Clear = MagicMock()
    aw2 = aps.Request("nv2", aw)
    assert aw.Clear.called
    
        3
  •  13
  •   Glenn Maynard    14 年前

    我不知道有什么内置的。实现起来非常简单:

    class assertMethodIsCalled(object):
        def __init__(self, obj, method):
            self.obj = obj
            self.method = method
    
        def called(self, *args, **kwargs):
            self.method_called = True
            self.orig_method(*args, **kwargs)
    
        def __enter__(self):
            self.orig_method = getattr(self.obj, self.method)
            setattr(self.obj, self.method, self.called)
            self.method_called = False
    
        def __exit__(self, exc_type, exc_value, traceback):
            assert getattr(self.obj, self.method) == self.called,
                "method %s was modified during assertMethodIsCalled" % self.method
    
            setattr(self.obj, self.method, self.orig_method)
    
            # If an exception was thrown within the block, we've already failed.
            if traceback is None:
                assert self.method_called,
                    "method %s of %s was not called" % (self.method, self.obj)
    
    class test(object):
        def a(self):
            print "test"
        def b(self):
            self.a()
    
    obj = test()
    with assertMethodIsCalled(obj, "a"):
        obj.b()
    

    这要求对象本身不会修改self.b,这几乎总是正确的。

        4
  •  7
  •   Community Sam Holder    7 年前

    是的,我可以给你提纲,但我的蟒蛇有点生锈,我太忙了,无法详细解释。

    基本上,您需要在调用原始对象的方法中放置一个代理,例如:

     class fred(object):
       def blog(self):
         print "We Blog"
    
    
     class methCallLogger(object):
       def __init__(self, meth):
         self.meth = meth
    
       def __call__(self, code=None):
         self.meth()
         # would also log the fact that it invoked the method
    
     #example
     f = fred()
     f.blog = methCallLogger(f.blog)
    

    这个 StackOverflow answer 关于Callable可以帮助您理解上面的内容。

    更详细地说:

    尽管答案被接受了,但由于与格伦的讨论很有趣,并且有几分钟的空闲时间,我想进一步讨论我的答案:

    # helper class defined elsewhere
    class methCallLogger(object):
       def __init__(self, meth):
         self.meth = meth
         self.was_called = False
    
       def __call__(self, code=None):
         self.meth()
         self.was_called = True
    
    #example
    class fred(object):
       def blog(self):
         print "We Blog"
    
    f = fred()
    g = fred()
    f.blog = methCallLogger(f.blog)
    g.blog = methCallLogger(g.blog)
    f.blog()
    assert(f.blog.was_called)
    assert(not g.blog.was_called)
    
        5
  •  4
  •   Max Shawabkeh    14 年前

    你可以模仿 aw.Clear 手动或使用类似的测试框架 pymox . 手动操作时,您可以使用如下方法:

    class MyTest(TestCase):
      def testClear():
        old_clear = aw.Clear
        clear_calls = 0
        aw.Clear = lambda: clear_calls += 1
        aps.Request('nv2', aw)
        assert clear_calls == 1
        aw.Clear = old_clear
    

    使用Pymox,你可以这样做:

    class MyTest(mox.MoxTestBase):
      def testClear():
        aw = self.m.CreateMock(aps.Request)
        aw.Clear()
        self.mox.ReplayAll()
        aps.Request('nv2', aw)