--- a/django/core/management.py
+++ b/django/core/management.py
@@ -1242,7 +1242,12 @@ def test(app_labels, verbosity=1):
     test_module = __import__(test_module_name, {}, {}, test_path[-1])
     test_runner = getattr(test_module, test_path[-1])
 
-    test_runner(app_list, verbosity)
+    failures = test_runner(app_list, verbosity)
+    if failures:
+        sys.exit(1)
+    else:
+        sys.exit(0)
+        
 test.help_doc = 'Runs the test suite for the specified applications, or the entire site if no apps are specified'
 test.args = '[--verbosity] ' + APP_ARGS
 
diff --git a/django/test/simple.py b/django/test/simple.py
index 88e6b49925605d9cd141c77f328b99e8012bab1b..7c0bd9dd8a0124accfd797a195914cf3f586d285 100644
--- a/django/test/simple.py
+++ b/django/test/simple.py
@@ -64,6 +64,8 @@ def run_tests(module_list, verbosity=1, 
     looking for doctests and unittests in models.py or tests.py within
     the module. A list of 'extra' tests may also be provided; these tests
     will be added to the test suite.
+
+    Returns the number of failures.
     """
     setup_test_environment()
     
@@ -79,7 +81,8 @@ def run_tests(module_list, verbosity=1, 
     old_name = settings.DATABASE_NAME
     create_test_db(verbosity)
     management.syncdb(verbosity, interactive=False)
-    unittest.TextTestRunner(verbosity=verbosity).run(suite)
+    test_result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
     destroy_test_db(old_name, verbosity)
     
     teardown_test_environment()
+    return len(test_result.failures)
\ No newline at end of file
diff --git a/docs/testing.txt b/docs/testing.txt
index a0b8a8a18724dd631db5c5613af98d328875ee2f..b2e8a46054eb242fb2b53f34503ece0e5ddab134 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -379,6 +379,9 @@ failed::
 
 When the tests have all been executed, the test database is destroyed.
 
+You can use the exit code of ``manage.py test`` for automation: it is
+0 in case of success, and 1 in case of any test failures.
+
 Using a different testing framework
 ===================================
 
@@ -398,6 +401,7 @@ #. Looking for Unit Tests and Doctests i
 #. Running the Unit Tests and Doctests that are found
 #. Destroying the test database.
 #. Performing global post-test teardown
+#. Returning the number of failures
 
 If you define your own test runner method and point ``TEST_RUNNER``
 at that method, Django will execute your test runner whenever you run
diff --git a/tests/runtests.py b/tests/runtests.py
index 20189c2d995c5dca6dda893c56a5078a3ffe2909..bae4dc76262984b5cef87d5dca0f3073ee8f12fe 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -118,7 +118,7 @@ def django_tests(verbosity, tests_to_run
 
     # Run the test suite, including the extra validation tests.
     from django.test.simple import run_tests
-    run_tests(test_models, verbosity, extra_tests=extra_tests)
+    failures = run_tests(test_models, verbosity, extra_tests=extra_tests)
 
     # Restore the old settings
     settings.INSTALLED_APPS = old_installed_apps
@@ -127,6 +127,8 @@ def django_tests(verbosity, tests_to_run
     settings.TEMPLATE_DIRS = old_template_dirs
     settings.USE_I18N = old_use_i18n
 
+    return failures
+
 if __name__ == "__main__":
     from optparse import OptionParser
     usage = "%prog [options] [model model model ...]"
@@ -140,4 +142,8 @@ if __name__ == "__main__":
     if options.settings:
         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
 
-    django_tests(int(options.verbosity), args)
+    failures = django_tests(int(options.verbosity), args)
+    if failures:
+        sys.exit(1)
+    else:
+        sys.exit(0)
