Skip to content

Instantly share code, notes, and snippets.

@blazetopher
Last active December 18, 2015 02:08
Show Gist options
  • Save blazetopher/5708548 to your computer and use it in GitHub Desktop.
Save blazetopher/5708548 to your computer and use it in GitHub Desktop.
Examples of test function reuse through multiple inheritance and the get_props decorator for test-specific properties

##Test Reuse via Multiple Inheritance This pattern allows reuse of the same set of "core" tests across many concrete implementations of a class hierarchy. The tests can be written to test core functionality while concrete test classes provide specifics geared towards each concrete implementation. In conjunction with the get_props decorator, this pattern can provide a very high degree of test reuse (resulting in increased test coverage) without sacrificing the specificity for concrete implementations. Addition of new concrete tests gets the benefit of all of the base tests with only a relatively small amount of property configuration.

##get_props Decorator Evaluates the contents of TESTING_PROPERTIES on a test-by-test basis and Provides a self.method_name.props attribute to the method that contains a dictionary of properties

The TESTING_PROPERTIES dictionary can be extended/amended by subclasses to provide specific properties on a class-wide and/or test-by-test basis

The TESTING_PROPERTIES dict should be deepcopied in each subclass and the keys overridden as necessary

All 'top level' values MUST be instances of dict

Keys overridden by subclasses are done so in a 'non-destructive' manner. In the example below, any keys present in the BaseTest.TESTING_PROPERTIES['defaults'] dictionary OTHER than 'time_steps' will be preserved.

The properties available to a specific test are a non-destructive combination of 'defaults' and those for the specific test.

Subclass Example - see testing_inheritance.py for more:

from copy import deepcopy
TESTING_PROPERTIES = deepcopy(BaseTest.TESTING_PROPERTIES
TESTING_PROPERTIES['defaults'] = {'time_steps': 20}
TESTING_PROPERTIES['test_method_one'] = {'my_prop': 'myval'}

@get_props()
def test_method_one(self):
    props = self.test_method_one.props
    time_steps = props['time_steps']
    assert time_steps == 20
    myprop = props['my_prop']
    assert myprop == 'myval'

##Running testing_inheritance.py The other file in this gist, testing_inheritance.py, showcases both the inheritance pattern and the get_props decorator. It requires nose to be installed (pip install nose)
Run with the -vs flags to show descriptive output on how the inheritance pattern and get_props decorator work:

nosetests testing_inheritance -vs
# Requires nose
# Run with the -vs flags to show descriptive output on how the inheritance pattern and get_props decorator work:
# nosetests testing_inheritance -vs
from unittest import TestCase, skip
from copy import deepcopy
import functools
###############
# The decorator function
###############
def get_props():
def decorating_function(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
func_name = function.__name__
props = dict(deepcopy(BaseTest.TESTING_PROPERTIES)['defaults'].items())
if isinstance(args[0], BaseTest):
sub_props = args[0].TESTING_PROPERTIES
props.update(sub_props['defaults'].items())
if func_name in sub_props:
props.update(sub_props[func_name].items())
wrapper.props = props
result = function(*args, **kwargs)
return result
return wrapper
return decorating_function
###############
# The base tests - does NOT inherit from TestCase
###############
class BaseTest(object):
TESTING_PROPERTIES = {
'defaults': {
'default_one': 'default value one',
'default_two': 'second default value',
}
}
def do_setup_function(self):
raise NotImplemented('Not implemented in the base class')
def test_my_name_is(self):
print '\n', self.my_name
self.assertEqual(self.__class__.__name__, self.my_name)
def test_use_setup_function(self):
res = self.do_setup_function()
print '\n', res
self.assertIsInstance(res, basestring)
@get_props()
def test_use_default_props(self):
props = self.test_use_default_props.props
print '\n', props['default_one']
self.assertEqual(props['default_one'], 'default value one')
@get_props()
def test_use_modified_props(self):
props = self.test_use_modified_props.props
print '\n', props['default_two']
print props['myproperty']
self.assertIn(self.my_name, props['myproperty'])
###############
# The concrete test classes - inherit from BOTH TestCase and BaseTest
###############
class MyTest(TestCase, BaseTest):
TESTING_PROPERTIES = deepcopy(BaseTest.TESTING_PROPERTIES)
TESTING_PROPERTIES['default_two'] = 'modified default for MyTest'
TESTING_PROPERTIES['test_use_modified_props'] = {
'myproperty': 'some property only available to this test, specific to MyTest'
}
def setUp(self):
self.my_name = self.__class__.__name__
def do_setup_function(self):
return "a string"
class MyOtherTest(TestCase, BaseTest):
TESTING_PROPERTIES = deepcopy(BaseTest.TESTING_PROPERTIES)
TESTING_PROPERTIES['default_two'] = 'modified default for MyOtherTest'
TESTING_PROPERTIES['test_use_modified_props'] = {
'myproperty': 'some property only available to this test, specific to MyOtherTest'
}
def setUp(self):
self.my_name = self.__class__.__name__
def do_setup_function(self):
return "another string"
class NotTestTwo(TestCase, BaseTest):
TESTING_PROPERTIES = deepcopy(BaseTest.TESTING_PROPERTIES)
TESTING_PROPERTIES['default_two'] = 'modified default for NotTestTwo'
TESTING_PROPERTIES['test_use_modified_props'] = {
'myproperty': 'some property only available to this test, specific to NotTestTwo'
}
def setUp(self):
self.my_name = self.__class__.__name__
@skip('Not valid to test here - so also don\'t need to implement do_setup_function')
def test_use_setup_function(self):
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment