Baz
Baz

Reputation: 13135

Binding to different methods

I use the following function to add tests to my Test Cases.

def add_tests(cls, args, name_builder):
   for a in args:
      def tb(a):
         return lambda self: self.test_body(*a)
      setattr(cls, name_builder(a), tb(a))

At the moment I can use it like this:

class TestEqual(unittest.TestCase):
   def test_body(self, i, j):
      self.assertNotEquals(0, i-j)

add_tests(TestEqual, ((0,0),(1,1)), build_name_1)

How can I change add_tests in order to do something like this:

class TestRestart(unittest.TestCase):

   def test_mode_flow(self, mode):
       machine.set_mode(mode)
       self.assertTrue(machine.restart())

   def test_gear_flow(self, gear, speed):
       machine.set_gear(gear)
       machine.set_speed(speed)
       self.assertTrue(machine.restart())

add_tests(TestRestart, test_mode_flow, (mode.SILENT, mode.NOISY), build_name_mode)
add_tests(TestRestart, test_gear_flow, ((1,33),(4,22)), build_name_gear)

In other words, I want to remove the hardcoded call to self.test_body(*a) and instead pass in a method of choice as an argument.

Upvotes: 0

Views: 38

Answers (1)

Bakuriu
Bakuriu

Reputation: 101959

You can pass the name of the method to your add_tests function:

def add_tests(cls, method_name, name_builder, *args):
    method = getattr(cls, method_name)
    for arg in args:
        setattr(cls, name_builder(arg), lambda self, arg=arg: method(*arg))

Used as:

class TestRestart(unittest.TestCase):
    def test_mode_flow(self, mode):
        machine.set_mode(mode)
        self.assertTrue(machine.restart())

    def test_gear_flow(self, gear, speed):
        machine.set_gear(gear)
        machine.set_speed(speed)
        self.assertTrue(machine.restart())

add_tests(TestRestart, 'test_mode_flow', build_name_mode, mode.SILENT, mode.NOISY)
add_tests(TestRestart, 'test_gear_flow', build_name_gear, (1,33), (4,22))

An alternative is to pass the method directly:

def add_tests(cls, method, name_builder, *args):
    for arg in args:
        setattr(cls, name_builder(arg), lambda self, arg=arg: method(self, *arg))

And use it as:

add_tests(TestRestart, TestRestart.test_mode_flow, build_name_mode, mode.SILENT, mode.NOISY)
add_tests(TestRestart, TestRestart.test_gear_flow, build_name_gear, (1,33), (4,22))

An other option would be to use a decorator:

from functools import wraps

def add_tests(*args):
    def decorator(method):
        @wraps(method)
        def wrapper(self):
            for arg in args:
                method(self, *arg)
        return wrapper
    return decorator

and use it as:

class TestRestart(unittest.TestCase):
    @add_tests(mode.SILENT, mode.NOISY)
    def test_mode_flow(self, mode):
        machine.set_mode(mode)
        self.assertTrue(machine.restart())

    @add_tests((1, 33), (4, 22))
    def test_gear_flow(self, gear, speed):
        machine.set_gear(gear)
        machine.set_speed(speed)
        self.assertTrue(machine.restart())

However this packs all tests into a single method, which may not be what you want. However it's possible to modify add_tests so that the error message show which argument failed, thus providing the same level of information.

Upvotes: 1

Related Questions