datadriventests / ddt

Data-Driven Tests for Python Unittest
http://ddt.readthedocs.org/
MIT License
440 stars 113 forks source link

Allow unpacking test data tuples into multiple arguments to the test case #3

Closed txels closed 10 years ago

txels commented 12 years ago

Right now, if you want to pass several arguments to your test, you typically provide a tuple for each test case, and have to unpack them inside the test method, such as:

@data((1,2,3), (4,5,9))
def test_things_with_3_numbers(self, arg):
    op1, op2, addition = arg
    ...

A nice improvement could be if ddt did this unpacking for you:

@packed_data((1,2,3), (4,5,9))
def test_things_with_3_numbers(self, op1, op2, addition):
    ...
santtu commented 12 years ago

I think that is more useful, since it removes need for one (extra) unpack. Alternatively it might be possible to pass a dict, which then would be used to match based on argument name (e.g. kwargs-like).

However this probably would lead to three different cases:

This would mean that @data(1,2,3) is interpreted as call to testfunc three times with one argument, @data((1,2),(3,4)) would be two calls, each with two arguments, @data({'a':1},{'b':2}) calls testfunc twice, once as testfunc(a=1) and second time as testfunc(b=1).

On the other hand, if you really wanted to pass a tuple, it would need to be wrapped in a tuple like @data(((1,),),(2,),)) would call testfunc((1,)) and testfunc((2,)). With the same logic to pass a dict would need @data(({...},), ...).

This would leave case of needing to pass both _args and *_kwargs at the same time unsolved, requiring a custom wrapper, but other cases would be covered by argument unboxing as above.

An alternative way would be to provide decorators that would do this and leave @data itself to pass only single value, always. Developer could then choose the correct underlying decorator to unbox data. Like this:

  @data(1,2,3)
  def test_single_value(self, value):
    ...

  @data((1,2), (2,3))
  @args
  def test_two_values(self, value_1, value_2):
    ...

  @data({'a':2}, {'b':3})
  @kwargs 
  def test_named_args(self, a=None, b=None):
    ...

Or perhaps provide @data in different flavors, e.g. @data, @data_args, @data_kwargs and make these pre-format data to fill a single form (maybe (args, kwargs) tuple)?

txels commented 12 years ago

I like the idea of the extra decorator. Why not this?:

  @unpack
  @data((1,2), (2,3))
  def test_two_values(self, value_1, value_2):
     ...

  @unpack
  @data({'a':2, 'b':3}, {'a':6})
  def test_named_args(self, a=None, b=None):
    ...

A single unpack decorator that unpacks dicts as positional arguments and tuples as keyword arguments. Although in the context of tests I am not sure about the value of keyword arguments, so I would implement a simple tuple unpacker first.

santtu commented 12 years ago

+1 for @unpack, I think it is a clean and simple and covers at least for now what I think is a majority of cases where the simple single-value (e.g. current behavior) test caller isn't enough.

OTOH; I think the order has to be

  @data(...)
  @unpack 
  def abc(...): ...

as it is turned into abc = data(unpack(...)) and the @ddtdecorator needs to see the @data decorator('s result) first.

txels commented 12 years ago

I do not think the order matters, as the method decorators are just adding information to the method that will later get processed by the class decorator. For some reason having unpack first looks more natural to me.

santtu commented 12 years ago

I trust you, I didn't check how @data works :-) A lot of decorators do wrap the function in a new function, which effectively is a delegate for the original one... on the other hand, it is all coming back to be now.. @data uses setattr on the original method, and doesn't delegate... so in that case, of course, it doesn't matter.

@unpack itself would have to be a delegate decorator, and would have to pass __name__ upwards to let @ddtpick up the correct name (in case the data __name__ wasn't there).

What would happen if @unpack is used multiple times?

    @unpack 
    @unpack 
    @data(a, b)
    def test_func(...): ...

This would turn by @ddt into:

    test_func_a = unpack(unpack(a))
    test_func_b = unpack(unpack(b))

Perhaps it is easiest to just concluce that @unpack will work only with a single argument to unbox, in which case unpack(unpack(..)) wouldn't be legal.

txels commented 12 years ago

Well, you have seen that I am using decorators in a non-typical way. I was planning for unpack to work the same way, which means it will just add an additional attribute to the original method (say %unpack=True) that would be used by the ddt decorator to call feed_data in a special way.

If I do it this way, no matter how many unpacks you use it won't make a difference.

txels commented 10 years ago

Solved in #16

arnm commented 9 years ago

I believe this could be enhanced further. TestNG's @DataProvider is what this really should be.

I really would like to do somethings like this:

def get_data():
    return ( (1, 2), (2, 3,), )

@ddt
class TestMe(unittest.TestCase):
    @unpack
    @data(get_data())
    def test_func(self, first, second):
        # each tuple in the returned tuple is unpacked
        pass

I want more control of what data is passed into the test. Being able to generate all inputs for a test programmatically from a function, like TestNG does, would be a great way to go.

viswagb commented 5 years ago

@arnm is your above comments any progress, im too looking same..in ddt