Running unit tests on nested functions

2020-03-01 12:33发布

问题:

I come from the Java world, where you can hide variables and functions and then run unit tests against them using reflection. I have used nested functions to hide implementation details of my classes so that only the public API is visible. I am trying to write unit tests against these nested functions to make sure that I don't break them as I develop. I have tried calling one of the nested functions like:

def outer():
    def inner():
        pass

outer.inner()

which results in the error message:

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

Is there a way for me to write unit tests against these nested functions? If not, is there a way to trigger the name munging for function names like you can for class variables by prefixing them with __?

回答1:

The Python convention is to name "private" functions and methods with a leading underscore. When you see a leading underscore, you know not to try and use it.

Remember, Python is not Java.



回答2:

inner doesn't exist until outer makes it. You should either move inner up to a toplevel function for testability, or have the outer test test all the possible execution paths of itself and inner.

Do note that the inner function isn't a simple function, it's a closure. Consider this case:

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

That's the standard testability trade-off. If your cyclomatic complexity is too high, your tests will be too numerous.



回答3:

I don't think that there is any chance to access inner() from the extern namespace.

However, in my opinion the fact that you keep inner() nested implies that the only "contract" that really matters is outer()'s one. inner() is part of the implementation, and you shouldn't want to test the implementation. If you really want to test inner(), do extensive tests on outer() with data that will involve all the functionalities of inner().



回答4:

No way to get inner function from outer function object (see the other replies!). Yet both unit tests and closures have made (for me at least) amazing developer performance improvements. Can we have both? Can we test nested functions in isolation?

Not easily.

However, such could seemingly be achieved with use of python modules parser, ast, or tokenizer to dice up the code itself, extracting inner functions (by some path through the nesting), and allowing tests to run them with state from enclosing functions (values for closed-over names) and stubs/mocks for more-nested functions (defined within the test target).

Anybody know of anything like this? Googling failed to find anything.



回答5:

I had the same doubt and found a way to get tests going for inner functions.

def outer():
    def inner():
        pass

    if __debug__:
        test_inner(inner)
        # return

def test_inner(f):
     f() # this calls the inner function

outer()

Basically you can send the inner function as a parameter to the outside and test it as you wish. When calling outer(), your test will run, and since it's a closure, it will preserve any extra property from the outer function (like variables). Using a list, you can send as many functions as you wish. To ignore the if, an option is to run the code like that:

python -O code.py


回答6:

I have written a small helper module which allows exactly this:

Examples of nested functions:

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

These can be unit tested using this kind of code:

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

The small helper module nested looks like this:

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