【问题标题】:Running unit tests on nested functions对嵌套函数运行单元测试
【发布时间】:2010-09-24 12:34:09
【问题描述】:

我来自 Java 世界,您可以在其中隐藏变量和函数,然后使用反射对它们运行单元测试。我使用嵌套函数来隐藏我的类的实现细节,这样只有公共 API 是可见的。我正在尝试针对这些嵌套函数编写单元测试,以确保在开发时不会破坏它们。我曾尝试调用其中一个嵌套函数,例如:

def outer():
    def inner():
        pass

outer.inner()

导致错误消息:

AttributeError: 'function' 对象没有属性 'inner'

我有没有办法针对这些嵌套函数编写单元测试?如果没有,有没有办法像类变量一样通过在它们前面加上__来触发函数名称的名称转换?

【问题讨论】:

    标签: python testing closures


    【解决方案1】:

    我遇到了一个有趣的案例,我能够使用 MagicMock 对嵌套函数运行单元测试。关键是嵌套函数作为参数发送给我可以访问的另一个函数。为了保持OP的例子:

    # my_app.py
    
    def util(func):
        # run some logic using func
    
    def outer():
        def inner():
            pass
    
        util(inner)
    

    能够模拟 util 函数允许访问调用参数,在这种情况下恰好是 inner 函数:

    def test_inner
        # Arrange
        mock_util = MagicMock()
    
        with patch.object(my_app, 'util', mock_util):
            outer() # run the outer function to capture the util call with MagicMock
    
        inner_function = mock_util.call_args[0][0]
    
        # Act
        inner_function() # run the inner function for testing
    
        # Assert
        # make whatever assertions you would like
    

    这次我很幸运,因为我不需要更改我被要求测试的代码。 @Victor-Barroso's answer 为那些正在寻找选择的人提供了一种有意的方式来做类似的事情。虽然,如果您要更改代码,您也可以关注@Dustin's advice 或任何其他关于此问题的好的答案。

    【讨论】:

      【解决方案2】:

      我写了一个小助手模块,它允许这样做:

      嵌套函数示例:

      def f(v1):
        v2 = 1
        def g(v3=2):
          return v1 + v2 + v3 + 4
        def h():
          return 16
        return g() + h() + 32
      
      class C(object):
        def foo(self):
          def k(x):
            return [ self, x ]
          return k(3)
      
      def m():
        vm = 1
        def n(an=2):
          vn = 4
          def o(ao=8):
            vo = 16
            return vm + an + vn + ao + vo
          return o()
        return n()
      

      这些可以使用这种代码进行单元测试:

      import unittest
      from nested import nested
      
      class TestNested(unittest.TestCase):
        def runTest(self):
          nestedG = nested(f, 'g', v1=8, v2=1)
          self.assertEqual(nestedG(2), 15)
          nestedH = nested(f, 'h')
          self.assertEqual(nestedH(), 16)
          nestedK = nested(C.foo, 'k', self='mock')
          self.assertEqual(nestedK(5), [ 'mock', 5 ])
          nestedN = nested(m, 'n', vm=1)
          nestedO = nested(nestedN, 'o', vm=1, an=2, vn=4)
          self.assertEqual(nestedO(8), 31)
      
      def main(argv):
        unittest.main()
      
      if __name__ == '__main__':
        import sys
        sys.exit(main(sys.argv))
      

      小助手模块nested 如下所示:

      import types
      
      def freeVar(val):
        def nested():
          return val
        return nested.__closure__[0]
      
      def nested(outer, innerName, **freeVars):
        if isinstance(outer, (types.FunctionType, types.MethodType)):
          outer = outer.func_code
        for const in outer.co_consts:
          if isinstance(const, types.CodeType) and const.co_name == innerName:
            return types.FunctionType(const, globals(), None, None, tuple(
                freeVar(freeVars[name]) for name in const.co_freevars))
      

      【讨论】:

        【解决方案3】:

        我也有同样的疑问,并找到了一种方法来测试内部函数。

        def outer():
            def inner():
                pass
        
            if __debug__:
                test_inner(inner)
                # return
        
        def test_inner(f):
             f() # this calls the inner function
        
        outer()
        

        基本上您可以将内部函数作为参数发送到外部并根据需要对其进行测试。当调用 outer() 时,您的测试将运行,并且由于它是一个闭包,它将保留外部函数的任何额外属性(如变量)。使用列表,您可以发送任意数量的函数。要忽略 if,一个选项是像这样运行代码:

        python -O code.py
        

        【讨论】:

          【解决方案4】:

          无法从外部函数对象获取内部函数(参见其他回复!)。然而,单元测试和闭包(至少对我而言)都为开发人员带来了惊人的性能提升。我们可以两者兼得吗?我们可以单独测试嵌套函数吗?

          不容易。

          然而,这似乎可以通过使用 python 模块解析器、ast 或标记器来分割代码本身、提取内部函数(通过嵌套的某些路径)并允许测试以封闭状态运行它们来实现函数(封闭名称的值)和更多嵌套函数的存根/模拟(在测试目标中定义)。

          有人知道这样的事情吗?谷歌搜索没有找到任何东西。

          【讨论】:

            【解决方案5】:

            我认为没有任何机会从 extern 命名空间访问 inner()。

            然而,在我看来,你保持 inner() 嵌套的事实意味着唯一真正重要的“合同”是 external() 的合同。 inner() 是实现的一部分,您不应该测试实现。 如果您真的想测试 inner(),请使用将涉及 inner() 的所有功能的数据对 outer() 进行广泛的测试。

            【讨论】:

            • 我相信可以访问cpython中的内部函数。下面有一个答案,它准确地说明了如何做到这一点。
            【解决方案6】:

            Python 约定是使用前导下划线命名“私有”函数和方法。当您看到前导下划线时,您就知道不要尝试使用它。

            记住,Python is not Java

            【讨论】:

            • 这如何回答这个问题?是否有很好的解决方案可以隔离此类内部功能以进行单元测试?即使没有从外部函数对象访问内部,这似乎也可以通过使用 python 模块解析器、ast 或标记器来分割代码本身来实现。显然,这比编写代码更容易陈述。 :-)
            • 它回答了这个问题,因为没有测试私有的实现细节。您可以依靠测试覆盖工具来告诉您公共接口的测试是否充分地执行了私有实现。
            • @Tim:基于问题中的最后一句话,如果可以将其标记为私有,则作者可以使用非内部函数。因此,了解此约定很有用。
            【解决方案7】:

            inner 直到 external 才存在。您应该将 inner 移至顶层函数以实现可测试性,或者让外部测试测试其自身和内部的所有可能执行路径。

            请注意,内部函数不是一个简单的函数,它是一个闭包。考虑这种情况:

            def outer(a):
                b = compute_something_from(a)
                def inner():
                    do_something_with(a, b)
            

            这是标准的可测试性权衡。如果你的cyclomatic complexity 太高,你的测试就会太多。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-08-22
              • 2020-01-26
              • 2021-09-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-09-29
              相关资源
              最近更新 更多