diff --git a/django/test/runner.py b/django/test/runner.py index 84fe2499f1..700a52c775 100644 --- a/django/test/runner.py +++ b/django/test/runner.py @@ -14,6 +14,8 @@ class DiscoverRunner(object): A Django test runner that uses unittest2 test discovery. """ + test_suite = TestSuite + test_runner = unittest.TextTestRunner test_loader = defaultTestLoader reorder_by = (TestCase, ) option_list = ( @@ -42,7 +44,7 @@ class DiscoverRunner(object): unittest.installHandler() def build_suite(self, test_labels=None, extra_tests=None, **kwargs): - suite = TestSuite() + suite = self.test_suite() test_labels = test_labels or ['.'] extra_tests = extra_tests or [] @@ -107,7 +109,7 @@ class DiscoverRunner(object): return setup_databases(self.verbosity, self.interactive, **kwargs) def run_suite(self, suite, **kwargs): - return unittest.TextTestRunner( + return self.test_runner( verbosity=self.verbosity, failfast=self.failfast, ).run(suite) @@ -201,7 +203,8 @@ def reorder_suite(suite, classes): classes[1], etc. Tests with no match in classes are placed last. """ class_count = len(classes) - bins = [unittest.TestSuite() for i in range(class_count+1)] + suite_class = type(suite) + bins = [suite_class() for i in range(class_count+1)] partition_suite(suite, classes, bins) for i in range(class_count): bins[0].addTests(bins[i+1]) @@ -218,8 +221,9 @@ def partition_suite(suite, classes, bins): Tests of type classes[i] are added to bins[i], tests with no match found in classes are place in bins[-1] """ + suite_class = type(suite) for test in suite: - if isinstance(test, unittest.TestSuite): + if isinstance(test, suite_class): partition_suite(test, classes, bins) else: for i in range(len(classes)): diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e84f738530..9f05055fd2 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -285,6 +285,14 @@ Templates * ``TypeError`` exceptions are not longer silenced when raised during the rendering of a template. +Tests +^^^^^ + +* :class:`~django.test.runner.DiscoverRunner` has two new attributes, + :attr:`~django.test.runner.DiscoverRunner.test_suite` and + :attr:`~django.test.runner.DiscoverRunner.test_runner`, which facilitate + overriding the way tests are collected and run. + Backwards incompatible changes in 1.7 ===================================== diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index a4c425aeb9..d02912dd31 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -338,6 +338,25 @@ execute and tear down the test suite. Attributes ~~~~~~~~~~ +.. attribute:: DiscoverRunner.test_suite + + .. versionadded:: 1.7 + + The class used to build the test suite. By default it is set to + ``unittest.TestSuite``. This can be overridden if you wish to implement + different logic for collecting tests. + +.. attribute:: DiscoverRunner.test_runner + + .. versionadded:: 1.7 + + This is the class of the low-level test runner which is used to execute + the individual tests and format the results. By default it is set to + ``unittest.TextTestRunner``. Despite the unfortunate similarity in + naming conventions, this is not the same type of class as + ``DiscoverRunner``, which covers a broader set of responsibilites. You + can override this attribute to modify the way tests are run and reported. + .. attribute:: DiscoverRunner.test_loader This is the class that loads tests, whether from TestCases or modules or diff --git a/tests/test_runner/test_discover_runner.py b/tests/test_runner/test_discover_runner.py index 4494b2bd3b..bd26e2e73d 100644 --- a/tests/test_runner/test_discover_runner.py +++ b/tests/test_runner/test_discover_runner.py @@ -1,7 +1,7 @@ from contextlib import contextmanager import os import sys -from unittest import expectedFailure +from unittest import expectedFailure, TestSuite, TextTestRunner, defaultTestLoader from django.test import TestCase from django.test.runner import DiscoverRunner @@ -68,3 +68,12 @@ class DiscoverRunnerTest(TestCase): ).countTestCases() self.assertEqual(count, 3) + + def test_overrideable_test_suite(self): + self.assertEqual(DiscoverRunner().test_suite, TestSuite) + + def test_overrideable_test_runner(self): + self.assertEqual(DiscoverRunner().test_runner, TextTestRunner) + + def test_overrideable_test_loader(self): + self.assertEqual(DiscoverRunner().test_loader, defaultTestLoader)