Brett Cannon added the comment:

OK, let's take a step back here and look at what exactly we are trying to 
simplify (which is now the updated example in PEP 399)::

    from test.support import import_fresh_module
    import unittest

    c_heapq = import_fresh_module('heapq', fresh=['_heapq'])
    py_heapq = import_fresh_module('heapq', blocked=['_heapq'])


    class ExampleTest:

        def test_example(self):
            self.assertTrue(hasattr(self.module, 'heapify'))


    class PyExampleTest(ExampleTest, unittest.TestCase):
        module = py_heapq


    @unittest.skipUnless(c_heapq, 'requires the C _heapq module')
    class CExampleTest(ExampleTest, unittest.TestCase):
        module = c_heapq


    if __name__ == '__main__':
        unittest.main()


Ignoring the unittest.main() boilerplate, we have two import_fresh_module() 
calls and the creation of two subclasses of ExampleTest, both of which inherit 
from unittest.TestCase and one of which is skipped if the acceleration code is 
lacking. So there is some boilerplate. The question is whether a solution be 
made that isn't too magical to minimize this code.

In my head I see this becoming something more like::

    from test.support import PEP399Tests

    pep399_tests = PEP399Tests('heapq', '_heapq')

    class ExampleTest:

        def test_example(self):
            self.assertTrue(hasattr(self.heapq, 'heapify'))

    PyExampleTest, CExampleTest = pep399_tests.create_test_cases(ExampleTest)


    if __name__ == '__main__':
        unittest.main()


This would cut out the import_fresh_module() calls (which don't need to be 
injected globally as you should be accessing the code through the test class' 
attribute storing the module and if you don't care what version you get you 
just do a standard imoprt), remembering to subclass unittest.TestCase, and to 
skip the accelerated version of the tests if it isn't needed. Basically it goes 
from 7 lines to 2 with not repetitious lines. You could make this a decorator, 
but honestly it's the same number of lines without the magic of mucking with a 
module's globals by getting at them through sys.modules (the decorator really 
only saves you from typing the variable names to hold the new test classes and 
the test class argument to begin with).

And the implementation should be relatively straight-forward (thanks to Eric 
having done most of the thinking on what needs to happen)::

  class PEP399Tests:
    # Using keyword-only arguments you could go as far as allowing for
    # customizing the attribute name to set, class name prefixes, etc.
    def __init__(module_name, *accelerated_names):
      self.module_name = module_name
      self.py_module = import_fresh_module(module_name, fresh=accelerated_names)
      self.accelerated_module = import_fresh_module(module_name, 
block=accelerated_names)

    def create_test_cases(self, test_class):
      class PyTestCase(test_class, unittest.TestCase): pass
      PyTestCase.__name__ = 'Py' + test_class.__name__
      setattr(PyTestCase, self.module_name, self.py_module)
      if self.accelerated_module is None:
        AcceleratedTestCase = None
      else:
        class AcceleratedTestCase(test_class, unittest.TestCase): pass
        AcceleratedTestCase.__name__ = 'Accelerated' + test_class.__name__
        setattr(AcceleratedTestCase, self.module_name, self.accelerated_module)
      return PyTestCase, AcceleratedTestCase


Does this approach seem more reasonable to people in terms of cutting down 
boilerplate without being too magical?

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue17037>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to