From d19109fd37e75ccf29d2ca64370102753dbc7c5b Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Fri, 21 Dec 2012 21:59:06 -0300 Subject: Fixed #19497 -- Refactored testing docs. Thanks Tim Graham for the review and suggestions. --- docs/index.txt | 6 +- .../contributing/writing-code/unit-tests.txt | 4 +- docs/intro/contributing.txt | 2 +- docs/intro/tutorial05.txt | 2 +- docs/misc/api-stability.txt | 2 +- docs/ref/django-admin.txt | 6 +- docs/ref/settings.txt | 6 +- docs/ref/signals.txt | 2 +- docs/releases/0.96.txt | 2 +- docs/releases/1.1-alpha-1.txt | 2 +- docs/releases/1.1-beta-1.txt | 2 +- docs/releases/1.1.txt | 4 +- .../django_unittest_classes_hierarchy.graffle | 883 -------- .../_images/django_unittest_classes_hierarchy.pdf | Bin 51979 -> 0 bytes .../_images/django_unittest_classes_hierarchy.svg | 3 - docs/topics/index.txt | 2 +- docs/topics/install.txt | 2 +- docs/topics/testing.txt | 2359 -------------------- .../django_unittest_classes_hierarchy.graffle | 883 ++++++++ .../_images/django_unittest_classes_hierarchy.pdf | Bin 0 -> 51979 bytes .../_images/django_unittest_classes_hierarchy.svg | 3 + docs/topics/testing/advanced.txt | 429 ++++ docs/topics/testing/doctests.txt | 81 + docs/topics/testing/index.txt | 111 + docs/topics/testing/overview.txt | 1784 +++++++++++++++ 25 files changed, 3314 insertions(+), 3266 deletions(-) delete mode 100644 docs/topics/_images/django_unittest_classes_hierarchy.graffle delete mode 100644 docs/topics/_images/django_unittest_classes_hierarchy.pdf delete mode 100644 docs/topics/_images/django_unittest_classes_hierarchy.svg delete mode 100644 docs/topics/testing.txt create mode 100644 docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle create mode 100644 docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf create mode 100644 docs/topics/testing/_images/django_unittest_classes_hierarchy.svg create mode 100644 docs/topics/testing/advanced.txt create mode 100644 docs/topics/testing/doctests.txt create mode 100644 docs/topics/testing/index.txt create mode 100644 docs/topics/testing/overview.txt (limited to 'docs') diff --git a/docs/index.txt b/docs/index.txt index 9fea8ff3f2..ab00da271c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -180,7 +180,11 @@ testing of Django applications: :doc:`Overview ` | :doc:`Adding custom commands ` -* **Testing:** :doc:`Overview ` +* **Testing:** + :doc:`Overview ` | + :doc:`Writing and running tests ` | + :doc:`Advanced topics ` | + :doc:`Doctests ` * **Deployment:** :doc:`Overview ` | diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 4e702ff83e..afef554a8c 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -15,8 +15,8 @@ The tests cover: We appreciate any and all contributions to the test suite! The Django tests all use the testing infrastructure that ships with Django for -testing applications. See :doc:`Testing Django applications ` -for an explanation of how to write new tests. +testing applications. See :doc:`Testing Django applications +` for an explanation of how to write new tests. .. _running-unit-tests: diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index a343814c02..c94038bc56 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -281,7 +281,7 @@ correctly in a couple different situations. computer programming, so there's lots of information out there: * A good first look at writing tests for Django can be found in the - documentation on :doc:`Testing Django applications`. + documentation on :doc:`Testing Django applications `. * Dive Into Python (a free online book for beginning Python developers) includes a great `introduction to Unit Testing`__. * After reading those, if you want something a little meatier to sink diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 163b7cdd0f..d1f95176ed 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -632,7 +632,7 @@ a piece of code, it usually means that code should be refactored or removed. Coverage will help to identify dead code. See :ref:`topics-testing-code-coverage` for details. -:doc:`Testing Django applications ` has comprehensive +:doc:`Testing Django applications ` has comprehensive information about testing. .. _Selenium: http://seleniumhq.org/ diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index 4f232e795b..a13cb5de69 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -71,7 +71,7 @@ of 1.0. This includes these APIs: external template tags. Before adding any such tags, we'll ensure that Django raises an error if it tries to load tags with duplicate names. -- :doc:`Testing ` +- :doc:`Testing ` - :doc:`django-admin utility `. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 306db8439e..6ab3b1d133 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1036,7 +1036,7 @@ test .. django-admin:: test -Runs tests for all installed models. See :doc:`/topics/testing` for more +Runs tests for all installed models. See :doc:`/topics/testing/index` for more information. .. django-admin-option:: --failfast @@ -1072,7 +1072,7 @@ For example, this command:: ...would perform the following steps: -1. Create a test database, as described in :doc:`/topics/testing`. +1. Create a test database, as described in :ref:`the-test-database`. 2. Populate the test database with fixture data from the given fixtures. (For more on fixtures, see the documentation for ``loaddata`` above.) 3. Runs the Django development server (as in ``runserver``), pointed at @@ -1080,7 +1080,7 @@ For example, this command:: This is useful in a number of ways: -* When you're writing :doc:`unit tests ` of how your views +* When you're writing :doc:`unit tests ` of how your views act with certain fixture data, you can use ``testserver`` to interact with the views in a Web browser, manually. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 135cddae25..5ecc221039 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -562,7 +562,7 @@ If the default value (``None``) is used with the SQLite database engine, the tests will use a memory resident database. For all other database engines the test database will use the name ``'test_' + DATABASE_NAME``. -See :doc:`/topics/testing`. +See :ref:`the-test-database`. .. setting:: TEST_CREATE @@ -1982,9 +1982,7 @@ TEST_RUNNER Default: ``'django.test.simple.DjangoTestSuiteRunner'`` The name of the class to use for starting the test suite. See -:doc:`/topics/testing`. - -.. _Testing Django Applications: ../testing/ +:ref:`other-testing-frameworks`. .. setting:: THOUSAND_SEPARATOR diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 0db540370d..3315f9781b 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -476,7 +476,7 @@ Test signals .. module:: django.test.signals :synopsis: Signals sent during testing. -Signals only sent when :doc:`running tests `. +Signals only sent when :ref:`running tests `. setting_changed --------------- diff --git a/docs/releases/0.96.txt b/docs/releases/0.96.txt index a608629957..a00f878df3 100644 --- a/docs/releases/0.96.txt +++ b/docs/releases/0.96.txt @@ -220,7 +220,7 @@ supported :doc:`serialization formats `, that will be loaded into your database at the start of your tests. This makes testing with real data much easier. -See :doc:`the testing documentation ` for the full details. +See :doc:`the testing documentation ` for the full details. Improvements to the admin interface ----------------------------------- diff --git a/docs/releases/1.1-alpha-1.txt b/docs/releases/1.1-alpha-1.txt index 10b0d5d71e..c8ac56cf48 100644 --- a/docs/releases/1.1-alpha-1.txt +++ b/docs/releases/1.1-alpha-1.txt @@ -51,7 +51,7 @@ Performance improvements .. currentmodule:: django.test -Tests written using Django's :doc:`testing framework ` now run +Tests written using Django's :doc:`testing framework ` now run dramatically faster (as much as 10 times faster in many cases). This was accomplished through the introduction of transaction-based tests: when diff --git a/docs/releases/1.1-beta-1.txt b/docs/releases/1.1-beta-1.txt index 9bac3a53f1..1555a9464a 100644 --- a/docs/releases/1.1-beta-1.txt +++ b/docs/releases/1.1-beta-1.txt @@ -102,7 +102,7 @@ Testing improvements .. currentmodule:: django.test.client A couple of small but very useful improvements have been made to the -:doc:`testing framework `: +:doc:`testing framework `: * The test :class:`Client` now can automatically follow redirects with the ``follow`` argument to :meth:`Client.get` and :meth:`Client.post`. This diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 852644dee4..84af7fc1d9 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -264,14 +264,14 @@ Testing improvements -------------------- A few notable improvements have been made to the :doc:`testing framework -`. +`. Test performance improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: django.test -Tests written using Django's :doc:`testing framework ` now run +Tests written using Django's :doc:`testing framework ` now run dramatically faster (as much as 10 times faster in many cases). This was accomplished through the introduction of transaction-based tests: when diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.graffle b/docs/topics/_images/django_unittest_classes_hierarchy.graffle deleted file mode 100644 index 7211c0f3be..0000000000 --- a/docs/topics/_images/django_unittest_classes_hierarchy.graffle +++ /dev/null @@ -1,883 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {559.28997802734375, 782.8900146484375}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-16 18:52:14 +0000 - Creator - Aymeric Augustin - DisplayScale - 1.000 cm = 1.000 cm - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 8 - - ID - 29 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {369, 459} - {216, 400.5} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 6 - Info - 2 - - - - Class - LineGraphic - Head - - ID - 12 - Info - 1 - - ID - 27 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {135, 270} - {369, 225} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 26 - Position - 0.5 - - - - Class - LineGraphic - Head - - ID - 10 - - ID - 26 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {135, 315} - {135, 225} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 9 - - - - Class - LineGraphic - Head - - ID - 9 - - ID - 25 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {135, 387} - {135, 342} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 8 - - - - Class - LineGraphic - Head - - ID - 8 - - ID - 23 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {135, 459} - {135, 414} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 7 - - - - Bounds - {{378, 252}, {81, 27}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 12 - - ID - 22 - Shape - NoteShape - Style - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\i\fs24 \cf2 Python < 2.7} - VerticalPad - 0 - - TextRelativeArea - {{0, 0}, {1, 1}} - - - Bounds - {{45, 252}, {81, 27}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 12 - - ID - 20 - Shape - NoteShape - Style - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\i\fs24 \cf2 Python \uc0\u8805 2.7} - VerticalPad - 0 - - - - Bounds - {{288, 198}, {162, 27}} - Class - ShapedGraphic - ID - 12 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 TestCase} - - - - Bounds - {{54, 198}, {162, 27}} - Class - ShapedGraphic - ID - 10 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 TestCase} - - - - Bounds - {{54, 315}, {162, 27}} - Class - ShapedGraphic - ID - 9 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 SimpleTestCase} - - - - Bounds - {{54, 387}, {162, 27}} - Class - ShapedGraphic - ID - 8 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 TransactionTestCase} - - - - Bounds - {{54, 459}, {162, 27}} - Class - ShapedGraphic - ID - 7 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 TestCase} - - - - Bounds - {{288, 459}, {162, 27}} - Class - ShapedGraphic - ID - 6 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 LiveServerTestCase} - - - - Bounds - {{18, 297}, {468, 207}} - Class - ShapedGraphic - ID - 13 - Shape - Rectangle - Style - - Text - - Align - 2 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr - -\f0\fs24 \cf0 django.test} - - TextPlacement - 0 - - - Bounds - {{18, 153}, {225, 90}} - Class - ShapedGraphic - ID - 18 - Shape - Rectangle - Style - - Text - - Align - 2 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr - -\f0\fs24 \cf0 django.utils.unittest\ -= unittest (standard library)} - - TextPlacement - 0 - - - Bounds - {{261, 153}, {225, 90}} - Class - ShapedGraphic - ID - 19 - Shape - Rectangle - Style - - Text - - Align - 2 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr - -\f0\fs24 \cf0 django.utils.unittest\ -= unittest2 (bundled copy)} - - TextPlacement - 0 - - - GridInfo - - ShowsGrid - YES - SnapsToGrid - YES - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Calque 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2012-12-16 19:08:28 +0000 - Modifier - Aymeric Augustin - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {595.28997802734375, 841.8900146484375} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canevas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - Frame - {{9, 4}, {694, 874}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{0, 0}, {559, 735}} - Zoom - 1 - ZoomValues - - - Canevas 1 - 1 - 1 - - - - - diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.pdf b/docs/topics/_images/django_unittest_classes_hierarchy.pdf deleted file mode 100644 index cedaba22ac..0000000000 Binary files a/docs/topics/_images/django_unittest_classes_hierarchy.pdf and /dev/null differ diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.svg b/docs/topics/_images/django_unittest_classes_hierarchy.svg deleted file mode 100644 index 0482f044dd..0000000000 --- a/docs/topics/_images/django_unittest_classes_hierarchy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -2012-12-16 19:08ZCanevas 1Calque 1django.utils.unittest= unittest2 (bundled copy)django.utils.unittest= unittest (standard library)django.testLiveServerTestCaseTestCaseTransactionTestCaseSimpleTestCaseTestCaseTestCasePython ≥ 2.7Python < 2.7 diff --git a/docs/topics/index.txt b/docs/topics/index.txt index 72f5090b15..82c5859b2c 100644 --- a/docs/topics/index.txt +++ b/docs/topics/index.txt @@ -13,7 +13,7 @@ Introductions to all the key parts of Django you'll need to know: templates class-based-views/index files - testing + testing/index auth cache conditional-view-processing diff --git a/docs/topics/install.txt b/docs/topics/install.txt index b71033f319..0c3767d3e1 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -135,7 +135,7 @@ table once ``syncdb`` has created it. After creating a database user with these permissions, you'll specify the details in your project's settings file, see :setting:`DATABASES` for details. -If you're using Django's :doc:`testing framework` to test +If you're using Django's :doc:`testing framework` to test database queries, Django will need permission to create a test database. .. _PostgreSQL: http://www.postgresql.org/ diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt deleted file mode 100644 index b4645c236b..0000000000 --- a/docs/topics/testing.txt +++ /dev/null @@ -1,2359 +0,0 @@ -=========================== -Testing Django applications -=========================== - -.. module:: django.test - :synopsis: Testing tools for Django applications. - -Automated testing is an extremely useful bug-killing tool for the modern -Web developer. You can use a collection of tests -- a **test suite** -- to -solve, or avoid, a number of problems: - -* When you're writing new code, you can use tests to validate your code - works as expected. - -* When you're refactoring or modifying old code, you can use tests to - ensure your changes haven't affected your application's behavior - unexpectedly. - -Testing a Web application is a complex task, because a Web application is made -of several layers of logic -- from HTTP-level request handling, to form -validation and processing, to template rendering. With Django's test-execution -framework and assorted utilities, you can simulate requests, insert test data, -inspect your application's output and generally verify your code is doing what -it should be doing. - -The best part is, it's really easy. - -This document is split into two primary sections. First, we explain how to -write tests with Django. Then, we explain how to run them. - -Writing tests -============= - -There are two primary ways to write tests with Django, corresponding to the -two test frameworks that ship in the Python standard library. The two -frameworks are: - -* **Unit tests** -- tests that are expressed as methods on a Python class - that subclasses :class:`unittest.TestCase` or Django's customized - :class:`TestCase`. For example:: - - import unittest - - class MyFuncTestCase(unittest.TestCase): - def testBasic(self): - a = ['larry', 'curly', 'moe'] - self.assertEqual(my_func(a, 0), 'larry') - self.assertEqual(my_func(a, 1), 'curly') - -* **Doctests** -- tests that are embedded in your functions' docstrings and - are written in a way that emulates a session of the Python interactive - interpreter. For example:: - - def my_func(a_list, idx): - """ - >>> a = ['larry', 'curly', 'moe'] - >>> my_func(a, 0) - 'larry' - >>> my_func(a, 1) - 'curly' - """ - return a_list[idx] - -We'll discuss choosing the appropriate test framework later, however, most -experienced developers prefer unit tests. You can also use any *other* Python -test framework, as we'll explain in a bit. - -Writing unit tests ------------------- - -Django's unit tests use a Python standard library module: :mod:`unittest`. This -module defines tests in class-based approach. - -.. admonition:: unittest2 - - Python 2.7 introduced some major changes to the unittest library, - adding some extremely useful features. To ensure that every Django - project can benefit from these new features, Django ships with a - copy of unittest2_, a copy of the Python 2.7 unittest library, - backported for Python 2.5 compatibility. - - To access this library, Django provides the - :mod:`django.utils.unittest` module alias. If you are using Python - 2.7, or you have installed unittest2 locally, Django will map the - alias to the installed version of the unittest library. Otherwise, - Django will use its own bundled version of unittest2. - - To use this alias, simply use:: - - from django.utils import unittest - - wherever you would have historically used:: - - import unittest - - If you want to continue to use the base unittest library, you can -- - you just won't get any of the nice new unittest2 features. - -.. _unittest2: http://pypi.python.org/pypi/unittest2 - -For a given Django application, the test runner looks for unit tests in two -places: - -* The ``models.py`` file. The test runner looks for any subclass of - :class:`unittest.TestCase` in this module. - -* A file called ``tests.py`` in the application directory -- i.e., the - directory that holds ``models.py``. Again, the test runner looks for any - subclass of :class:`unittest.TestCase` in this module. - -Here is an example :class:`unittest.TestCase` subclass:: - - from django.utils import unittest - from myapp.models import Animal - - class AnimalTestCase(unittest.TestCase): - def setUp(self): - self.lion = Animal(name="lion", sound="roar") - self.cat = Animal(name="cat", sound="meow") - - def test_animals_can_speak(self): - """Animals that can speak are correctly identified""" - self.assertEqual(self.lion.speak(), 'The lion says "roar"') - self.assertEqual(self.cat.speak(), 'The cat says "meow"') - -When you :ref:`run your tests `, the default behavior of the test -utility is to find all the test cases (that is, subclasses of -:class:`unittest.TestCase`) in ``models.py`` and ``tests.py``, automatically -build a test suite out of those test cases, and run that suite. - -There is a second way to define the test suite for a module: if you define a -function called ``suite()`` in either ``models.py`` or ``tests.py``, the -Django test runner will use that function to construct the test suite for that -module. This follows the `suggested organization`_ for unit tests. See the -Python documentation for more details on how to construct a complex test -suite. - -For more details about :mod:`unittest`, see the Python documentation. - -.. _suggested organization: http://docs.python.org/library/unittest.html#organizing-tests - -.. warning:: - - If your tests rely on database access such as creating or querying models, - be sure to create your test classes as subclasses of - :class:`django.test.TestCase` rather than :class:`unittest.TestCase`. - - In the example above, we instantiate some models but do not save them to - the database. Using :class:`unittest.TestCase` avoids the cost of running - each test in a transaction and flushing the database, but for most - applications the scope of tests you will be able to write this way will - be fairly limited, so it's easiest to use :class:`django.test.TestCase`. - -Writing doctests ----------------- - -Doctests use Python's standard :mod:`doctest` module, which searches your -docstrings for statements that resemble a session of the Python interactive -interpreter. A full explanation of how :mod:`doctest` works is out of the scope -of this document; read Python's official documentation for the details. - -.. admonition:: What's a **docstring**? - - A good explanation of docstrings (and some guidelines for using them - effectively) can be found in :pep:`257`: - - A docstring is a string literal that occurs as the first statement in - a module, function, class, or method definition. Such a docstring - becomes the ``__doc__`` special attribute of that object. - - For example, this function has a docstring that describes what it does:: - - def add_two(num): - "Return the result of adding two to the provided number." - return num + 2 - - Because tests often make great documentation, putting tests directly in - your docstrings is an effective way to document *and* test your code. - -As with unit tests, for a given Django application, the test runner looks for -doctests in two places: - -* The ``models.py`` file. You can define module-level doctests and/or a - doctest for individual models. It's common practice to put - application-level doctests in the module docstring and model-level - doctests in the model docstrings. - -* A file called ``tests.py`` in the application directory -- i.e., the - directory that holds ``models.py``. This file is a hook for any and all - doctests you want to write that aren't necessarily related to models. - -This example doctest is equivalent to the example given in the unittest section -above:: - - # models.py - - from django.db import models - - class Animal(models.Model): - """ - An animal that knows how to make noise - - # Create some animals - >>> lion = Animal.objects.create(name="lion", sound="roar") - >>> cat = Animal.objects.create(name="cat", sound="meow") - - # Make 'em speak - >>> lion.speak() - 'The lion says "roar"' - >>> cat.speak() - 'The cat says "meow"' - """ - name = models.CharField(max_length=20) - sound = models.CharField(max_length=20) - - def speak(self): - return 'The %s says "%s"' % (self.name, self.sound) - -When you :ref:`run your tests `, the test runner will find this -docstring, notice that portions of it look like an interactive Python session, -and execute those lines while checking that the results match. - -In the case of model tests, note that the test runner takes care of creating -its own test database. That is, any test that accesses a database -- by -creating and saving model instances, for example -- will not affect your -production database. However, the database is not refreshed between doctests, -so if your doctest requires a certain state you should consider flushing the -database or loading a fixture. (See the section on fixtures, below, for more -on this.) Note that to use this feature, the database user Django is connecting -as must have ``CREATE DATABASE`` rights. - -For more details about :mod:`doctest`, see the Python documentation. - -Which should I use? -------------------- - -Because Django supports both of the standard Python test frameworks, it's up to -you and your tastes to decide which one to use. You can even decide to use -*both*. - -For developers new to testing, however, this choice can seem confusing. Here, -then, are a few key differences to help you decide which approach is right for -you: - -* If you've been using Python for a while, :mod:`doctest` will probably feel - more "pythonic". It's designed to make writing tests as easy as possible, - so it requires no overhead of writing classes or methods. You simply put - tests in docstrings. This has the added advantage of serving as - documentation (and correct documentation, at that!). However, while - doctests are good for some simple example code, they are not very good if - you want to produce either high quality, comprehensive tests or high - quality documentation. Test failures are often difficult to debug - as it can be unclear exactly why the test failed. Thus, doctests should - generally be avoided and used primarily for documentation examples only. - -* The :mod:`unittest` framework will probably feel very familiar to - developers coming from Java. :mod:`unittest` is inspired by Java's JUnit, - so you'll feel at home with this method if you've used JUnit or any test - framework inspired by JUnit. - -* If you need to write a bunch of tests that share similar code, then - you'll appreciate the :mod:`unittest` framework's organization around - classes and methods. This makes it easy to abstract common tasks into - common methods. The framework also supports explicit setup and/or cleanup - routines, which give you a high level of control over the environment - in which your test cases are run. - -* If you're writing tests for Django itself, you should use :mod:`unittest`. - -.. _running-tests: - -Running tests -============= - -Once you've written tests, run them using the :djadmin:`test` command of -your project's ``manage.py`` utility:: - - $ ./manage.py test - -By default, this will run every test in every application in -:setting:`INSTALLED_APPS`. If you only want to run tests for a particular -application, add the application name to the command line. For example, if your -:setting:`INSTALLED_APPS` contains ``'myproject.polls'`` and -``'myproject.animals'``, you can run the ``myproject.animals`` unit tests alone -with this command:: - - $ ./manage.py test animals - -Note that we used ``animals``, not ``myproject.animals``. - -You can be even *more* specific by naming an individual test case. To -run a single test case in an application (for example, the -``AnimalTestCase`` described in the "Writing unit tests" section), add -the name of the test case to the label on the command line:: - - $ ./manage.py test animals.AnimalTestCase - -And it gets even more granular than that! To run a *single* test -method inside a test case, add the name of the test method to the -label:: - - $ ./manage.py test animals.AnimalTestCase.test_animals_can_speak - -You can use the same rules if you're using doctests. Django will use the -test label as a path to the test method or class that you want to run. -If your ``models.py`` or ``tests.py`` has a function with a doctest, or -class with a class-level doctest, you can invoke that test by appending the -name of the test method or class to the label:: - - $ ./manage.py test animals.classify - -If you want to run the doctest for a specific method in a class, add the -name of the method to the label:: - - $ ./manage.py test animals.Classifier.run - -If you're using a ``__test__`` dictionary to specify doctests for a -module, Django will use the label as a key in the ``__test__`` dictionary -for defined in ``models.py`` and ``tests.py``. - -If you press ``Ctrl-C`` while the tests are running, the test runner will -wait for the currently running test to complete and then exit gracefully. -During a graceful exit the test runner will output details of any test -failures, report on how many tests were run and how many errors and failures -were encountered, and destroy any test databases as usual. Thus pressing -``Ctrl-C`` can be very useful if you forget to pass the :djadminopt:`--failfast` -option, notice that some tests are unexpectedly failing, and want to get details -on the failures without waiting for the full test run to complete. - -If you do not want to wait for the currently running test to finish, you -can press ``Ctrl-C`` a second time and the test run will halt immediately, -but not gracefully. No details of the tests run before the interruption will -be reported, and any test databases created by the run will not be destroyed. - -.. admonition:: Test with warnings enabled - - It's a good idea to run your tests with Python warnings enabled: - ``python -Wall manage.py test``. The ``-Wall`` flag tells Python to - display deprecation warnings. Django, like many other Python libraries, - uses these warnings to flag when features are going away. It also might - flag areas in your code that aren't strictly wrong but could benefit - from a better implementation. - -Running tests outside the test runner -------------------------------------- - -If you want to run tests outside of ``./manage.py test`` -- for example, -from a shell prompt -- you will need to set up the test -environment first. Django provides a convenience method to do this:: - - >>> from django.test.utils import setup_test_environment - >>> setup_test_environment() - -This convenience method sets up the test database, and puts other -Django features into modes that allow for repeatable testing. - -The call to :meth:`~django.test.utils.setup_test_environment` is made -automatically as part of the setup of ``./manage.py test``. You only -need to manually invoke this method if you're not using running your -tests via Django's test runner. - -The test database ------------------ - -Tests that require a database (namely, model tests) will not use your "real" -(production) database. Separate, blank databases are created for the tests. - -Regardless of whether the tests pass or fail, the test databases are destroyed -when all the tests have been executed. - -By default the test databases get their names by prepending ``test_`` -to the value of the :setting:`NAME` settings for the databases -defined in :setting:`DATABASES`. When using the SQLite database engine -the tests will by default use an in-memory database (i.e., the -database will be created in memory, bypassing the filesystem -entirely!). If you want to use a different database name, specify -:setting:`TEST_NAME` in the dictionary for any given database in -:setting:`DATABASES`. - -Aside from using a separate database, the test runner will otherwise -use all of the same database settings you have in your settings file: -:setting:`ENGINE`, :setting:`USER`, :setting:`HOST`, etc. The test -database is created by the user specified by :setting:`USER`, so you'll need -to make sure that the given user account has sufficient privileges to -create a new database on the system. - -For fine-grained control over the character encoding of your test -database, use the :setting:`TEST_CHARSET` option. If you're using -MySQL, you can also use the :setting:`TEST_COLLATION` option to -control the particular collation used by the test database. See the -:doc:`settings documentation ` for details of these -advanced settings. - -.. admonition:: Finding data from your production database when running tests? - - If your code attempts to access the database when its modules are compiled, - this will occur *before* the test database is set up, with potentially - unexpected results. For example, if you have a database query in - module-level code and a real database exists, production data could pollute - your tests. *It is a bad idea to have such import-time database queries in - your code* anyway - rewrite your code so that it doesn't do this. - -.. _topics-testing-masterslave: - -Testing master/slave configurations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you're testing a multiple database configuration with master/slave -replication, this strategy of creating test databases poses a problem. -When the test databases are created, there won't be any replication, -and as a result, data created on the master won't be seen on the -slave. - -To compensate for this, Django allows you to define that a database is -a *test mirror*. Consider the following (simplified) example database -configuration:: - - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'myproject', - 'HOST': 'dbmaster', - # ... plus some other settings - }, - 'slave': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'myproject', - 'HOST': 'dbslave', - 'TEST_MIRROR': 'default' - # ... plus some other settings - } - } - -In this setup, we have two database servers: ``dbmaster``, described -by the database alias ``default``, and ``dbslave`` described by the -alias ``slave``. As you might expect, ``dbslave`` has been configured -by the database administrator as a read slave of ``dbmaster``, so in -normal activity, any write to ``default`` will appear on ``slave``. - -If Django created two independent test databases, this would break any -tests that expected replication to occur. However, the ``slave`` -database has been configured as a test mirror (using the -:setting:`TEST_MIRROR` setting), indicating that under testing, -``slave`` should be treated as a mirror of ``default``. - -When the test environment is configured, a test version of ``slave`` -will *not* be created. Instead the connection to ``slave`` -will be redirected to point at ``default``. As a result, writes to -``default`` will appear on ``slave`` -- but because they are actually -the same database, not because there is data replication between the -two databases. - -.. _topics-testing-creation-dependencies: - -Controlling creation order for test databases -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, Django will always create the ``default`` database first. -However, no guarantees are made on the creation order of any other -databases in your test setup. - -If your database configuration requires a specific creation order, you -can specify the dependencies that exist using the -:setting:`TEST_DEPENDENCIES` setting. Consider the following -(simplified) example database configuration:: - - DATABASES = { - 'default': { - # ... db settings - 'TEST_DEPENDENCIES': ['diamonds'] - }, - 'diamonds': { - # ... db settings - }, - 'clubs': { - # ... db settings - 'TEST_DEPENDENCIES': ['diamonds'] - }, - 'spades': { - # ... db settings - 'TEST_DEPENDENCIES': ['diamonds','hearts'] - }, - 'hearts': { - # ... db settings - 'TEST_DEPENDENCIES': ['diamonds','clubs'] - } - } - -Under this configuration, the ``diamonds`` database will be created first, -as it is the only database alias without dependencies. The ``default`` and -``clubs`` alias will be created next (although the order of creation of this -pair is not guaranteed); then ``hearts``; and finally ``spades``. - -If there are any circular dependencies in the -:setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured`` -exception will be raised. - -Order in which tests are executed ---------------------------------- - -In order to guarantee that all ``TestCase`` code starts with a clean database, -the Django test runner reorders tests in the following way: - -* First, all unittests (including :class:`unittest.TestCase`, - :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and - :class:`~django.test.TransactionTestCase`) are run with no particular ordering - guaranteed nor enforced among them. - -* Then any other tests (e.g. doctests) that may alter the database without - restoring it to its original state are run. - -.. versionchanged:: 1.5 - Before Django 1.5, the only guarantee was that - :class:`~django.test.TestCase` tests were always ran first, before any other - tests. - -.. note:: - - The new ordering of tests may reveal unexpected dependencies on test case - ordering. This is the case with doctests that relied on state left in the - database by a given :class:`~django.test.TransactionTestCase` test, they - must be updated to be able to run independently. - -Other test conditions ---------------------- - -Regardless of the value of the :setting:`DEBUG` setting in your configuration -file, all Django tests run with :setting:`DEBUG`\=False. This is to ensure that -the observed output of your code matches what will be seen in a production -setting. - -Caches are not cleared after each test, and running "manage.py test fooapp" can -insert data from the tests into the cache of a live system if you run your -tests in production because, unlike databases, a separate "test cache" is not -used. This behavior `may change`_ in the future. - -.. _may change: https://code.djangoproject.com/ticket/11505 - -Understanding the test output ------------------------------ - -When you run your tests, you'll see a number of messages as the test runner -prepares itself. You can control the level of detail of these messages with the -``verbosity`` option on the command line:: - - Creating test database... - Creating table myapp_animal - Creating table myapp_mineral - Loading 'initial_data' fixtures... - No fixtures found. - -This tells you that the test runner is creating a test database, as described -in the previous section. - -Once the test database has been created, Django will run your tests. -If everything goes well, you'll see something like this:: - - ---------------------------------------------------------------------- - Ran 22 tests in 0.221s - - OK - -If there are test failures, however, you'll see full details about which tests -failed:: - - ====================================================================== - FAIL: Doctest: ellington.core.throttle.models - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/dev/django/test/doctest.py", line 2153, in runTest - raise self.failureException(self.format_failure(new.getvalue())) - AssertionError: Failed doctest test for myapp.models - File "/dev/myapp/models.py", line 0, in models - - ---------------------------------------------------------------------- - File "/dev/myapp/models.py", line 14, in myapp.models - Failed example: - throttle.check("actor A", "action one", limit=2, hours=1) - Expected: - True - Got: - False - - ---------------------------------------------------------------------- - Ran 2 tests in 0.048s - - FAILED (failures=1) - -A full explanation of this error output is beyond the scope of this document, -but it's pretty intuitive. You can consult the documentation of Python's -:mod:`unittest` library for details. - -Note that the return code for the test-runner script is 1 for any number of -failed and erroneous tests. If all the tests pass, the return code is 0. This -feature is useful if you're using the test-runner script in a shell script and -need to test for success or failure at that level. - -Speeding up the tests ---------------------- - -In recent versions of Django, the default password hasher is rather slow by -design. If during your tests you are authenticating many users, you may want -to use a custom settings file and set the :setting:`PASSWORD_HASHERS` setting -to a faster hashing algorithm:: - - PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.MD5PasswordHasher', - ) - -Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing -algorithm used in fixtures, if any. - -.. _topics-testing-code-coverage: - -Integration with coverage.py ----------------------------- - -Code coverage describes how much source code has been tested. It shows which -parts of your code are being exercised by tests and which are not. It's an -important part of testing applications, so it's strongly recommended to check -the coverage of your tests. - -Django can be easily integrated with `coverage.py`_, a tool for measuring code -coverage of Python programs. First, `install coverage.py`_. Next, run the -following from your project folder containing ``manage.py``:: - - coverage run --source='.' manage.py test myapp - -This runs your tests and collects coverage data of the executed files in your -project. You can see a report of this data by typing following command:: - - coverage report - -Note that some Django code was executed while running tests, but it is not -listed here because of the ``source`` flag passed to the previous command. - -For more options like annotated HTML listings detailing missed lines, see the -`coverage.py`_ docs. - -.. _coverage.py: http://nedbatchelder.com/code/coverage/ -.. _install coverage.py: http://pypi.python.org/pypi/coverage - -Testing tools -============= - -Django provides a small set of tools that come in handy when writing tests. - -.. _test-client: - -The test client ---------------- - -.. module:: django.test.client - :synopsis: Django's test client. - -The test client is a Python class that acts as a dummy Web browser, allowing -you to test your views and interact with your Django-powered application -programmatically. - -Some of the things you can do with the test client are: - -* Simulate GET and POST requests on a URL and observe the response -- - everything from low-level HTTP (result headers and status codes) to - page content. - -* Test that the correct view is executed for a given URL. - -* Test that a given request is rendered by a given Django template, with - a template context that contains certain values. - -Note that the test client is not intended to be a replacement for Selenium_ or -other "in-browser" frameworks. Django's test client has a different focus. In -short: - -* Use Django's test client to establish that the correct view is being - called and that the view is collecting the correct context data. - -* Use in-browser frameworks like Selenium_ to test *rendered* HTML and the - *behavior* of Web pages, namely JavaScript functionality. Django also - provides special support for those frameworks; see the section on - :class:`~django.test.LiveServerTestCase` for more details. - -A comprehensive test suite should use a combination of both test types. - -Overview and a quick example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To use the test client, instantiate ``django.test.client.Client`` and retrieve -Web pages:: - - >>> from django.test.client import Client - >>> c = Client() - >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) - >>> response.status_code - 200 - >>> response = c.get('/customer/details/') - >>> response.content - '>> c.get('/login/') - - This is incorrect:: - - >>> c.get('http://www.example.com/login/') - - The test client is not capable of retrieving Web pages that are not - powered by your Django project. If you need to retrieve other Web pages, - use a Python standard library module such as :mod:`urllib` or - :mod:`urllib2`. - -* To resolve URLs, the test client uses whatever URLconf is pointed-to by - your :setting:`ROOT_URLCONF` setting. - -* Although the above example would work in the Python interactive - interpreter, some of the test client's functionality, notably the - template-related functionality, is only available *while tests are - running*. - - The reason for this is that Django's test runner performs a bit of black - magic in order to determine which template was loaded by a given view. - This black magic (essentially a patching of Django's template system in - memory) only happens during test running. - -* By default, the test client will disable any CSRF checks - performed by your site. - - If, for some reason, you *want* the test client to perform CSRF - checks, you can create an instance of the test client that - enforces CSRF checks. To do this, pass in the - ``enforce_csrf_checks`` argument when you construct your - client:: - - >>> from django.test import Client - >>> csrf_client = Client(enforce_csrf_checks=True) - -Making requests -~~~~~~~~~~~~~~~ - -Use the ``django.test.client.Client`` class to make requests. - -.. class:: Client(enforce_csrf_checks=False, **defaults) - - It requires no arguments at time of construction. However, you can use - keywords arguments to specify some default headers. For example, this will - send a ``User-Agent`` HTTP header in each request:: - - >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') - - The values from the ``extra`` keywords arguments passed to - :meth:`~django.test.client.Client.get()`, - :meth:`~django.test.client.Client.post()`, etc. have precedence over - the defaults passed to the class constructor. - - The ``enforce_csrf_checks`` argument can be used to test CSRF - protection (see above). - - Once you have a ``Client`` instance, you can call any of the following - methods: - - .. method:: Client.get(path, data={}, follow=False, **extra) - - - Makes a GET request on the provided ``path`` and returns a ``Response`` - object, which is documented below. - - The key-value pairs in the ``data`` dictionary are used to create a GET - data payload. For example:: - - >>> c = Client() - >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}) - - ...will result in the evaluation of a GET request equivalent to:: - - /customers/details/?name=fred&age=7 - - The ``extra`` keyword arguments parameter can be used to specify - headers to be sent in the request. For example:: - - >>> c = Client() - >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, - ... HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - ...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the - details view, which is a good way to test code paths that use the - :meth:`django.http.HttpRequest.is_ajax()` method. - - .. admonition:: CGI specification - - The headers sent via ``**extra`` should follow CGI_ specification. - For example, emulating a different "Host" header as sent in the - HTTP request from the browser to the server should be passed - as ``HTTP_HOST``. - - .. _CGI: http://www.w3.org/CGI/ - - If you already have the GET arguments in URL-encoded form, you can - use that encoding instead of using the data argument. For example, - the previous GET request could also be posed as:: - - >>> c = Client() - >>> c.get('/customers/details/?name=fred&age=7') - - If you provide a URL with both an encoded GET data and a data argument, - the data argument will take precedence. - - If you set ``follow`` to ``True`` the client will follow any redirects - and a ``redirect_chain`` attribute will be set in the response object - containing tuples of the intermediate urls and status codes. - - If you had a URL ``/redirect_me/`` that redirected to ``/next/``, that - redirected to ``/final/``, this is what you'd see:: - - >>> response = c.get('/redirect_me/', follow=True) - >>> response.redirect_chain - [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] - - .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) - - Makes a POST request on the provided ``path`` and returns a - ``Response`` object, which is documented below. - - The key-value pairs in the ``data`` dictionary are used to submit POST - data. For example:: - - >>> c = Client() - >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) - - ...will result in the evaluation of a POST request to this URL:: - - /login/ - - ...with this POST data:: - - name=fred&passwd=secret - - If you provide ``content_type`` (e.g. :mimetype:`text/xml` for an XML - payload), the contents of ``data`` will be sent as-is in the POST - request, using ``content_type`` in the HTTP ``Content-Type`` header. - - If you don't provide a value for ``content_type``, the values in - ``data`` will be transmitted with a content type of - :mimetype:`multipart/form-data`. In this case, the key-value pairs in - ``data`` will be encoded as a multipart message and used to create the - POST data payload. - - To submit multiple values for a given key -- for example, to specify - the selections for a ``', - '') - - ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be - raised if one of them cannot be parsed. - -.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) - - .. versionadded:: 1.4 - - Asserts that the strings ``html1`` and ``html2`` are *not* equal. The - comparison is based on HTML semantics. See - :meth:`~SimpleTestCase.assertHTMLEqual` for details. - - ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be - raised if one of them cannot be parsed. - -.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) - - .. versionadded:: 1.5 - - Asserts that the strings ``xml1`` and ``xml2`` are equal. The - comparison is based on XML semantics. Similarily to - :meth:`~SimpleTestCase.assertHTMLEqual`, the comparison is - made on parsed content, hence only semantic differences are considered, not - syntax differences. When unvalid XML is passed in any parameter, an - ``AssertionError`` is always raised, even if both string are identical. - -.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) - - .. versionadded:: 1.5 - - Asserts that the strings ``xml1`` and ``xml2`` are *not* equal. The - comparison is based on XML semantics. See - :meth:`~SimpleTestCase.assertXMLEqual` for details. - -.. _topics-testing-email: - -Email services --------------- - -If any of your Django views send email using :doc:`Django's email -functionality `, you probably don't want to send email each time -you run a test using that view. For this reason, Django's test runner -automatically redirects all Django-sent email to a dummy outbox. This lets you -test every aspect of sending email -- from the number of messages sent to the -contents of each message -- without actually sending the messages. - -The test runner accomplishes this by transparently replacing the normal -email backend with a testing backend. -(Don't worry -- this has no effect on any other email senders outside of -Django, such as your machine's mail server, if you're running one.) - -.. currentmodule:: django.core.mail - -.. data:: django.core.mail.outbox - -During test running, each outgoing email is saved in -``django.core.mail.outbox``. This is a simple list of all -:class:`~django.core.mail.EmailMessage` instances that have been sent. -The ``outbox`` attribute is a special attribute that is created *only* when -the ``locmem`` email backend is used. It doesn't normally exist as part of the -:mod:`django.core.mail` module and you can't import it directly. The code -below shows how to access this attribute correctly. - -Here's an example test that examines ``django.core.mail.outbox`` for length -and contents:: - - from django.core import mail - from django.test import TestCase - - class EmailTest(TestCase): - def test_send_email(self): - # Send message. - mail.send_mail('Subject here', 'Here is the message.', - 'from@example.com', ['to@example.com'], - fail_silently=False) - - # Test that one message has been sent. - self.assertEqual(len(mail.outbox), 1) - - # Verify that the subject of the first message is correct. - self.assertEqual(mail.outbox[0].subject, 'Subject here') - -As noted :ref:`previously `, the test outbox is emptied -at the start of every test in a Django ``TestCase``. To empty the outbox -manually, assign the empty list to ``mail.outbox``:: - - from django.core import mail - - # Empty the test outbox - mail.outbox = [] - -.. _skipping-tests: - -Skipping tests --------------- - -.. currentmodule:: django.test - -The unittest library provides the :func:`@skipIf ` and -:func:`@skipUnless ` decorators to allow you to skip tests -if you know ahead of time that those tests are going to fail under certain -conditions. - -For example, if your test requires a particular optional library in order to -succeed, you could decorate the test case with :func:`@skipIf -`. Then, the test runner will report that the test wasn't -executed and why, instead of failing the test or omitting the test altogether. - -To supplement these test skipping behaviors, Django provides two -additional skip decorators. Instead of testing a generic boolean, -these decorators check the capabilities of the database, and skip the -test if the database doesn't support a specific named feature. - -The decorators use a string identifier to describe database features. -This string corresponds to attributes of the database connection -features class. See :class:`~django.db.backends.BaseDatabaseFeatures` -class for a full list of database features that can be used as a basis -for skipping tests. - -.. function:: skipIfDBFeature(feature_name_string) - -Skip the decorated test if the named database feature is supported. - -For example, the following test will not be executed if the database -supports transactions (e.g., it would *not* run under PostgreSQL, but -it would under MySQL with MyISAM tables):: - - class MyTests(TestCase): - @skipIfDBFeature('supports_transactions') - def test_transaction_behavior(self): - # ... conditional test code - -.. function:: skipUnlessDBFeature(feature_name_string) - -Skip the decorated test if the named database feature is *not* -supported. - -For example, the following test will only be executed if the database -supports transactions (e.g., it would run under PostgreSQL, but *not* -under MySQL with MyISAM tables):: - - class MyTests(TestCase): - @skipUnlessDBFeature('supports_transactions') - def test_transaction_behavior(self): - # ... conditional test code - -Live test server ----------------- - -.. versionadded:: 1.4 - -.. currentmodule:: django.test - -.. class:: LiveServerTestCase() - -``LiveServerTestCase`` does basically the same as -:class:`~django.test.TransactionTestCase` with one extra feature: it launches a -live Django server in the background on setup, and shuts it down on teardown. -This allows the use of automated test clients other than the -:ref:`Django dummy client ` such as, for example, the Selenium_ -client, to execute a series of functional tests inside a browser and simulate a -real user's actions. - -By default the live server's address is `'localhost:8081'` and the full URL -can be accessed during the tests with ``self.live_server_url``. If you'd like -to change the default address (in the case, for example, where the 8081 port is -already taken) then you may pass a different one to the :djadmin:`test` command -via the :djadminopt:`--liveserver` option, for example: - -.. code-block:: bash - - ./manage.py test --liveserver=localhost:8082 - -Another way of changing the default server address is by setting the -`DJANGO_LIVE_TEST_SERVER_ADDRESS` environment variable somewhere in your -code (for example, in a :ref:`custom test runner`): - -.. code-block:: python - - import os - os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8082' - -In the case where the tests are run by multiple processes in parallel (for -example, in the context of several simultaneous `continuous integration`_ -builds), the processes will compete for the same address, and therefore your -tests might randomly fail with an "Address already in use" error. To avoid this -problem, you can pass a comma-separated list of ports or ranges of ports (at -least as many as the number of potential parallel processes). For example: - -.. code-block:: bash - - ./manage.py test --liveserver=localhost:8082,8090-8100,9000-9200,7041 - -Then, during test execution, each new live test server will try every specified -port until it finds one that is free and takes it. - -.. _continuous integration: http://en.wikipedia.org/wiki/Continuous_integration - -To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium -test. First of all, you need to install the `selenium package`_ into your -Python path: - -.. code-block:: bash - - pip install selenium - -Then, add a ``LiveServerTestCase``-based test to your app's tests module -(for example: ``myapp/tests.py``). The code for this test may look as follows: - -.. code-block:: python - - from django.test import LiveServerTestCase - from selenium.webdriver.firefox.webdriver import WebDriver - - class MySeleniumTests(LiveServerTestCase): - fixtures = ['user-data.json'] - - @classmethod - def setUpClass(cls): - cls.selenium = WebDriver() - super(MySeleniumTests, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - cls.selenium.quit() - super(MySeleniumTests, cls).tearDownClass() - - def test_login(self): - self.selenium.get('%s%s' % (self.live_server_url, '/login/')) - username_input = self.selenium.find_element_by_name("username") - username_input.send_keys('myuser') - password_input = self.selenium.find_element_by_name("password") - password_input.send_keys('secret') - self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() - -Finally, you may run the test as follows: - -.. code-block:: bash - - ./manage.py test myapp.MySeleniumTests.test_login - -This example will automatically open Firefox then go to the login page, enter -the credentials and press the "Log in" button. Selenium offers other drivers in -case you do not have Firefox installed or wish to use another browser. The -example above is just a tiny fraction of what the Selenium client can do; check -out the `full reference`_ for more details. - -.. _Selenium: http://seleniumhq.org/ -.. _selenium package: http://pypi.python.org/pypi/selenium -.. _full reference: http://selenium-python.readthedocs.org/en/latest/api.html -.. _Firefox: http://www.mozilla.com/firefox/ - -.. note:: - - ``LiveServerTestCase`` makes use of the :doc:`staticfiles contrib app - ` so you'll need to have your project configured - accordingly (in particular by setting :setting:`STATIC_URL`). - -.. note:: - - When using an in-memory SQLite database to run the tests, the same database - connection will be shared by two threads in parallel: the thread in which - the live server is run and the thread in which the test case is run. It's - important to prevent simultaneous database queries via this shared - connection by the two threads, as that may sometimes randomly cause the - tests to fail. So you need to ensure that the two threads don't access the - database at the same time. In particular, this means that in some cases - (for example, just after clicking a link or submitting a form), you might - need to check that a response is received by Selenium and that the next - page is loaded before proceeding with further test execution. - Do this, for example, by making Selenium wait until the `` HTML tag - is found in the response (requires Selenium > 2.13): - - .. code-block:: python - - def test_login(self): - from selenium.webdriver.support.wait import WebDriverWait - timeout = 2 - ... - self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() - # Wait until the response is received - WebDriverWait(self.selenium, timeout).until( - lambda driver: driver.find_element_by_tag_name('body')) - - The tricky thing here is that there's really no such thing as a "page load," - especially in modern Web apps that generate HTML dynamically after the - server generates the initial document. So, simply checking for the presence - of `` in the response might not necessarily be appropriate for all - use cases. Please refer to the `Selenium FAQ`_ and - `Selenium documentation`_ for more information. - - .. _Selenium FAQ: http://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_WebDriver_fails_to_find_elements_/_Does_not_block_on_page_loa - .. _Selenium documentation: http://seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits - -Using different testing frameworks -================================== - -Clearly, :mod:`doctest` and :mod:`unittest` are not the only Python testing -frameworks. While Django doesn't provide explicit support for alternative -frameworks, it does provide a way to invoke tests constructed for an -alternative framework as if they were normal Django tests. - -When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER` -setting to determine what to do. By default, :setting:`TEST_RUNNER` points to -``'django.test.simple.DjangoTestSuiteRunner'``. This class defines the default Django -testing behavior. This behavior involves: - -#. Performing global pre-test setup. - -#. Looking for unit tests and doctests in the ``models.py`` and - ``tests.py`` files in each installed application. - -#. Creating the test databases. - -#. Running ``syncdb`` to install models and initial data into the test - databases. - -#. Running the unit tests and doctests that are found. - -#. Destroying the test databases. - -#. Performing global post-test teardown. - -If you define your own test runner class and point :setting:`TEST_RUNNER` at -that class, Django will execute your test runner whenever you run -``./manage.py test``. In this way, it is possible to use any test framework -that can be executed from Python code, or to modify the Django test execution -process to satisfy whatever testing requirements you may have. - -.. _topics-testing-test_runner: - -Defining a test runner ----------------------- - -.. currentmodule:: django.test.simple - -A test runner is a class defining a ``run_tests()`` method. Django ships -with a ``DjangoTestSuiteRunner`` class that defines the default Django -testing behavior. This class defines the ``run_tests()`` entry point, -plus a selection of other methods that are used to by ``run_tests()`` to -set up, execute and tear down the test suite. - -.. class:: DjangoTestSuiteRunner(verbosity=1, interactive=True, failfast=True, **kwargs) - - ``verbosity`` determines the amount of notification and debug information - that will be printed to the console; ``0`` is no output, ``1`` is normal - output, and ``2`` is verbose output. - - If ``interactive`` is ``True``, the test suite has permission to ask the - user for instructions when the test suite is executed. An example of this - behavior would be asking for permission to delete an existing test - database. If ``interactive`` is ``False``, the test suite must be able to - run without any manual intervention. - - If ``failfast`` is ``True``, the test suite will stop running after the - first test failure is detected. - - Django will, from time to time, extend the capabilities of - the test runner by adding new arguments. The ``**kwargs`` declaration - allows for this expansion. If you subclass ``DjangoTestSuiteRunner`` or - write your own test runner, ensure accept and handle the ``**kwargs`` - parameter. - - .. versionadded:: 1.4 - - Your test runner may also define additional command-line options. - If you add an ``option_list`` attribute to a subclassed test runner, - those options will be added to the list of command-line options that - the :djadmin:`test` command can use. - -Attributes -~~~~~~~~~~ - -.. attribute:: DjangoTestSuiteRunner.option_list - - .. versionadded:: 1.4 - - This is the tuple of ``optparse`` options which will be fed into the - management command's ``OptionParser`` for parsing arguments. See the - documentation for Python's ``optparse`` module for more details. - -Methods -~~~~~~~ - -.. method:: DjangoTestSuiteRunner.run_tests(test_labels, extra_tests=None, **kwargs) - - Run the test suite. - - ``test_labels`` is a list of strings describing the tests to be run. A test - label can take one of three forms: - - * ``app.TestCase.test_method`` -- Run a single test method in a test - case. - * ``app.TestCase`` -- Run all the test methods in a test case. - * ``app`` -- Search for and run all tests in the named application. - - If ``test_labels`` has a value of ``None``, the test runner should run - search for tests in all the applications in :setting:`INSTALLED_APPS`. - - ``extra_tests`` is a list of extra ``TestCase`` instances to add to the - suite that is executed by the test runner. These extra tests are run - in addition to those discovered in the modules listed in ``test_labels``. - - This method should return the number of tests that failed. - -.. method:: DjangoTestSuiteRunner.setup_test_environment(**kwargs) - - Sets up the test environment ready for testing. - -.. method:: DjangoTestSuiteRunner.build_suite(test_labels, extra_tests=None, **kwargs) - - Constructs a test suite that matches the test labels provided. - - ``test_labels`` is a list of strings describing the tests to be run. A test - label can take one of three forms: - - * ``app.TestCase.test_method`` -- Run a single test method in a test - case. - * ``app.TestCase`` -- Run all the test methods in a test case. - * ``app`` -- Search for and run all tests in the named application. - - If ``test_labels`` has a value of ``None``, the test runner should run - search for tests in all the applications in :setting:`INSTALLED_APPS`. - - ``extra_tests`` is a list of extra ``TestCase`` instances to add to the - suite that is executed by the test runner. These extra tests are run - in addition to those discovered in the modules listed in ``test_labels``. - - Returns a ``TestSuite`` instance ready to be run. - -.. method:: DjangoTestSuiteRunner.setup_databases(**kwargs) - - Creates the test databases. - - Returns a data structure that provides enough detail to undo the changes - that have been made. This data will be provided to the ``teardown_databases()`` - function at the conclusion of testing. - -.. method:: DjangoTestSuiteRunner.run_suite(suite, **kwargs) - - Runs the test suite. - - Returns the result produced by the running the test suite. - -.. method:: DjangoTestSuiteRunner.teardown_databases(old_config, **kwargs) - - Destroys the test databases, restoring pre-test conditions. - - ``old_config`` is a data structure defining the changes in the - database configuration that need to be reversed. It is the return - value of the ``setup_databases()`` method. - -.. method:: DjangoTestSuiteRunner.teardown_test_environment(**kwargs) - - Restores the pre-test environment. - -.. method:: DjangoTestSuiteRunner.suite_result(suite, result, **kwargs) - - Computes and returns a return code based on a test suite, and the result - from that test suite. - - -Testing utilities ------------------ - -.. module:: django.test.utils - :synopsis: Helpers to write custom test runners. - -To assist in the creation of your own test runner, Django provides a number of -utility methods in the ``django.test.utils`` module. - -.. function:: setup_test_environment() - - Performs any global pre-test setup, such as the installing the - instrumentation of the template rendering system and setting up - the dummy email outbox. - -.. function:: teardown_test_environment() - - Performs any global post-test teardown, such as removing the black - magic hooks into the template system and restoring normal email - services. - -.. currentmodule:: django.db.connection.creation - -The creation module of the database backend (``connection.creation``) -also provides some utilities that can be useful during testing. - -.. function:: create_test_db([verbosity=1, autoclobber=False]) - - Creates a new test database and runs ``syncdb`` against it. - - ``verbosity`` has the same behavior as in ``run_tests()``. - - ``autoclobber`` describes the behavior that will occur if a - database with the same name as the test database is discovered: - - * If ``autoclobber`` is ``False``, the user will be asked to - approve destroying the existing database. ``sys.exit`` is - called if the user does not approve. - - * If autoclobber is ``True``, the database will be destroyed - without consulting the user. - - Returns the name of the test database that it created. - - ``create_test_db()`` has the side effect of modifying the value of - :setting:`NAME` in :setting:`DATABASES` to match the name of the test - database. - -.. function:: destroy_test_db(old_database_name, [verbosity=1]) - - Destroys the database whose name is the value of :setting:`NAME` in - :setting:`DATABASES`, and sets :setting:`NAME` to the value of - ``old_database_name``. - - The ``verbosity`` argument has the same behavior as for - :class:`~django.test.simple.DjangoTestSuiteRunner`. diff --git a/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle b/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle new file mode 100644 index 0000000000..7211c0f3be --- /dev/null +++ b/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle @@ -0,0 +1,883 @@ + + + + + ActiveLayerIndex + 0 + ApplicationVersion + + com.omnigroup.OmniGrafflePro + 139.16.0.171715 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {559.28997802734375, 782.8900146484375}} + Class + SolidGraphic + ID + 2 + Style + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + BaseZoom + 0 + CanvasOrigin + {0, 0} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2012-12-16 18:52:14 +0000 + Creator + Aymeric Augustin + DisplayScale + 1.000 cm = 1.000 cm + GraphDocumentVersion + 8 + GraphicsList + + + Class + LineGraphic + Head + + ID + 8 + + ID + 29 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + -1 + Points + + {369, 459} + {216, 400.5} + + Style + + stroke + + HeadArrow + UMLInheritance + HeadScale + 0.79999995231628418 + Legacy + + LineType + 2 + TailArrow + 0 + + + Tail + + ID + 6 + Info + 2 + + + + Class + LineGraphic + Head + + ID + 12 + Info + 1 + + ID + 27 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + -1 + Points + + {135, 270} + {369, 225} + + Style + + stroke + + HeadArrow + UMLInheritance + HeadScale + 0.79999995231628418 + Legacy + + LineType + 2 + TailArrow + 0 + + + Tail + + ID + 26 + Position + 0.5 + + + + Class + LineGraphic + Head + + ID + 10 + + ID + 26 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + -1 + Points + + {135, 315} + {135, 225} + + Style + + stroke + + HeadArrow + UMLInheritance + HeadScale + 0.79999995231628418 + Legacy + + LineType + 2 + TailArrow + 0 + + + Tail + + ID + 9 + + + + Class + LineGraphic + Head + + ID + 9 + + ID + 25 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + -1 + Points + + {135, 387} + {135, 342} + + Style + + stroke + + HeadArrow + UMLInheritance + HeadScale + 0.79999995231628418 + Legacy + + LineType + 2 + TailArrow + 0 + + + Tail + + ID + 8 + + + + Class + LineGraphic + Head + + ID + 8 + + ID + 23 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + -1 + Points + + {135, 459} + {135, 414} + + Style + + stroke + + HeadArrow + UMLInheritance + HeadScale + 0.79999995231628418 + Legacy + + LineType + 2 + TailArrow + 0 + + + Tail + + ID + 7 + + + + Bounds + {{378, 252}, {81, 27}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 12 + + ID + 22 + Shape + NoteShape + Style + + stroke + + Color + + b + 0 + g + 0.501961 + r + 0 + + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\i\fs24 \cf2 Python < 2.7} + VerticalPad + 0 + + TextRelativeArea + {{0, 0}, {1, 1}} + + + Bounds + {{45, 252}, {81, 27}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 12 + + ID + 20 + Shape + NoteShape + Style + + stroke + + Color + + b + 0 + g + 0.501961 + r + 0 + + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\i\fs24 \cf2 Python \uc0\u8805 2.7} + VerticalPad + 0 + + + + Bounds + {{288, 198}, {162, 27}} + Class + ShapedGraphic + ID + 12 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase} + + + + Bounds + {{54, 198}, {162, 27}} + Class + ShapedGraphic + ID + 10 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase} + + + + Bounds + {{54, 315}, {162, 27}} + Class + ShapedGraphic + ID + 9 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 SimpleTestCase} + + + + Bounds + {{54, 387}, {162, 27}} + Class + ShapedGraphic + ID + 8 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TransactionTestCase} + + + + Bounds + {{54, 459}, {162, 27}} + Class + ShapedGraphic + ID + 7 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase} + + + + Bounds + {{288, 459}, {162, 27}} + Class + ShapedGraphic + ID + 6 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.666667 + + + stroke + + CornerRadius + 5 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 LiveServerTestCase} + + + + Bounds + {{18, 297}, {468, 207}} + Class + ShapedGraphic + ID + 13 + Shape + Rectangle + Style + + Text + + Align + 2 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.test} + + TextPlacement + 0 + + + Bounds + {{18, 153}, {225, 90}} + Class + ShapedGraphic + ID + 18 + Shape + Rectangle + Style + + Text + + Align + 2 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.utils.unittest\ += unittest (standard library)} + + TextPlacement + 0 + + + Bounds + {{261, 153}, {225, 90}} + Class + ShapedGraphic + ID + 19 + Shape + Rectangle + Style + + Text + + Align + 2 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.utils.unittest\ += unittest2 (bundled copy)} + + TextPlacement + 0 + + + GridInfo + + ShowsGrid + YES + SnapsToGrid + YES + + GuidesLocked + NO + GuidesVisible + YES + HPages + 1 + ImageCounter + 1 + KeepToScale + + Layers + + + Lock + NO + Name + Calque 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + dot + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2012-12-16 19:08:28 +0000 + Modifier + Aymeric Augustin + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + PageBreaks + YES + PrintInfo + + NSBottomMargin + + float + 41 + + NSHorizonalPagination + + coded + BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG + + NSLeftMargin + + float + 18 + + NSPaperSize + + size + {595.28997802734375, 841.8900146484375} + + NSPrintReverseOrientation + + int + 0 + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + Canevas 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + Frame + {{9, 4}, {694, 874}} + ListView + + OutlineWidth + 142 + RightSidebar + + ShowRuler + + Sidebar + + SidebarWidth + 120 + VisibleRegion + {{0, 0}, {559, 735}} + Zoom + 1 + ZoomValues + + + Canevas 1 + 1 + 1 + + + + + diff --git a/docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf b/docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf new file mode 100644 index 0000000000..cedaba22ac Binary files /dev/null and b/docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf differ diff --git a/docs/topics/testing/_images/django_unittest_classes_hierarchy.svg b/docs/topics/testing/_images/django_unittest_classes_hierarchy.svg new file mode 100644 index 0000000000..0482f044dd --- /dev/null +++ b/docs/topics/testing/_images/django_unittest_classes_hierarchy.svg @@ -0,0 +1,3 @@ + + +2012-12-16 19:08ZCanevas 1Calque 1django.utils.unittest= unittest2 (bundled copy)django.utils.unittest= unittest (standard library)django.testLiveServerTestCaseTestCaseTransactionTestCaseSimpleTestCaseTestCaseTestCasePython ≥ 2.7Python < 2.7 diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt new file mode 100644 index 0000000000..0674b2e41b --- /dev/null +++ b/docs/topics/testing/advanced.txt @@ -0,0 +1,429 @@ +======================= +Advanced testing topics +======================= + +The request factory +=================== + +.. module:: django.test.client + +.. class:: RequestFactory + +The :class:`~django.test.client.RequestFactory` shares the same API as +the test client. However, instead of behaving like a browser, the +RequestFactory provides a way to generate a request instance that can +be used as the first argument to any view. This means you can test a +view function the same way as you would test any other function -- as +a black box, with exactly known inputs, testing for specific outputs. + +The API for the :class:`~django.test.client.RequestFactory` is a slightly +restricted subset of the test client API: + +* It only has access to the HTTP methods :meth:`~Client.get()`, + :meth:`~Client.post()`, :meth:`~Client.put()`, + :meth:`~Client.delete()`, :meth:`~Client.head()` and + :meth:`~Client.options()`. + +* These methods accept all the same arguments *except* for + ``follows``. Since this is just a factory for producing + requests, it's up to you to handle the response. + +* It does not support middleware. Session and authentication + attributes must be supplied by the test itself if required + for the view to function properly. + +Example +------- + +The following is a simple unit test using the request factory:: + + from django.utils import unittest + from django.test.client import RequestFactory + + class SimpleTest(unittest.TestCase): + def setUp(self): + # Every test needs access to the request factory. + self.factory = RequestFactory() + + def test_details(self): + # Create an instance of a GET request. + request = self.factory.get('/customer/details') + + # Test my_view() as if it were deployed at /customer/details + response = my_view(request) + self.assertEqual(response.status_code, 200) + +.. _topics-testing-advanced-multidb: + +Tests and multiple databases +============================ + +.. _topics-testing-masterslave: + +Testing master/slave configurations +----------------------------------- + +If you're testing a multiple database configuration with master/slave +replication, this strategy of creating test databases poses a problem. +When the test databases are created, there won't be any replication, +and as a result, data created on the master won't be seen on the +slave. + +To compensate for this, Django allows you to define that a database is +a *test mirror*. Consider the following (simplified) example database +configuration:: + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'myproject', + 'HOST': 'dbmaster', + # ... plus some other settings + }, + 'slave': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'myproject', + 'HOST': 'dbslave', + 'TEST_MIRROR': 'default' + # ... plus some other settings + } + } + +In this setup, we have two database servers: ``dbmaster``, described +by the database alias ``default``, and ``dbslave`` described by the +alias ``slave``. As you might expect, ``dbslave`` has been configured +by the database administrator as a read slave of ``dbmaster``, so in +normal activity, any write to ``default`` will appear on ``slave``. + +If Django created two independent test databases, this would break any +tests that expected replication to occur. However, the ``slave`` +database has been configured as a test mirror (using the +:setting:`TEST_MIRROR` setting), indicating that under testing, +``slave`` should be treated as a mirror of ``default``. + +When the test environment is configured, a test version of ``slave`` +will *not* be created. Instead the connection to ``slave`` +will be redirected to point at ``default``. As a result, writes to +``default`` will appear on ``slave`` -- but because they are actually +the same database, not because there is data replication between the +two databases. + +.. _topics-testing-creation-dependencies: + +Controlling creation order for test databases +--------------------------------------------- + +By default, Django will always create the ``default`` database first. +However, no guarantees are made on the creation order of any other +databases in your test setup. + +If your database configuration requires a specific creation order, you +can specify the dependencies that exist using the +:setting:`TEST_DEPENDENCIES` setting. Consider the following +(simplified) example database configuration:: + + DATABASES = { + 'default': { + # ... db settings + 'TEST_DEPENDENCIES': ['diamonds'] + }, + 'diamonds': { + # ... db settings + }, + 'clubs': { + # ... db settings + 'TEST_DEPENDENCIES': ['diamonds'] + }, + 'spades': { + # ... db settings + 'TEST_DEPENDENCIES': ['diamonds','hearts'] + }, + 'hearts': { + # ... db settings + 'TEST_DEPENDENCIES': ['diamonds','clubs'] + } + } + +Under this configuration, the ``diamonds`` database will be created first, +as it is the only database alias without dependencies. The ``default`` and +``clubs`` alias will be created next (although the order of creation of this +pair is not guaranteed); then ``hearts``; and finally ``spades``. + +If there are any circular dependencies in the +:setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured`` +exception will be raised. + +Running tests outside the test runner +===================================== + +If you want to run tests outside of ``./manage.py test`` -- for example, +from a shell prompt -- you will need to set up the test +environment first. Django provides a convenience method to do this:: + + >>> from django.test.utils import setup_test_environment + >>> setup_test_environment() + +This convenience method sets up the test database, and puts other +Django features into modes that allow for repeatable testing. + +The call to :meth:`~django.test.utils.setup_test_environment` is made +automatically as part of the setup of ``./manage.py test``. You only +need to manually invoke this method if you're not using running your +tests via Django's test runner. + +.. _other-testing-frameworks: + +Using different testing frameworks +================================== + +Clearly, :mod:`doctest` and :mod:`unittest` are not the only Python testing +frameworks. While Django doesn't provide explicit support for alternative +frameworks, it does provide a way to invoke tests constructed for an +alternative framework as if they were normal Django tests. + +When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER` +setting to determine what to do. By default, :setting:`TEST_RUNNER` points to +``'django.test.simple.DjangoTestSuiteRunner'``. This class defines the default Django +testing behavior. This behavior involves: + +#. Performing global pre-test setup. + +#. Looking for unit tests and doctests in the ``models.py`` and + ``tests.py`` files in each installed application. + +#. Creating the test databases. + +#. Running ``syncdb`` to install models and initial data into the test + databases. + +#. Running the unit tests and doctests that are found. + +#. Destroying the test databases. + +#. Performing global post-test teardown. + +If you define your own test runner class and point :setting:`TEST_RUNNER` at +that class, Django will execute your test runner whenever you run +``./manage.py test``. In this way, it is possible to use any test framework +that can be executed from Python code, or to modify the Django test execution +process to satisfy whatever testing requirements you may have. + +.. _topics-testing-test_runner: + +Defining a test runner +---------------------- + +.. currentmodule:: django.test.simple + +A test runner is a class defining a ``run_tests()`` method. Django ships +with a ``DjangoTestSuiteRunner`` class that defines the default Django +testing behavior. This class defines the ``run_tests()`` entry point, +plus a selection of other methods that are used to by ``run_tests()`` to +set up, execute and tear down the test suite. + +.. class:: DjangoTestSuiteRunner(verbosity=1, interactive=True, failfast=True, **kwargs) + + ``verbosity`` determines the amount of notification and debug information + that will be printed to the console; ``0`` is no output, ``1`` is normal + output, and ``2`` is verbose output. + + If ``interactive`` is ``True``, the test suite has permission to ask the + user for instructions when the test suite is executed. An example of this + behavior would be asking for permission to delete an existing test + database. If ``interactive`` is ``False``, the test suite must be able to + run without any manual intervention. + + If ``failfast`` is ``True``, the test suite will stop running after the + first test failure is detected. + + Django will, from time to time, extend the capabilities of + the test runner by adding new arguments. The ``**kwargs`` declaration + allows for this expansion. If you subclass ``DjangoTestSuiteRunner`` or + write your own test runner, ensure accept and handle the ``**kwargs`` + parameter. + + .. versionadded:: 1.4 + + Your test runner may also define additional command-line options. + If you add an ``option_list`` attribute to a subclassed test runner, + those options will be added to the list of command-line options that + the :djadmin:`test` command can use. + +Attributes +~~~~~~~~~~ + +.. attribute:: DjangoTestSuiteRunner.option_list + + .. versionadded:: 1.4 + + This is the tuple of ``optparse`` options which will be fed into the + management command's ``OptionParser`` for parsing arguments. See the + documentation for Python's ``optparse`` module for more details. + +Methods +~~~~~~~ + +.. method:: DjangoTestSuiteRunner.run_tests(test_labels, extra_tests=None, **kwargs) + + Run the test suite. + + ``test_labels`` is a list of strings describing the tests to be run. A test + label can take one of three forms: + + * ``app.TestCase.test_method`` -- Run a single test method in a test + case. + * ``app.TestCase`` -- Run all the test methods in a test case. + * ``app`` -- Search for and run all tests in the named application. + + If ``test_labels`` has a value of ``None``, the test runner should run + search for tests in all the applications in :setting:`INSTALLED_APPS`. + + ``extra_tests`` is a list of extra ``TestCase`` instances to add to the + suite that is executed by the test runner. These extra tests are run + in addition to those discovered in the modules listed in ``test_labels``. + + This method should return the number of tests that failed. + +.. method:: DjangoTestSuiteRunner.setup_test_environment(**kwargs) + + Sets up the test environment ready for testing. + +.. method:: DjangoTestSuiteRunner.build_suite(test_labels, extra_tests=None, **kwargs) + + Constructs a test suite that matches the test labels provided. + + ``test_labels`` is a list of strings describing the tests to be run. A test + label can take one of three forms: + + * ``app.TestCase.test_method`` -- Run a single test method in a test + case. + * ``app.TestCase`` -- Run all the test methods in a test case. + * ``app`` -- Search for and run all tests in the named application. + + If ``test_labels`` has a value of ``None``, the test runner should run + search for tests in all the applications in :setting:`INSTALLED_APPS`. + + ``extra_tests`` is a list of extra ``TestCase`` instances to add to the + suite that is executed by the test runner. These extra tests are run + in addition to those discovered in the modules listed in ``test_labels``. + + Returns a ``TestSuite`` instance ready to be run. + +.. method:: DjangoTestSuiteRunner.setup_databases(**kwargs) + + Creates the test databases. + + Returns a data structure that provides enough detail to undo the changes + that have been made. This data will be provided to the ``teardown_databases()`` + function at the conclusion of testing. + +.. method:: DjangoTestSuiteRunner.run_suite(suite, **kwargs) + + Runs the test suite. + + Returns the result produced by the running the test suite. + +.. method:: DjangoTestSuiteRunner.teardown_databases(old_config, **kwargs) + + Destroys the test databases, restoring pre-test conditions. + + ``old_config`` is a data structure defining the changes in the + database configuration that need to be reversed. It is the return + value of the ``setup_databases()`` method. + +.. method:: DjangoTestSuiteRunner.teardown_test_environment(**kwargs) + + Restores the pre-test environment. + +.. method:: DjangoTestSuiteRunner.suite_result(suite, result, **kwargs) + + Computes and returns a return code based on a test suite, and the result + from that test suite. + + +Testing utilities +----------------- + +.. module:: django.test.utils + :synopsis: Helpers to write custom test runners. + +To assist in the creation of your own test runner, Django provides a number of +utility methods in the ``django.test.utils`` module. + +.. function:: setup_test_environment() + + Performs any global pre-test setup, such as the installing the + instrumentation of the template rendering system and setting up + the dummy email outbox. + +.. function:: teardown_test_environment() + + Performs any global post-test teardown, such as removing the black + magic hooks into the template system and restoring normal email + services. + +.. currentmodule:: django.db.connection.creation + +The creation module of the database backend (``connection.creation``) +also provides some utilities that can be useful during testing. + +.. function:: create_test_db([verbosity=1, autoclobber=False]) + + Creates a new test database and runs ``syncdb`` against it. + + ``verbosity`` has the same behavior as in ``run_tests()``. + + ``autoclobber`` describes the behavior that will occur if a + database with the same name as the test database is discovered: + + * If ``autoclobber`` is ``False``, the user will be asked to + approve destroying the existing database. ``sys.exit`` is + called if the user does not approve. + + * If autoclobber is ``True``, the database will be destroyed + without consulting the user. + + Returns the name of the test database that it created. + + ``create_test_db()`` has the side effect of modifying the value of + :setting:`NAME` in :setting:`DATABASES` to match the name of the test + database. + +.. function:: destroy_test_db(old_database_name, [verbosity=1]) + + Destroys the database whose name is the value of :setting:`NAME` in + :setting:`DATABASES`, and sets :setting:`NAME` to the value of + ``old_database_name``. + + The ``verbosity`` argument has the same behavior as for + :class:`~django.test.simple.DjangoTestSuiteRunner`. + +.. _topics-testing-code-coverage: + +Integration with coverage.py +============================ + +Code coverage describes how much source code has been tested. It shows which +parts of your code are being exercised by tests and which are not. It's an +important part of testing applications, so it's strongly recommended to check +the coverage of your tests. + +Django can be easily integrated with `coverage.py`_, a tool for measuring code +coverage of Python programs. First, `install coverage.py`_. Next, run the +following from your project folder containing ``manage.py``:: + + coverage run --source='.' manage.py test myapp + +This runs your tests and collects coverage data of the executed files in your +project. You can see a report of this data by typing following command:: + + coverage report + +Note that some Django code was executed while running tests, but it is not +listed here because of the ``source`` flag passed to the previous command. + +For more options like annotated HTML listings detailing missed lines, see the +`coverage.py`_ docs. + +.. _coverage.py: http://nedbatchelder.com/code/coverage/ +.. _install coverage.py: http://pypi.python.org/pypi/coverage diff --git a/docs/topics/testing/doctests.txt b/docs/topics/testing/doctests.txt new file mode 100644 index 0000000000..5036e946a9 --- /dev/null +++ b/docs/topics/testing/doctests.txt @@ -0,0 +1,81 @@ +=================== +Django and doctests +=================== + +Doctests use Python's standard :mod:`doctest` module, which searches your +docstrings for statements that resemble a session of the Python interactive +interpreter. A full explanation of how :mod:`doctest` works is out of the scope +of this document; read Python's official documentation for the details. + +.. admonition:: What's a **docstring**? + + A good explanation of docstrings (and some guidelines for using them + effectively) can be found in :pep:`257`: + + A docstring is a string literal that occurs as the first statement in + a module, function, class, or method definition. Such a docstring + becomes the ``__doc__`` special attribute of that object. + + For example, this function has a docstring that describes what it does:: + + def add_two(num): + "Return the result of adding two to the provided number." + return num + 2 + + Because tests often make great documentation, putting tests directly in + your docstrings is an effective way to document *and* test your code. + +As with unit tests, for a given Django application, the test runner looks for +doctests in two places: + +* The ``models.py`` file. You can define module-level doctests and/or a + doctest for individual models. It's common practice to put + application-level doctests in the module docstring and model-level + doctests in the model docstrings. + +* A file called ``tests.py`` in the application directory -- i.e., the + directory that holds ``models.py``. This file is a hook for any and all + doctests you want to write that aren't necessarily related to models. + +This example doctest is equivalent to the example given in the unittest section +above:: + + # models.py + + from django.db import models + + class Animal(models.Model): + """ + An animal that knows how to make noise + + # Create some animals + >>> lion = Animal.objects.create(name="lion", sound="roar") + >>> cat = Animal.objects.create(name="cat", sound="meow") + + # Make 'em speak + >>> lion.speak() + 'The lion says "roar"' + >>> cat.speak() + 'The cat says "meow"' + """ + name = models.CharField(max_length=20) + sound = models.CharField(max_length=20) + + def speak(self): + return 'The %s says "%s"' % (self.name, self.sound) + +When you :ref:`run your tests `, the test runner will find this +docstring, notice that portions of it look like an interactive Python session, +and execute those lines while checking that the results match. + +In the case of model tests, note that the test runner takes care of creating +its own test database. That is, any test that accesses a database -- by +creating and saving model instances, for example -- will not affect your +production database. However, the database is not refreshed between doctests, +so if your doctest requires a certain state you should consider flushing the +database or loading a fixture. (See the section on :ref:`fixtures +` for more on this.) Note that to use this feature, +the database user Django is connecting as must have ``CREATE DATABASE`` +rights. + +For more details about :mod:`doctest`, see the Python documentation. diff --git a/docs/topics/testing/index.txt b/docs/topics/testing/index.txt new file mode 100644 index 0000000000..0345b72703 --- /dev/null +++ b/docs/topics/testing/index.txt @@ -0,0 +1,111 @@ +================= +Testing in Django +================= + +.. toctree:: + :hidden: + + overview + doctests + advanced + +Automated testing is an extremely useful bug-killing tool for the modern +Web developer. You can use a collection of tests -- a **test suite** -- to +solve, or avoid, a number of problems: + +* When you're writing new code, you can use tests to validate your code + works as expected. + +* When you're refactoring or modifying old code, you can use tests to + ensure your changes haven't affected your application's behavior + unexpectedly. + +Testing a Web application is a complex task, because a Web application is made +of several layers of logic -- from HTTP-level request handling, to form +validation and processing, to template rendering. With Django's test-execution +framework and assorted utilities, you can simulate requests, insert test data, +inspect your application's output and generally verify your code is doing what +it should be doing. + +The best part is, it's really easy. + +Unit tests v. doctests +====================== + +There are two primary ways to write tests with Django, corresponding to the +two test frameworks that ship in the Python standard library. The two +frameworks are: + +* **Unit tests** -- tests that are expressed as methods on a Python class + that subclasses :class:`unittest.TestCase` or Django's customized + :class:`TestCase`. For example:: + + import unittest + + class MyFuncTestCase(unittest.TestCase): + def testBasic(self): + a = ['larry', 'curly', 'moe'] + self.assertEqual(my_func(a, 0), 'larry') + self.assertEqual(my_func(a, 1), 'curly') + +* **Doctests** -- tests that are embedded in your functions' docstrings and + are written in a way that emulates a session of the Python interactive + interpreter. For example:: + + def my_func(a_list, idx): + """ + >>> a = ['larry', 'curly', 'moe'] + >>> my_func(a, 0) + 'larry' + >>> my_func(a, 1) + 'curly' + """ + return a_list[idx] + +Which should I use? +------------------- + +Because Django supports both of the standard Python test frameworks, it's up to +you and your tastes to decide which one to use. You can even decide to use +*both*. + +For developers new to testing, however, this choice can seem confusing. Here, +then, are a few key differences to help you decide which approach is right for +you: + +* If you've been using Python for a while, :mod:`doctest` will probably feel + more "pythonic". It's designed to make writing tests as easy as possible, + so it requires no overhead of writing classes or methods. You simply put + tests in docstrings. This has the added advantage of serving as + documentation (and correct documentation, at that!). However, while + doctests are good for some simple example code, they are not very good if + you want to produce either high quality, comprehensive tests or high + quality documentation. Test failures are often difficult to debug + as it can be unclear exactly why the test failed. Thus, doctests should + generally be avoided and used primarily for documentation examples only. + +* The :mod:`unittest` framework will probably feel very familiar to + developers coming from Java. :mod:`unittest` is inspired by Java's JUnit, + so you'll feel at home with this method if you've used JUnit or any test + framework inspired by JUnit. + +* If you need to write a bunch of tests that share similar code, then + you'll appreciate the :mod:`unittest` framework's organization around + classes and methods. This makes it easy to abstract common tasks into + common methods. The framework also supports explicit setup and/or cleanup + routines, which give you a high level of control over the environment + in which your test cases are run. + +* If you're writing tests for Django itself, you should use :mod:`unittest`. + +Where to go from here +===================== + +As unit tests are preferred in Django, we treat them in detail in the +:doc:`overview` document. + +:doc:`doctests` describes Django-specific features when using doctests. + +You can also use any *other* Python test framework, Django provides an API and +tools for that kind of integration. They are described in the +:ref:`other-testing-frameworks` section of :doc:`advanced`. diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt new file mode 100644 index 0000000000..5f64789019 --- /dev/null +++ b/docs/topics/testing/overview.txt @@ -0,0 +1,1784 @@ +=========================== +Testing Django applications +=========================== + +.. module:: django.test + :synopsis: Testing tools for Django applications. + +.. seealso:: + + The :doc:`testing tutorial ` and the + :doc:`advanced testing topics `. + +This document is split into two primary sections. First, we explain how to write +tests with Django. Then, we explain how to run them. + +Writing tests +============= + +Django's unit tests use a Python standard library module: :mod:`unittest`. This +module defines tests in class-based approach. + +.. admonition:: unittest2 + + Python 2.7 introduced some major changes to the unittest library, + adding some extremely useful features. To ensure that every Django + project can benefit from these new features, Django ships with a + copy of unittest2_, a copy of the Python 2.7 unittest library, + backported for Python 2.5 compatibility. + + To access this library, Django provides the + :mod:`django.utils.unittest` module alias. If you are using Python + 2.7, or you have installed unittest2 locally, Django will map the + alias to the installed version of the unittest library. Otherwise, + Django will use its own bundled version of unittest2. + + To use this alias, simply use:: + + from django.utils import unittest + + wherever you would have historically used:: + + import unittest + + If you want to continue to use the base unittest library, you can -- + you just won't get any of the nice new unittest2 features. + +.. _unittest2: http://pypi.python.org/pypi/unittest2 + +For a given Django application, the test runner looks for unit tests in two +places: + +* The ``models.py`` file. The test runner looks for any subclass of + :class:`unittest.TestCase` in this module. + +* A file called ``tests.py`` in the application directory -- i.e., the + directory that holds ``models.py``. Again, the test runner looks for any + subclass of :class:`unittest.TestCase` in this module. + +Here is an example :class:`unittest.TestCase` subclass:: + + from django.utils import unittest + from myapp.models import Animal + + class AnimalTestCase(unittest.TestCase): + def setUp(self): + self.lion = Animal(name="lion", sound="roar") + self.cat = Animal(name="cat", sound="meow") + + def test_animals_can_speak(self): + """Animals that can speak are correctly identified""" + self.assertEqual(self.lion.speak(), 'The lion says "roar"') + self.assertEqual(self.cat.speak(), 'The cat says "meow"') + +When you :ref:`run your tests `, the default behavior of the test +utility is to find all the test cases (that is, subclasses of +:class:`unittest.TestCase`) in ``models.py`` and ``tests.py``, automatically +build a test suite out of those test cases, and run that suite. + +There is a second way to define the test suite for a module: if you define a +function called ``suite()`` in either ``models.py`` or ``tests.py``, the +Django test runner will use that function to construct the test suite for that +module. This follows the `suggested organization`_ for unit tests. See the +Python documentation for more details on how to construct a complex test +suite. + +For more details about :mod:`unittest`, see the Python documentation. + +.. _suggested organization: http://docs.python.org/library/unittest.html#organizing-tests + +.. warning:: + + If your tests rely on database access such as creating or querying models, + be sure to create your test classes as subclasses of + :class:`django.test.TestCase` rather than :class:`unittest.TestCase`. + + In the example above, we instantiate some models but do not save them to + the database. Using :class:`unittest.TestCase` avoids the cost of running + each test in a transaction and flushing the database, but for most + applications the scope of tests you will be able to write this way will + be fairly limited, so it's easiest to use :class:`django.test.TestCase`. + +.. _running-tests: + +Running tests +============= + +Once you've written tests, run them using the :djadmin:`test` command of +your project's ``manage.py`` utility:: + + $ ./manage.py test + +By default, this will run every test in every application in +:setting:`INSTALLED_APPS`. If you only want to run tests for a particular +application, add the application name to the command line. For example, if your +:setting:`INSTALLED_APPS` contains ``'myproject.polls'`` and +``'myproject.animals'``, you can run the ``myproject.animals`` unit tests alone +with this command:: + + $ ./manage.py test animals + +Note that we used ``animals``, not ``myproject.animals``. + +You can be even *more* specific by naming an individual test case. To +run a single test case in an application (for example, the +``AnimalTestCase`` described in the "Writing unit tests" section), add +the name of the test case to the label on the command line:: + + $ ./manage.py test animals.AnimalTestCase + +And it gets even more granular than that! To run a *single* test +method inside a test case, add the name of the test method to the +label:: + + $ ./manage.py test animals.AnimalTestCase.test_animals_can_speak + +You can use the same rules if you're using doctests. Django will use the +test label as a path to the test method or class that you want to run. +If your ``models.py`` or ``tests.py`` has a function with a doctest, or +class with a class-level doctest, you can invoke that test by appending the +name of the test method or class to the label:: + + $ ./manage.py test animals.classify + +If you want to run the doctest for a specific method in a class, add the +name of the method to the label:: + + $ ./manage.py test animals.Classifier.run + +If you're using a ``__test__`` dictionary to specify doctests for a +module, Django will use the label as a key in the ``__test__`` dictionary +for defined in ``models.py`` and ``tests.py``. + +If you press ``Ctrl-C`` while the tests are running, the test runner will +wait for the currently running test to complete and then exit gracefully. +During a graceful exit the test runner will output details of any test +failures, report on how many tests were run and how many errors and failures +were encountered, and destroy any test databases as usual. Thus pressing +``Ctrl-C`` can be very useful if you forget to pass the :djadminopt:`--failfast` +option, notice that some tests are unexpectedly failing, and want to get details +on the failures without waiting for the full test run to complete. + +If you do not want to wait for the currently running test to finish, you +can press ``Ctrl-C`` a second time and the test run will halt immediately, +but not gracefully. No details of the tests run before the interruption will +be reported, and any test databases created by the run will not be destroyed. + +.. admonition:: Test with warnings enabled + + It's a good idea to run your tests with Python warnings enabled: + ``python -Wall manage.py test``. The ``-Wall`` flag tells Python to + display deprecation warnings. Django, like many other Python libraries, + uses these warnings to flag when features are going away. It also might + flag areas in your code that aren't strictly wrong but could benefit + from a better implementation. + +.. _the-test-database: + +The test database +----------------- + +Tests that require a database (namely, model tests) will not use your "real" +(production) database. Separate, blank databases are created for the tests. + +Regardless of whether the tests pass or fail, the test databases are destroyed +when all the tests have been executed. + +By default the test databases get their names by prepending ``test_`` +to the value of the :setting:`NAME` settings for the databases +defined in :setting:`DATABASES`. When using the SQLite database engine +the tests will by default use an in-memory database (i.e., the +database will be created in memory, bypassing the filesystem +entirely!). If you want to use a different database name, specify +:setting:`TEST_NAME` in the dictionary for any given database in +:setting:`DATABASES`. + +Aside from using a separate database, the test runner will otherwise +use all of the same database settings you have in your settings file: +:setting:`ENGINE`, :setting:`USER`, :setting:`HOST`, etc. The test +database is created by the user specified by :setting:`USER`, so you'll need +to make sure that the given user account has sufficient privileges to +create a new database on the system. + +For fine-grained control over the character encoding of your test +database, use the :setting:`TEST_CHARSET` option. If you're using +MySQL, you can also use the :setting:`TEST_COLLATION` option to +control the particular collation used by the test database. See the +:doc:`settings documentation ` for details of these +advanced settings. + +.. admonition:: Finding data from your production database when running tests? + + If your code attempts to access the database when its modules are compiled, + this will occur *before* the test database is set up, with potentially + unexpected results. For example, if you have a database query in + module-level code and a real database exists, production data could pollute + your tests. *It is a bad idea to have such import-time database queries in + your code* anyway - rewrite your code so that it doesn't do this. + +.. seealso:: + + The :ref:`advanced multi-db testing topics `. + +Order in which tests are executed +--------------------------------- + +In order to guarantee that all ``TestCase`` code starts with a clean database, +the Django test runner reorders tests in the following way: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +.. versionchanged:: 1.5 + Before Django 1.5, the only guarantee was that + :class:`~django.test.TestCase` tests were always ran first, before any other + tests. + +.. note:: + + The new ordering of tests may reveal unexpected dependencies on test case + ordering. This is the case with doctests that relied on state left in the + database by a given :class:`~django.test.TransactionTestCase` test, they + must be updated to be able to run independently. + +Other test conditions +--------------------- + +Regardless of the value of the :setting:`DEBUG` setting in your configuration +file, all Django tests run with :setting:`DEBUG`\=False. This is to ensure that +the observed output of your code matches what will be seen in a production +setting. + +Caches are not cleared after each test, and running "manage.py test fooapp" can +insert data from the tests into the cache of a live system if you run your +tests in production because, unlike databases, a separate "test cache" is not +used. This behavior `may change`_ in the future. + +.. _may change: https://code.djangoproject.com/ticket/11505 + +Understanding the test output +----------------------------- + +When you run your tests, you'll see a number of messages as the test runner +prepares itself. You can control the level of detail of these messages with the +``verbosity`` option on the command line:: + + Creating test database... + Creating table myapp_animal + Creating table myapp_mineral + Loading 'initial_data' fixtures... + No fixtures found. + +This tells you that the test runner is creating a test database, as described +in the previous section. + +Once the test database has been created, Django will run your tests. +If everything goes well, you'll see something like this:: + + ---------------------------------------------------------------------- + Ran 22 tests in 0.221s + + OK + +If there are test failures, however, you'll see full details about which tests +failed:: + + ====================================================================== + FAIL: Doctest: ellington.core.throttle.models + ---------------------------------------------------------------------- + Traceback (most recent call last): + File "/dev/django/test/doctest.py", line 2153, in runTest + raise self.failureException(self.format_failure(new.getvalue())) + AssertionError: Failed doctest test for myapp.models + File "/dev/myapp/models.py", line 0, in models + + ---------------------------------------------------------------------- + File "/dev/myapp/models.py", line 14, in myapp.models + Failed example: + throttle.check("actor A", "action one", limit=2, hours=1) + Expected: + True + Got: + False + + ---------------------------------------------------------------------- + Ran 2 tests in 0.048s + + FAILED (failures=1) + +A full explanation of this error output is beyond the scope of this document, +but it's pretty intuitive. You can consult the documentation of Python's +:mod:`unittest` library for details. + +Note that the return code for the test-runner script is 1 for any number of +failed and erroneous tests. If all the tests pass, the return code is 0. This +feature is useful if you're using the test-runner script in a shell script and +need to test for success or failure at that level. + +Speeding up the tests +--------------------- + +In recent versions of Django, the default password hasher is rather slow by +design. If during your tests you are authenticating many users, you may want +to use a custom settings file and set the :setting:`PASSWORD_HASHERS` setting +to a faster hashing algorithm:: + + PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ) + +Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing +algorithm used in fixtures, if any. + +Testing tools +============= + +Django provides a small set of tools that come in handy when writing tests. + +.. _test-client: + +The test client +--------------- + +.. module:: django.test.client + :synopsis: Django's test client. + +The test client is a Python class that acts as a dummy Web browser, allowing +you to test your views and interact with your Django-powered application +programmatically. + +Some of the things you can do with the test client are: + +* Simulate GET and POST requests on a URL and observe the response -- + everything from low-level HTTP (result headers and status codes) to + page content. + +* Test that the correct view is executed for a given URL. + +* Test that a given request is rendered by a given Django template, with + a template context that contains certain values. + +Note that the test client is not intended to be a replacement for Selenium_ or +other "in-browser" frameworks. Django's test client has a different focus. In +short: + +* Use Django's test client to establish that the correct view is being + called and that the view is collecting the correct context data. + +* Use in-browser frameworks like Selenium_ to test *rendered* HTML and the + *behavior* of Web pages, namely JavaScript functionality. Django also + provides special support for those frameworks; see the section on + :class:`~django.test.LiveServerTestCase` for more details. + +A comprehensive test suite should use a combination of both test types. + +Overview and a quick example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the test client, instantiate ``django.test.client.Client`` and retrieve +Web pages:: + + >>> from django.test.client import Client + >>> c = Client() + >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) + >>> response.status_code + 200 + >>> response = c.get('/customer/details/') + >>> response.content + '>> c.get('/login/') + + This is incorrect:: + + >>> c.get('http://www.example.com/login/') + + The test client is not capable of retrieving Web pages that are not + powered by your Django project. If you need to retrieve other Web pages, + use a Python standard library module such as :mod:`urllib` or + :mod:`urllib2`. + +* To resolve URLs, the test client uses whatever URLconf is pointed-to by + your :setting:`ROOT_URLCONF` setting. + +* Although the above example would work in the Python interactive + interpreter, some of the test client's functionality, notably the + template-related functionality, is only available *while tests are + running*. + + The reason for this is that Django's test runner performs a bit of black + magic in order to determine which template was loaded by a given view. + This black magic (essentially a patching of Django's template system in + memory) only happens during test running. + +* By default, the test client will disable any CSRF checks + performed by your site. + + If, for some reason, you *want* the test client to perform CSRF + checks, you can create an instance of the test client that + enforces CSRF checks. To do this, pass in the + ``enforce_csrf_checks`` argument when you construct your + client:: + + >>> from django.test import Client + >>> csrf_client = Client(enforce_csrf_checks=True) + +Making requests +~~~~~~~~~~~~~~~ + +Use the ``django.test.client.Client`` class to make requests. + +.. class:: Client(enforce_csrf_checks=False, **defaults) + + It requires no arguments at time of construction. However, you can use + keywords arguments to specify some default headers. For example, this will + send a ``User-Agent`` HTTP header in each request:: + + >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') + + The values from the ``extra`` keywords arguments passed to + :meth:`~django.test.client.Client.get()`, + :meth:`~django.test.client.Client.post()`, etc. have precedence over + the defaults passed to the class constructor. + + The ``enforce_csrf_checks`` argument can be used to test CSRF + protection (see above). + + Once you have a ``Client`` instance, you can call any of the following + methods: + + .. method:: Client.get(path, data={}, follow=False, **extra) + + + Makes a GET request on the provided ``path`` and returns a ``Response`` + object, which is documented below. + + The key-value pairs in the ``data`` dictionary are used to create a GET + data payload. For example:: + + >>> c = Client() + >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}) + + ...will result in the evaluation of a GET request equivalent to:: + + /customers/details/?name=fred&age=7 + + The ``extra`` keyword arguments parameter can be used to specify + headers to be sent in the request. For example:: + + >>> c = Client() + >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, + ... HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + ...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the + details view, which is a good way to test code paths that use the + :meth:`django.http.HttpRequest.is_ajax()` method. + + .. admonition:: CGI specification + + The headers sent via ``**extra`` should follow CGI_ specification. + For example, emulating a different "Host" header as sent in the + HTTP request from the browser to the server should be passed + as ``HTTP_HOST``. + + .. _CGI: http://www.w3.org/CGI/ + + If you already have the GET arguments in URL-encoded form, you can + use that encoding instead of using the data argument. For example, + the previous GET request could also be posed as:: + + >>> c = Client() + >>> c.get('/customers/details/?name=fred&age=7') + + If you provide a URL with both an encoded GET data and a data argument, + the data argument will take precedence. + + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + If you had a URL ``/redirect_me/`` that redirected to ``/next/``, that + redirected to ``/final/``, this is what you'd see:: + + >>> response = c.get('/redirect_me/', follow=True) + >>> response.redirect_chain + [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] + + .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) + + Makes a POST request on the provided ``path`` and returns a + ``Response`` object, which is documented below. + + The key-value pairs in the ``data`` dictionary are used to submit POST + data. For example:: + + >>> c = Client() + >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) + + ...will result in the evaluation of a POST request to this URL:: + + /login/ + + ...with this POST data:: + + name=fred&passwd=secret + + If you provide ``content_type`` (e.g. :mimetype:`text/xml` for an XML + payload), the contents of ``data`` will be sent as-is in the POST + request, using ``content_type`` in the HTTP ``Content-Type`` header. + + If you don't provide a value for ``content_type``, the values in + ``data`` will be transmitted with a content type of + :mimetype:`multipart/form-data`. In this case, the key-value pairs in + ``data`` will be encoded as a multipart message and used to create the + POST data payload. + + To submit multiple values for a given key -- for example, to specify + the selections for a ``', + '') + + ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be + raised if one of them cannot be parsed. + +.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) + + .. versionadded:: 1.4 + + Asserts that the strings ``html1`` and ``html2`` are *not* equal. The + comparison is based on HTML semantics. See + :meth:`~SimpleTestCase.assertHTMLEqual` for details. + + ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be + raised if one of them cannot be parsed. + +.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) + + .. versionadded:: 1.5 + + Asserts that the strings ``xml1`` and ``xml2`` are equal. The + comparison is based on XML semantics. Similarily to + :meth:`~SimpleTestCase.assertHTMLEqual`, the comparison is + made on parsed content, hence only semantic differences are considered, not + syntax differences. When unvalid XML is passed in any parameter, an + ``AssertionError`` is always raised, even if both string are identical. + +.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) + + .. versionadded:: 1.5 + + Asserts that the strings ``xml1`` and ``xml2`` are *not* equal. The + comparison is based on XML semantics. See + :meth:`~SimpleTestCase.assertXMLEqual` for details. + +.. _topics-testing-email: + +Email services +-------------- + +If any of your Django views send email using :doc:`Django's email +functionality `, you probably don't want to send email each time +you run a test using that view. For this reason, Django's test runner +automatically redirects all Django-sent email to a dummy outbox. This lets you +test every aspect of sending email -- from the number of messages sent to the +contents of each message -- without actually sending the messages. + +The test runner accomplishes this by transparently replacing the normal +email backend with a testing backend. +(Don't worry -- this has no effect on any other email senders outside of +Django, such as your machine's mail server, if you're running one.) + +.. currentmodule:: django.core.mail + +.. data:: django.core.mail.outbox + +During test running, each outgoing email is saved in +``django.core.mail.outbox``. This is a simple list of all +:class:`~django.core.mail.EmailMessage` instances that have been sent. +The ``outbox`` attribute is a special attribute that is created *only* when +the ``locmem`` email backend is used. It doesn't normally exist as part of the +:mod:`django.core.mail` module and you can't import it directly. The code +below shows how to access this attribute correctly. + +Here's an example test that examines ``django.core.mail.outbox`` for length +and contents:: + + from django.core import mail + from django.test import TestCase + + class EmailTest(TestCase): + def test_send_email(self): + # Send message. + mail.send_mail('Subject here', 'Here is the message.', + 'from@example.com', ['to@example.com'], + fail_silently=False) + + # Test that one message has been sent. + self.assertEqual(len(mail.outbox), 1) + + # Verify that the subject of the first message is correct. + self.assertEqual(mail.outbox[0].subject, 'Subject here') + +As noted :ref:`previously `, the test outbox is emptied +at the start of every test in a Django ``TestCase``. To empty the outbox +manually, assign the empty list to ``mail.outbox``:: + + from django.core import mail + + # Empty the test outbox + mail.outbox = [] + +.. _skipping-tests: + +Skipping tests +-------------- + +.. currentmodule:: django.test + +The unittest library provides the :func:`@skipIf ` and +:func:`@skipUnless ` decorators to allow you to skip tests +if you know ahead of time that those tests are going to fail under certain +conditions. + +For example, if your test requires a particular optional library in order to +succeed, you could decorate the test case with :func:`@skipIf +`. Then, the test runner will report that the test wasn't +executed and why, instead of failing the test or omitting the test altogether. + +To supplement these test skipping behaviors, Django provides two +additional skip decorators. Instead of testing a generic boolean, +these decorators check the capabilities of the database, and skip the +test if the database doesn't support a specific named feature. + +The decorators use a string identifier to describe database features. +This string corresponds to attributes of the database connection +features class. See :class:`~django.db.backends.BaseDatabaseFeatures` +class for a full list of database features that can be used as a basis +for skipping tests. + +.. function:: skipIfDBFeature(feature_name_string) + +Skip the decorated test if the named database feature is supported. + +For example, the following test will not be executed if the database +supports transactions (e.g., it would *not* run under PostgreSQL, but +it would under MySQL with MyISAM tables):: + + class MyTests(TestCase): + @skipIfDBFeature('supports_transactions') + def test_transaction_behavior(self): + # ... conditional test code + +.. function:: skipUnlessDBFeature(feature_name_string) + +Skip the decorated test if the named database feature is *not* +supported. + +For example, the following test will only be executed if the database +supports transactions (e.g., it would run under PostgreSQL, but *not* +under MySQL with MyISAM tables):: + + class MyTests(TestCase): + @skipUnlessDBFeature('supports_transactions') + def test_transaction_behavior(self): + # ... conditional test code -- cgit v1.3