Bug 1047101 - Provide a way to do data-driven tests with Marionette test runner. r=jgriffin

This commit is contained in:
Julien Pagès 2014-08-30 09:55:00 -04:00
parent 982f59f883
commit f22fb74257
3 changed files with 153 additions and 0 deletions

View File

@ -85,8 +85,101 @@ def skip_if_b2g(target):
sys.stderr.write('skipping ... ')
return wrapper
def parameterized(func_suffix, *args, **kwargs):
"""
A decorator that can generate methods given a base method and some data.
**func_suffix** is used as a suffix for the new created method and must be
unique given a base method. if **func_suffix** countains characters that
are not allowed in normal python function name, these characters will be
replaced with "_".
This decorator can be used more than once on a single base method. The class
must have a metaclass of :class:`MetaParameterized`.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
class MyTestCase(MarionetteTestCase):
@parameterized("1", 5, named='name')
@parameterized("2", 6, named='name2')
def test_it(self, value, named=None):
print value, named
:param func_suffix: will be used as a suffix for the new method
:param \*args: arguments to pass to the new method
:param \*\*kwargs: named arguments to pass to the new method
"""
def wrapped(func):
if not hasattr(func, 'metaparameters'):
func.metaparameters = []
func.metaparameters.append((func_suffix, args, kwargs))
return func
return wrapped
def with_parameters(parameters):
"""
A decorator that can generate methods given a base method and some data.
Acts like :func:`parameterized`, but define all methods in one call.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
DATA = [("1", [5], {'named':'name'}), ("2", [6], {'named':'name2'})]
class MyTestCase(MarionetteTestCase):
@with_parameters(DATA)
def test_it(self, value, named=None):
print value, named
:param parameters: list of tuples (**func_suffix**, **args**, **kwargs**)
defining parameters like in :func:`todo`.
"""
def wrapped(func):
func.metaparameters = parameters
return func
return wrapped
def wraps_parameterized(func, func_suffix, args, kwargs):
"""Internal: for MetaParameterized"""
def wrapper(self):
return func(self, *args, **kwargs)
wrapper.__name__ = func.__name__ + '_' + str(func_suffix)
wrapper.__doc__ = '[%s] %s' % (func_suffix, func.__doc__)
return wrapper
class MetaParameterized(type):
"""
A metaclass that allow a class to use decorators like :func:`parameterized`
or :func:`with_parameters` to generate new methods.
"""
RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]')
def __new__(cls, name, bases, attrs):
for k, v in attrs.items():
if callable(v) and hasattr(v, 'metaparameters'):
for func_suffix, args, kwargs in v.metaparameters:
func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix)
wrapper = wraps_parameterized(v, func_suffix, args, kwargs)
if wrapper.__name__ in attrs:
raise KeyError("%s is already a defined method on %s" %
(wrapper.__name__, name))
attrs[wrapper.__name__] = wrapper
del attrs[k]
return type.__new__(cls, name, bases, attrs)
class CommonTestCase(unittest.TestCase):
__metaclass__ = MetaParameterized
match_re = None
failureException = AssertionError

View File

@ -0,0 +1,59 @@
from marionette_test import parameterized, with_parameters, MetaParameterized, \
MarionetteTestCase
class Parameterizable(object):
__metaclass__ = MetaParameterized
class TestDataDriven(MarionetteTestCase):
def test_parameterized(self):
class Test(Parameterizable):
def __init__(self):
self.parameters = []
@parameterized('1', 'thing', named=43)
@parameterized('2', 'thing2')
def test(self, thing, named=None):
self.parameters.append((thing, named))
self.assertFalse(hasattr(Test, 'test'))
self.assertTrue(hasattr(Test, 'test_1'))
self.assertTrue(hasattr(Test, 'test_2'))
test = Test()
test.test_1()
test.test_2()
self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)])
def test_with_parameters(self):
DATA = [('1', ('thing',), {'named': 43}),
('2', ('thing2',), {'named': None})]
class Test(Parameterizable):
def __init__(self):
self.parameters = []
@with_parameters(DATA)
def test(self, thing, named=None):
self.parameters.append((thing, named))
self.assertFalse(hasattr(Test, 'test'))
self.assertTrue(hasattr(Test, 'test_1'))
self.assertTrue(hasattr(Test, 'test_2'))
test = Test()
test.test_1()
test.test_2()
self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)])
def test_parameterized_same_name_raises_error(self):
with self.assertRaises(KeyError):
class Test(Parameterizable):
@parameterized('1', 'thing', named=43)
@parameterized('1', 'thing2')
def test(self, thing, named=None):
pass
def test_marionette_test_case_is_parameterizable(self):
self.assertTrue(issubclass(MarionetteTestCase.__metaclass__, MetaParameterized))

View File

@ -11,6 +11,7 @@ b2g = true
; true if the test should be skipped
skip = false
[test_data_driven.py]
[test_session.py]
[test_capabilities.py]