在嵌套函数中运行单元测试

17 投票
7 回答
15525 浏览
提问于 2025-04-11 19:38

我来自Java的世界,在那里你可以隐藏变量和函数,然后通过反射来对它们进行单元测试。我曾经使用嵌套函数来隐藏我类的实现细节,这样只有公共接口是可见的。我现在想对这些嵌套函数进行单元测试,以确保在开发过程中不会破坏它们。我尝试像这样调用其中一个嵌套函数:

def outer():
    def inner():
        pass

outer.inner()

结果却出现了错误信息:

AttributeError: 'function' object has no attribute 'inner'

我能否对这些嵌套函数进行单元测试?如果不能,有没有办法像对类变量那样,通过在函数名前加上__来触发名称混淆?

7 个回答

6

我写了一个小工具模块,可以实现这个功能:

下面是一些嵌套函数的例子:

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

内层函数在外层函数创建之前是不存在的。你应该把内层函数提到顶层,这样更方便测试,或者让外层函数的测试覆盖所有可能的执行路径,包括它自己和内层函数。

需要注意的是,内层函数并不是一个简单的函数,它是一个闭包。想象一下这个情况:

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

这就是测试性和复杂性之间的标准权衡。如果你的圈复杂度太高,你的测试就会变得非常多。

2

在Python编程中,有个约定是把“私有”的函数和方法前面加一个下划线。看到这个下划线,你就知道最好不要去使用它。

记住,Python和Java是不一样的

撰写回答