Ticket #12624: t12624-r12231.1.diff

File t12624-r12231.1.diff, 16.6 KB (added by Russell Keith-Magee, 15 years ago)

First draft at class-based test runners.

  • django/conf/global_settings.py

    diff -r e165fea06e06 django/conf/global_settings.py
    a b  
    487487# TESTING #
    488488###########
    489489
    490 # The name of the method to use to invoke the test suite
    491 TEST_RUNNER = 'django.test.simple.run_tests'
     490# The name of the class to use to run the test suite
     491TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
    492492
    493493# The name of the database to use for testing purposes.
    494494# If None, a name of 'test_' + DATABASE_NAME will be assumed
  • django/core/management/commands/test.py

    diff -r e165fea06e06 django/core/management/commands/test.py
    a b  
    2121        verbosity = int(options.get('verbosity', 1))
    2222        interactive = options.get('interactive', True)
    2323        failfast = options.get('failfast', False)
    24         test_runner = get_runner(settings)
     24        TestRunner = get_runner(settings)
    2525
    26         # Some custom test runners won't accept the failfast flag, so let's make sure they accept it before passing it to them
    27         if 'failfast' in test_runner.func_code.co_varnames:
    28             failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive,
    29                                    failfast=failfast)
     26        if hasattr(TestRunner, 'func_name'):
     27            # Pre 1.1 test runners were just functions,
     28            # and did not support the 'failfast' option.
     29            import warnings
     30            warnings.warn(
     31                'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
     32                PendingDeprecationWarning
     33            )
     34            failures = TestRunner(test_labels, verbosity=verbosity, interactive=interactive)
    3035        else:
    31             failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
     36            test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
     37            failures = test_runner.run_tests(test_labels)
    3238
    3339        if failures:
    3440            sys.exit(bool(failures))
  • django/test/simple.py

    diff -r e165fea06e06 django/test/simple.py
    a b  
    4040        """
    4141        self._keyboard_interrupt_intercepted = True
    4242        sys.stderr.write(" <Test run halted by Ctrl-C> ")
    43         # Set the interrupt handler back to the default handler, so that 
     43        # Set the interrupt handler back to the default handler, so that
    4444        # another Ctrl-C press will trigger immediate exit.
    4545        signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
    4646
     
    197197        bins[0].addTests(bins[i+1])
    198198    return bins[0]
    199199
     200
     201class DjangoTestSuiteRunner(object):
     202    def __init__(self, verbosity=1, interactive=True, failfast=True):
     203        self.verbosity = verbosity
     204        self.interactive = interactive
     205        self.failfast = failfast
     206
     207    def setup_test_environment(self):
     208        setup_test_environment()
     209        settings.DEBUG = False
     210
     211    def build_suite(self, test_labels, extra_tests=[]):
     212        suite = unittest.TestSuite()
     213
     214        if test_labels:
     215            for label in test_labels:
     216                if '.' in label:
     217                    suite.addTest(build_test(label))
     218                else:
     219                    app = get_app(label)
     220                    suite.addTest(build_suite(app))
     221        else:
     222            for app in get_apps():
     223                suite.addTest(build_suite(app))
     224
     225        for test in extra_tests:
     226            suite.addTest(test)
     227
     228        return reorder_suite(suite, (TestCase,))
     229
     230    def setup_databases(self):
     231        from django.db import connections
     232        old_names = []
     233        for alias in connections:
     234            connection = connections[alias]
     235            old_names.append((connection, connection.settings_dict['NAME']))
     236            connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive)
     237        return old_names
     238
     239    def run_suite(self, suite):
     240        return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
     241
     242    def teardown_databases(self, old_names):
     243        for connection, old_name in old_names:
     244            connection.creation.destroy_test_db(old_name, self.verbosity)
     245
     246    def teardown_test_environment(self):
     247        teardown_test_environment()
     248
     249    def suite_result(self, result):
     250        return len(result.failures) + len(result.errors)
     251
     252    def run_tests(self, test_labels, extra_tests=[]):
     253        """
     254        Run the unit tests for all the test labels in the provided list.
     255        Labels must be of the form:
     256         - app.TestClass.test_method
     257            Run a single specific test method
     258         - app.TestClass
     259            Run all the test methods in a given class
     260         - app
     261            Search for doctests and unittests in the named application.
     262
     263        When looking for tests, the test runner will look in the models and
     264        tests modules for the application.
     265
     266        A list of 'extra' tests may also be provided; these tests
     267        will be added to the test suite.
     268
     269        Returns the number of tests that failed.
     270        """
     271        self.setup_test_environment()
     272
     273        old_names = self.setup_databases()
     274
     275        suite = self.build_suite(test_labels, extra_tests)
     276
     277        result = self.run_suite(suite)
     278
     279        self.teardown_databases(old_names)
     280
     281        self.teardown_test_environment()
     282
     283        return self.suite_result(result)
     284
    200285def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=[]):
    201     """
    202     Run the unit tests for all the test labels in the provided list.
    203     Labels must be of the form:
    204      - app.TestClass.test_method
    205         Run a single specific test method
    206      - app.TestClass
    207         Run all the test methods in a given class
    208      - app
    209         Search for doctests and unittests in the named application.
    210 
    211     When looking for tests, the test runner will look in the models and
    212     tests modules for the application.
    213 
    214     A list of 'extra' tests may also be provided; these tests
    215     will be added to the test suite.
    216 
    217     Returns the number of tests that failed.
    218     """
    219     setup_test_environment()
    220 
    221     settings.DEBUG = False
    222     suite = unittest.TestSuite()
    223 
    224     if test_labels:
    225         for label in test_labels:
    226             if '.' in label:
    227                 suite.addTest(build_test(label))
    228             else:
    229                 app = get_app(label)
    230                 suite.addTest(build_suite(app))
    231     else:
    232         for app in get_apps():
    233             suite.addTest(build_suite(app))
    234 
    235     for test in extra_tests:
    236         suite.addTest(test)
    237 
    238     suite = reorder_suite(suite, (TestCase,))
    239 
    240     from django.db import connections
    241     old_names = []
    242     for alias in connections:
    243         connection = connections[alias]
    244         old_names.append((connection, connection.settings_dict['NAME']))
    245         connection.creation.create_test_db(verbosity, autoclobber=not interactive)
    246     result = DjangoTestRunner(verbosity=verbosity, failfast=failfast).run(suite)
    247     for connection, old_name in old_names:
    248         connection.creation.destroy_test_db(old_name, verbosity)
    249 
    250     teardown_test_environment()
    251 
    252     return len(result.failures) + len(result.errors)
     286    import warnings
     287    warnings.warn(
     288        'The run_tests() test runner has been deprecated in favor of DjangoTestSuiteRunner.',
     289        PendingDeprecationWarning
     290    )
     291    test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
     292    return test_runner.run_tests(test_labels, extra_tests=[])
  • docs/internals/deprecation.txt

    diff -r e165fea06e06 docs/internals/deprecation.txt
    a b  
    7474          ``django.utils.formats.get_format()`` to get the appropriate
    7575          formats.
    7676
     77        * The ability to use a function-based test runners will be removed,
     78          along with the ``django.test.simple.run_tests()`` test runner.
     79
    7780    * 2.0
    7881        * ``django.views.defaults.shortcut()``. This function has been moved
    7982          to ``django.contrib.contenttypes.views.shortcut()`` as part of the
  • docs/releases/1.2.txt

    diff -r e165fea06e06 docs/releases/1.2.txt
    a b  
    367367django.form.fields to django.core.validators. You will need to update
    368368your imports if you are using it.
    369369
     370Function-based test runners
     371---------------------------
     372
     373Django 1.2 changes the test runner tools to use a class-based
     374approach. Old style function-based test runners will still work, but
     375should be updated to use the new :ref:`class-based runners
     376<topics-testing-test_runner>`.
     377
    370378What's new in Django 1.2
    371379========================
    372380
     
    428436.. code-block:: html+django
    429437
    430438    {% ifnotequal a b %}
    431      ...
     439    ...
    432440    {% endifnotequal %}
    433441
    434442You can now do this::
     
    436444.. code-block:: html+django
    437445
    438446    {% if a != b %}
    439      ...
     447    ...
    440448    {% endif %}
    441449
    442450There's really no reason to use ``{% ifequal %}`` or ``{% ifnotequal %}``
  • docs/topics/testing.txt

    diff -r e165fea06e06 docs/topics/testing.txt
    a b  
    275275    $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
    276276
    277277.. versionadded:: 1.2
    278    You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``. 
     278   You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``.
    279279
    280 If you press ``Ctrl-C`` while the tests are running, the test runner will 
     280If you press ``Ctrl-C`` while the tests are running, the test runner will
    281281wait for the currently running test to complete and then exit gracefully.
    282 During a graceful exit the test runner will output details of any test 
     282During a graceful exit the test runner will output details of any test
    283283failures, report on how many tests were run and how many errors and failures
    284 were encountered, and destroy any test databases as usual. Thus pressing 
     284were encountered, and destroy any test databases as usual. Thus pressing
    285285``Ctrl-C`` can be very useful if you forget to pass the :djadminopt:`--failfast`
    286 option, notice that some tests are unexpectedly failing, and want to get details 
     286option, notice that some tests are unexpectedly failing, and want to get details
    287287on the failures without waiting for the full test run to complete.
    288288
    289289If you do not want to wait for the currently running test to finish, you
     
    12281228
    12291229When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER`
    12301230setting to determine what to do. By default, :setting:`TEST_RUNNER` points to
    1231 ``'django.test.simple.run_tests'``. This method defines the default Django
     1231``'django.test.simple.DjangoTestSuiteRunner'``. This class defines the default Django
    12321232testing behavior. This behavior involves:
    12331233
    12341234    #. Performing global pre-test setup.
    12351235
    1236     #. Creating the test database.
     1236    #. Creating the test databases.
    12371237
    12381238    #. Running ``syncdb`` to install models and initial data into the test
    1239        database.
     1239       databases.
    12401240
    12411241    #. Looking for unit tests and doctests in the ``models.py`` and
    12421242       ``tests.py`` files in each installed application.
    12431243
    12441244    #. Running the unit tests and doctests that are found.
    12451245
    1246     #. Destroying the test database.
     1246    #. Destroying the test databases.
    12471247
    12481248    #. Performing global post-test teardown.
    12491249
    12501250If you define your own test runner method and point :setting:`TEST_RUNNER` at
    12511251that method, Django will execute your test runner whenever you run
    12521252``./manage.py test``. In this way, it is possible to use any test framework
    1253 that can be executed from Python code.
     1253that can be executed from Python code, or to modify the Django test execution
     1254process to satisfy whatever testing requirements you may have.
     1255
     1256.. _topics-testing-test_runner:
    12541257
    12551258Defining a test runner
    12561259----------------------
    12571260
    1258 .. versionadded:: 1.0
     1261.. versionchanged:: 1.2
     1262   Prior to 1.2, test runners were a single function, not a class.
    12591263
    12601264.. currentmodule:: django.test.simple
    12611265
    1262 By convention, a test runner should be called ``run_tests``. The only strict
    1263 requirement is that it has the same arguments as the Django test runner:
     1266A test runner is a class defining a ``run_tests()`` method. Django ships
     1267with a ``DjangoTestSuiteRunner`` class that defines the default Django
     1268testing behavior. This class defines the ``run_tests()`` entry point,
     1269plus a selection of other methods that are used to by ``run_tests()`` to
     1270set up, execute and tear down the test suite.
    12641271
    1265 .. function:: run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])
     1272.. class:: DjangoTestSuiteRunner(verbosity=1, interactive=True, failfast=True)
     1273
     1274    ``verbosity`` determines the amount of notification and debug information
     1275    that will be printed to the console; ``0`` is no output, ``1`` is normal
     1276    output, and ``2`` is verbose output.
     1277
     1278    If ``interactive`` is ``True``, the test suite has permission to ask the
     1279    user for instructions when the test suite is executed. An example of this
     1280    behavior would be asking for permission to delete an existing test
     1281    database. If ``interactive`` is ``False``, the test suite must be able to
     1282    run without any manual intervention.
     1283
     1284    If ``failfast`` is ``True``, the test suite will stop running after the
     1285    first test failure is detected.
     1286
     1287.. method:: DjangoTestSuiteRunner.run_tests(test_labels, extra_tests=[])
     1288
     1289    Run the test suite.
    12661290
    12671291    ``test_labels`` is a list of strings describing the tests to be run. A test
    12681292    label can take one of three forms:
     
    12751299    If ``test_labels`` has a value of ``None``, the test runner should run
    12761300    search for tests in all the applications in :setting:`INSTALLED_APPS`.
    12771301
    1278     ``verbosity`` determines the amount of notification and debug information
    1279     that will be printed to the console; ``0`` is no output, ``1`` is normal
    1280     output, and ``2`` is verbose output.
     1302    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
     1303    suite that is executed by the test runner. These extra tests are run
     1304    in addition to those discovered in the modules listed in ``test_labels``.
    12811305
    1282     If ``interactive`` is ``True``, the test suite has permission to ask the
    1283     user for instructions when the test suite is executed. An example of this
    1284     behavior would be asking for permission to delete an existing test
    1285     database. If ``interactive`` is ``False``, the test suite must be able to
    1286     run without any manual intervention.
     1306    This method should return the number of tests that failed.
     1307
     1308.. method:: DjangoTestSuiteRunner.setup_test_environment()
     1309
     1310    Sets up the test environment ready for testing.
     1311
     1312.. method:: DjangoTestSuiteRunner.build_suite(test_labels, extra_tests=[])
     1313
     1314    Constructs a test suite that matches the test labels provided.
     1315
     1316    ``test_labels`` is a list of strings describing the tests to be run. A test
     1317    label can take one of three forms:
     1318
     1319        * ``app.TestCase.test_method`` -- Run a single test method in a test
     1320          case.
     1321        * ``app.TestCase`` -- Run all the test methods in a test case.
     1322        * ``app`` -- Search for and run all tests in the named application.
     1323
     1324    If ``test_labels`` has a value of ``None``, the test runner should run
     1325    search for tests in all the applications in :setting:`INSTALLED_APPS`.
    12871326
    12881327    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
    12891328    suite that is executed by the test runner. These extra tests are run
    1290     in addition to those discovered in the modules listed in ``module_list``.
     1329    in addition to those discovered in the modules listed in ``test_labels``.
    12911330
    1292     This method should return the number of tests that failed.
     1331    Returns a ``TestSuite`` instance ready to be run.
     1332
     1333.. method:: DjangoTestSuiteRunner.setup_databases()
     1334
     1335    Creates the test databases.
     1336
     1337    Returns the list of old database names that will need to be restored
     1338
     1339.. method:: DjangoTestSuiteRunner.run_suite(suite)
     1340
     1341    Runs the test suite.
     1342
     1343    Returns the result produced by the running the test suite.
     1344
     1345.. method:: DjangoTestSuiteRunner.teardown_databases(old_names)
     1346
     1347    Destroys the test databases, restoring the old names.
     1348
     1349.. method:: DjangoTestSuiteRunner.teardown_test_environment()
     1350
     1351    Restores the pre-test environment.
     1352
     1353.. method:: DjangoTestSuiteRunner.suite_result(result)
     1354
     1355    Computes and returns a return code based on a test suite result.
     1356
    12931357
    12941358Testing utilities
    12951359-----------------
Back to Top