SlideShare a Scribd company logo
1 of 145
Nasal
django’s
     Passage    by @ErikRose



           Scale up your project.
           Streamline your tests.
OK!   1729   tests, 0 failures, 0 errors, 13 skips in 436.4s
Django Testing Pain Points
Django Testing Pain Points
  • Crowded
Django Testing Pain Points
  • Crowded
  • Slow
Django Testing Pain Points
  • Crowded
  • Slow
  • Overbroad
Django Testing Pain Points
  • Crowded
                INSTALLED_APPS = [...
  • Slow           'django.contrib.admin',
                   'django.contrib.admindocs',
                   'django.contrib.auth',
  • Overbroad      'django.contrib.contenttypes',
                   'django.contrib.humanize',
                   'django.contrib.markup',
                   'django.contrib.messages',
                   'django.contrib.redirects',
                   'django.contrib.sessions',
                   'django.contrib.sitemaps',
                   'django.contrib.sites',
                   'django.contrib.staticfiles',
                   'django_crypto',
                   'django_extensions', ...]
Django Testing Pain Points
  • Crowded
  • Slow
  • Overbroad
  • Rough
Django Testing Pain Points
  • Crowded
  • Slow
  • Overbroad
  • Rough
  • Extensible but not scalably so
Installation
Installation
pip install django-nose
Installation
pip install django-nose




INSTALLED_APPS = (
    ...
    'django_nose',
    ...
)

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
Installation
pip install django-nose




INSTALLED_APPS = (
    ...
    'django_nose',
    ...
)

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'




./manage.py test ...
brevity
goodbye, boilerplate
Discovery
Discovery
project/frob/tests/__init__.py:
   from   project.frob.tests.test_forms import *
   from   project.frob.tests.test_models import *
   from   project.frob.tests.test_views import *
   from   project.frob.tests.test_tarts import *
   from   project.frob.tests.test_urls import *
   from   project.frob.tests.test_zoos import *
Discovery
Discovery
Discovery
(?:^|[b_./-])[Tt]est
Discovery
        (?:^|[b_./-])[Tt]est


test_something         test_things.py


testSomething          thing_tests.py
Discovery
        (?:^|[b_./-])[Tt]est


test_something         test_things.py


testSomething          thing_tests.py


@nottest               @istest
def testimonial()      def check_boogie()
Discovery
        (?:^|[b_./-])[Tt]est


test_something         test_things.py


testSomething          thing_tests.py


@nottest               @istest
def testimonial()      def check_boogie()


      class SomeCase(TestCase)
Discovery
Discovery
• No more accidental shadowing
Discovery
• No more accidental shadowing
• No more…
 SocialCampaignAccountComputationTestCase

 SocialCampaignsItemUserUtilsTestCase
 SelfServiceCandidateRequestConfirmationTests
Discovery
• No more accidental shadowing
• No more…
 SocialCampaignAccountComputationTestCase

 SocialCampaignsItemUserUtilsTestCase
 SelfServiceCandidateRequestConfirmationTests

• No more forgetting to import
Discovery
INSTALLED_APPS = [...
   'django.contrib.admin',
   'django.contrib.admindocs',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.humanize',
   'django.contrib.markup',
   'django.contrib.messages',
   'django.contrib.redirects',
   'django.contrib.sessions',
   'django.contrib.sitemaps',
   'django.contrib.sites',
   'django.contrib.staticfiles',
   'django_crypto',
   'django_extensions', ...]
Discovery
Discovery
   some_app/tests/__init__.py
                 /test_models.py
                 /test_views.py
                 /test_frobbers.py

another_app/tests/__init__.py
                 /test_forms.py
                 /test_views.py
                 /sample_data.dat
Discovery
   some_app/tests/__init__.py
                 /test_models.py
                 /test_views.py
                 /test_frobbers.py

another_app/tests/__init__.py
                 /test_forms.py
                 /test_views.py
                 /sample_data.dat


   frob_app/tests/__init__.py
                 /model_tests.py
                 /view_tests.py
Discovery
./manage.py test
 myapp.CacheUtilsTestCase.test_incr
Discovery
./manage.py test
 myapp.CacheUtilsTestCase.test_incr



./manage.py test
 myapp.tests.test_cache:CacheUtilsTestCase.test_incr
Discovery
./manage.py test
 myapp.CacheUtilsTestCase.test_incr



./manage.py test
 myapp.tests.test_cache:CacheUtilsTestCase.test_incr

./manage.py test
 myapp.tests.test_cache
power tools
   hello, toys
Functions as Tests
Functions as Tests
# Look, ma: no class!
def test_height_initted():
    """We should be able to get a height
    even on no-tty Terminals."""
    t = Terminal(stream=StringIO())
    eq_(type(t.height), int)
Functions as Tests
# Look, ma: no class!
def test_height_initted():
    """We should be able to get a height
    even on no-tty Terminals."""
    t = Terminal(stream=StringIO())
    eq_(type(t.height), int)
Functions as Tests
# Look, ma: no class!
def test_height_initted():
    """We should be able to get a height
    even on no-tty Terminals."""
    t = Terminal(stream=StringIO())
    eq_(type(t.height), int)



@with_setup(setup_func, teardown_func)
def test_woobie():
    ...
Test Generators
data = [('thing1', 'result1'),
        ('Steven', 'Steve'),
        ...]
Test Generators
data = [('thing1', 'result1'),
        ('Steven', 'Steve'),
        ...]

def test_munge():
    """
    Test munging independently on
    several pieces of data.
    """
    for t, r in data:
        yield check_datum, t, r

def check_datum(t, r):
    assert munge(t) == r
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
./manage.py test -a '!selenium'
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
./manage.py test -a '!selenium'


@attr(speed='slow')
class MyTestCase:
    def test_long_integration(self):
        ...
    def test_end_to_end_something(self):
        ...
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
./manage.py test -a '!selenium'


@attr(speed='slow')
class MyTestCase:
    def test_long_integration(self):
        ...
    def test_end_to_end_something(self):
        ...
./manage.py test speed=slow
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
./manage.py test -a '!selenium'


@attr(speed='slow')
class MyTestCase:
    def test_long_integration(self):
        ...
    def test_end_to_end_something(self):
        ...
./manage.py test speed=slow
./manage.py test -a selenium,speed=slow    # and
Test Attributes
from nose.plugins.attrib import attr

@attr('selenium')
def test_click_around():
    ...
./manage.py test -a selenium
./manage.py test -a '!selenium'


@attr(speed='slow')
class MyTestCase:
    def test_long_integration(self):
        ...
    def test_end_to_end_something(self):
        ...
./manage.py test speed=slow
./manage.py test -a selenium,speed=slow      # and
./manage.py test -a selenium -a speed=slow   # or
XML Output
XML Output
./manage.py test --with-xunit
XML Output
./manage.py test --with-xunit --xunit-file=funky.xml
XML Output
./manage.py test --with-xunit --xunit-file=funky.xml


<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="nosetests" tests="1" errors="1"
           failures="0" skip="0">
    <testcase classname="path_to_test_suite.TestSomething"
               name="test_it" time="0">
        <error type="exceptions.ValueError"
                 message="oops, too many gerbils">
             Traceback (most recent call last):
             ...
             ValueError: oops, too many gerbils
        </error>
    </testcase>
</testsuite>
Word of Warning

./manage.py test
Word of Warning

./manage.py test   -s
Word of Warning

./manage.py test          -s
alias t='./manage.py test --with-progressive -s'
More Goodies
More Goodies
• Custom error classes
More Goodies
• Custom error classes
• Extensibility
More Goodies
• Custom error classes
• Extensibility
• Plugins
More Goodies
• Custom error classes
• Extensibility
• Plugins
• Parallelization
speed
speed
django-nose is magic. I lied.
support.mozilla.org
django-nose Speed
   support.mozilla.org
django-nose Speed
      support.mozilla.org


• 1200 tests
django-nose Speed
      support.mozilla.org


• 1200 tests
• 20 minutes to test on Jenkins
django-nose Speed
      support.mozilla.org


• 1200 tests
• 20 minutes to test on Jenkins
• 5 minutes locally
django-nose Speed
django-nose Speed



     TESTING!
django-nose Speed

• Swordfighting
• Switching contexts
django-nose Speed

• Swordfighting
• Switching contexts
• Not running the tests
django-nose Speed

• Swordfighting
• Switching contexts
• Not running the tests
• Breaking the build
% time ./manage.py test
Creating test database for alias 'default'...
...
blah blah blah
./manage.py test 50.75s user 6.01s system 30% cpu 302.594 total
FastFixtureTestCase
FastFixtureTestCase
[
    {
         "pk": 1,
         "model": "forums.forum",
         "fields": {
             "name": "Test forum",
             "slug": "test-forum",
             "last_post": 25
         }
    },
    {
         "pk": 2,
         "model": "forums.forum",
         "fields": {
             "name": "Another Forum",
             "slug": "another-forum"
         }
    },
    {
         "pk": 3,
         "model": "forums.forum",
         "fields": {
             "name": "Restricted Forum",
             "slug": "restricted-forum"
         }
    },
    {
         "pk": 4,
         "model": "forums.forum",
         "fields": {
             "name": "Visible Restricted Forum",
             "slug": "visible",
"created": "2010-05-04 13:08:28",



    FastFixtureTestCase
             "thread": 5,
             "author": 47963
         }
    },
    {
         "pk": 26,
         "model": "forums.post",
         "fields": {
             "content": "test",
             "updated": "2010-05-02 16:08:28",
             "created": "2010-05-02 16:08:28",
             "thread": 4,
             "author": 118533
         }
    },
    {
         "pk": 25,
         "model": "forums.post",
         "fields": {
             "content": "test",
             "updated": "2010-05-21 16:08:28",
             "created": "2010-05-21 16:08:28",
             "thread": 4,
             "author": 118533
         }
    },
    {
         "pk": 1,
         "model": "forums.post",
         "fields": {
             "content": "This is a test post!",
             "updated": "2010-04-28 16:06:03",
             "created": "2010-04-28 16:05:13",
             "thread": 1,
             "author": 118533
         }
    }
]
FastFixtureTestCase


class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']
FastFixtureTestCase


SET GLOBAL general_log = 'ON';
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class ForumTestCase(TestCase):
    fixtures = ['users.json',
                'posts.json',
                'forums_permissions.json']

    def test_new_post_updates_thread(self):
        ...

    def test_new_post_updates_forum(self):
        ...

    def test_replies_count(self):
        ...

    def test_sticky_threads_first(self):
        ...
FastFixtureTestCase
class FastFixtureTestCase(TestCase):
    """Loads fixtures just once per class."""

   def setup_class(self):
       load_fixtures()
       commit()

   def run_test(self):
       run_the_test()
       rollback()

   def teardown_class(self):
       remove_fixtures()
       commit()
FastFixtureTestCase
class FastFixtureTestCase(TestCase):
    """Loads fixtures just once per class."""

   def setup_class(self):
       load_fixtures()
       commit()

   def run_test(self):
       run_the_test()
       rollback()

   def teardown_class(self):
       remove_fixtures()
       commit()
FastFixtureTestCase
class FastFixtureTestCase(TestCase):
    """Loads fixtures just once per class."""

   def setup_class(self):
       load_fixtures()
       commit()

   def run_test(self):
       run_the_test()
       rollback()

   def teardown_class(self):
       remove_fixtures()
       commit()
FastFixtureTestCase
class FastFixtureTestCase(TestCase):
    """Loads fixtures just once per class."""

   def setup_class(self):
       load_fixtures()
       commit()

   def run_test(self):
       run_the_test()
       rollback()

   def teardown_class(self):
       remove_fixtures()
       commit()
FastFixtureTestCase
class FastFixtureTestCase(TestCase):
    """Loads fixtures just once per class."""
                               """A copy of Django 1.3.0's stock loaddata.py, adapted so that, instead of
                               loading any data, it returns the tables referenced by a set of fixtures so we


   def setup_class(self):      can truncate them (and no others) quickly after we're finished with them."""

                               import os
                               import gzip


       load_fixtures()
                               import zipfile

                               from    django.conf import settings
                               from    django.core import serializers



       commit()
                               from    django.db import router, DEFAULT_DB_ALIAS
                               from    django.db.models import get_apps
                               from    django.utils.itercompat import product

                               try:
                                   import bz2
                                   has_bz2 = True
                               except ImportError:
                                   has_bz2 = False


   def run_test(self):         def tables_used_by_fixtures(fixture_labels, using=DEFAULT_DB_ALIAS):
                                   """Act like Django's stock loaddata command, but, instead of loading data,
                                   return an iterable of the names of the tables into which data would be


       run_the_test()              loaded."""
                                   # Keep a count of the installed objects and fixtures
                                   fixture_count = 0
                                   loaded_object_count = 0


       rollback()
                                   fixture_object_count = 0
                                   tables = set()

                                   class SingleZipReader(zipfile.ZipFile):
                                       def __init__(self, *args, **kwargs):
                                           zipfile.ZipFile.__init__(self, *args, **kwargs)
                                           if settings.DEBUG:
                                               assert len(self.namelist()) == 1, "Zip-compressed fixtures must
                               contain only one file."


   def teardown_class(self):           def read(self):
                                           return zipfile.ZipFile.read(self, self.namelist()[0])

                                      compression_types = {


       remove_fixtures()
                                          None:   file,
                                          'gz':   gzip.GzipFile,
                                          'zip': SingleZipReader
                                      }



       commit()
                                      if has_bz2:
                                          compression_types['bz2'] = bz2.BZ2File

                                      app_module_paths = []
                                      for app in get_apps():
                                          if hasattr(app, '__path__'):
                                              # It's a 'models/' subpackage
                                              for path in app.__path__:
                                                  app_module_paths.append(path)
                                          else:
                                              # It's a models.py module
                                              app_module_paths.append(app.__file__)

                                   app_fixtures = [os.path.join(os.path.dirname(path), 'fixtures') for path in
                               app_module_paths]
                                   for fixture_label in fixture_labels:
                                       parts = fixture_label.split('.')

                                          if len(parts) > 1 and parts[-1] in compression_types:
FastFixtureTestCase
FastFixtureTestCase
                                  Queries



   Stock Fixtures




Per-Class Fixtures



                     0   10,000   20,000    30,000   40,000
FastFixtureTestCase
                                     Queries



   Stock Fixtures                 37,583




Per-Class Fixtures



                     0   10,000     20,000     30,000   40,000
FastFixtureTestCase
                                             Queries



   Stock Fixtures                         37,583




Per-Class Fixtures       4,116



                     0           10,000     20,000     30,000   40,000
FastFixtureTestCase
FastFixtureTestCase
                               Seconds



   Stock Fixtures




Per-Class Fixtures



                     0   100   200       300   400
FastFixtureTestCase
                                     Seconds



   Stock Fixtures              302




Per-Class Fixtures



                     0   100         200       300   400
FastFixtureTestCase
                                          Seconds



   Stock Fixtures                   302




Per-Class Fixtures       97



                     0        100         200       300   400
Fixture Bundling
class ThreadsTemplateTests(FastFixtureTestCase):
    fixtures = ['users.json', 'posts.json',
                'forums_permissions.json']
    ...

class ForumsTemplateTests(FastFixtureTestCase):
    fixtures = ['users.json', 'posts.json',
                'forums_permissions.json']
    ...

class NewThreadTests(FastFixtureTestCase):
    fixtures = ['users.json', 'posts.json',
                'forums_permissions.json']
    ...
Fixture Bundling
              fixtures
 TestCase1→ A B C
 TestCase2→ A B C D
 TestCase3→ A B C
 TestCase4→      B      D
 TestCase5→ A B C D
 TestCase6→ A B C
Fixture Bundling
              fixtures
 TestCase1→ A B C
 TestCase3→ A B C
 TestCase6→ A B C
 TestCase2→ A B C D
 TestCase5→ A B C D
 TestCase4→      B      D
Fixture Bundling
              fixtures
                        ☚ load ABC
 TestCase1→ A B C
 TestCase3→ A B C
 TestCase6→ A B C
 TestCase2→ A B C D
 TestCase5→ A B C D
 TestCase4→      B      D
Fixture Bundling
              fixtures
                        ☚ load ABC
 TestCase1→ A B C
 TestCase3→ A B C
 TestCase6→ A B C
                            ☚ load ABCD
 TestCase2→ A B C D
 TestCase5→ A B C D
 TestCase4→      B      D
Fixture Bundling
              fixtures
                        ☚ load ABC
 TestCase1→ A B C
 TestCase3→ A B C
 TestCase6→ A B C
                            ☚ load ABCD
 TestCase2→ A B C D
 TestCase5→ A B C D
                            ☚ load BD
 TestCase4→      B      D
Fixture Bundling
Fixture Bundling
                              Seconds



Per-Class Fixtures




 Fixture Bundling



                     0   25   50        75   100
Fixture Bundling
                              Seconds



Per-Class Fixtures            97




 Fixture Bundling



                     0   25   50        75   100
Fixture Bundling
                                   Seconds



Per-Class Fixtures                 97




 Fixture Bundling             74



                     0   25        50        75   100
Fixture Bundling
                     --with-fixture-bundling
                                     Seconds



Per-Class Fixtures                   97




 Fixture Bundling               74



                     0     25        50        75   100
Database Reuse
Database Reuse
Database Reuse
                            Seconds



Fixture Bundling




      DB Reuse



                   0   20   40        60   80
Database Reuse
                             Seconds



Fixture Bundling            74




      DB Reuse



                   0   20        40    60   80
Database Reuse
                                  Seconds



Fixture Bundling                 74




      DB Reuse              62



                   0   20             40    60   80
Database Reuse
               REUSE_DB=1 ./manage.py test
                                  Seconds



Fixture Bundling                 74




      DB Reuse              62



                   0   20             40    60   80
Speed Wrap-up
Speed Wrap-up
                               Seconds

    Stock Django


Per-Class Fixtures


 Fixture Bundling


       DB Reuse


                     0   100   200       300   400
Speed Wrap-up
                                     Seconds

    Stock Django               302


Per-Class Fixtures


 Fixture Bundling


       DB Reuse


                     0   100         200       300   400
Speed Wrap-up
                                          Seconds

    Stock Django                    302


Per-Class Fixtures       97


 Fixture Bundling


       DB Reuse


                     0        100         200       300   400
Speed Wrap-up
                                           Seconds

    Stock Django                     302


Per-Class Fixtures        97


 Fixture Bundling        74


       DB Reuse


                     0         100         200       300   400
Speed Wrap-up
                                               Seconds

    Stock Django                         302


Per-Class Fixtures            97


 Fixture Bundling        74


       DB Reuse          62

                     0             100         200       300   400
TransactionTestCases
TransactionTestCases
Hygienic TransactionTestCases
Hygienic TransactionTestCases


  class MyNiceTestCase(TransactionTestCase):
      cleans_up_after_itself = True

     def _fixture_setup(self):
         ... # Don't flush.
nose-progressive
    a decent testing ui
not nose-progressive
not nose-progressive
nose-progressive
nose-progressive
nose-progressive


   pip install nose-progressive

./manage.py test --with-progressive
Ponies to Come
• Test models
• Coverage
• Profiling
• Better 1.4 layout support
 ./manage.py test
 ! ! --traverse-namespace myproj.myapp
ErikRose
erik@votizen.com
ErikRose
erik@votizen.com


 django-nose
    sprint
  tomorrow,
  9am-2pm
?
            ErikRose
  erik@votizen.com


     django-nose
        sprint
      tomorrow,
        9am-2pm


Image Credits:
• Anatomical diagram by Patrick J. Lynch,
    medical illustrator, edited by user DiebBuche:
    http://commons.wikimedia.org/wiki/File:Mouth_anatomy-de.svg
• “Testing!” comic adapted from http://xkcd.com/303/
• Memory hierarchy diagram: http://i.imgur.com/X1Hi1.gif

More Related Content

What's hot

Javascript Testing with Jasmine 101
Javascript Testing with Jasmine 101Javascript Testing with Jasmine 101
Javascript Testing with Jasmine 101Roy Yu
 
From typing the test to testing the type
From typing the test to testing the typeFrom typing the test to testing the type
From typing the test to testing the typeWim Godden
 
Mozilla Web QA - Evolution of our Python WebDriver framework
Mozilla Web QA - Evolution of our Python WebDriver frameworkMozilla Web QA - Evolution of our Python WebDriver framework
Mozilla Web QA - Evolution of our Python WebDriver frameworkdavehunt82
 
Testing Javascript with Jasmine
Testing Javascript with JasmineTesting Javascript with Jasmine
Testing Javascript with JasmineTim Tyrrell
 
Isolated development in python
Isolated development in pythonIsolated development in python
Isolated development in pythonAndrés J. Díaz
 
Test Infected Presentation
Test Infected PresentationTest Infected Presentation
Test Infected Presentationwillmation
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript TestingThomas Fuchs
 
Testing JavaScript Applications
Testing JavaScript ApplicationsTesting JavaScript Applications
Testing JavaScript ApplicationsThe Rolling Scopes
 
JavaScript TDD with Jasmine and Karma
JavaScript TDD with Jasmine and KarmaJavaScript TDD with Jasmine and Karma
JavaScript TDD with Jasmine and KarmaChristopher Bartling
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2Yi-Huan Chan
 
Testing JS with Jasmine
Testing JS with JasmineTesting JS with Jasmine
Testing JS with JasmineEvgeny Gurin
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaExoLeaders.com
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitMichelangelo van Dam
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnitJace Ju
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in LaravelAhmed Yahia
 
Advanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingAdvanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingLars Thorup
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsFITC
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testingjeresig
 

What's hot (20)

Javascript Testing with Jasmine 101
Javascript Testing with Jasmine 101Javascript Testing with Jasmine 101
Javascript Testing with Jasmine 101
 
From typing the test to testing the type
From typing the test to testing the typeFrom typing the test to testing the type
From typing the test to testing the type
 
Mozilla Web QA - Evolution of our Python WebDriver framework
Mozilla Web QA - Evolution of our Python WebDriver frameworkMozilla Web QA - Evolution of our Python WebDriver framework
Mozilla Web QA - Evolution of our Python WebDriver framework
 
Testing Javascript with Jasmine
Testing Javascript with JasmineTesting Javascript with Jasmine
Testing Javascript with Jasmine
 
Isolated development in python
Isolated development in pythonIsolated development in python
Isolated development in python
 
Test Infected Presentation
Test Infected PresentationTest Infected Presentation
Test Infected Presentation
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript Testing
 
Testing JavaScript Applications
Testing JavaScript ApplicationsTesting JavaScript Applications
Testing JavaScript Applications
 
JavaScript TDD with Jasmine and Karma
JavaScript TDD with Jasmine and KarmaJavaScript TDD with Jasmine and Karma
JavaScript TDD with Jasmine and Karma
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2
 
Testing JS with Jasmine
Testing JS with JasmineTesting JS with Jasmine
Testing JS with Jasmine
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with Karma
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnit
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in Laravel
 
Advanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingAdvanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit Testing
 
Getting Testy With Perl6
Getting Testy With Perl6Getting Testy With Perl6
Getting Testy With Perl6
 
Django
Django Django
Django
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS Applications
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 

Viewers also liked

Bank Account Of Life
Bank Account Of LifeBank Account Of Life
Bank Account Of LifeNafass
 
Building Quality Experiences for Users in Any Language
Building Quality Experiences for Users in Any LanguageBuilding Quality Experiences for Users in Any Language
Building Quality Experiences for Users in Any LanguageJohn Collins
 
Linguistic Potluck: Crowdsourcing localization with Rails
Linguistic Potluck: Crowdsourcing localization with RailsLinguistic Potluck: Crowdsourcing localization with Rails
Linguistic Potluck: Crowdsourcing localization with RailsHeatherRivers
 
mobile development platforms
mobile development platformsmobile development platforms
mobile development platformsguestfa9375
 
Open Software Platforms for Mobile Digital Broadcasting
Open Software Platforms for Mobile Digital BroadcastingOpen Software Platforms for Mobile Digital Broadcasting
Open Software Platforms for Mobile Digital BroadcastingFrancois Lefebvre
 
2008 Fourth Quarter Real Estate Commentary
2008 Fourth Quarter Real Estate Commentary2008 Fourth Quarter Real Estate Commentary
2008 Fourth Quarter Real Estate Commentaryalghanim
 
My trans kit checklist gw1 ds1_gw3
My trans kit checklist gw1 ds1_gw3My trans kit checklist gw1 ds1_gw3
My trans kit checklist gw1 ds1_gw3David Sommer
 
Strategies for Friendly English and Successful Localization
Strategies for Friendly English and Successful LocalizationStrategies for Friendly English and Successful Localization
Strategies for Friendly English and Successful LocalizationJohn Collins
 
My Valentine Gift - YOU Decide
My Valentine Gift - YOU DecideMy Valentine Gift - YOU Decide
My Valentine Gift - YOU DecideSizzlynRose
 
Stc 2014 unraveling the mysteries of localization kits
Stc 2014 unraveling the mysteries of localization kitsStc 2014 unraveling the mysteries of localization kits
Stc 2014 unraveling the mysteries of localization kitsDavid Sommer
 
Sample of instructions
Sample of instructionsSample of instructions
Sample of instructionsDavid Sommer
 
Designing for Multiple Mobile Platforms
Designing for Multiple Mobile PlatformsDesigning for Multiple Mobile Platforms
Designing for Multiple Mobile PlatformsRobert Douglas
 
Sample email submission
Sample email submissionSample email submission
Sample email submissionDavid Sommer
 
Putting Out Fires with Content Strategy (InfoDevDC meetup)
Putting Out Fires with Content Strategy (InfoDevDC meetup)Putting Out Fires with Content Strategy (InfoDevDC meetup)
Putting Out Fires with Content Strategy (InfoDevDC meetup)John Collins
 
Putting Out Fires with Content Strategy (STC Academic SIG)
Putting Out Fires with Content Strategy (STC Academic SIG)Putting Out Fires with Content Strategy (STC Academic SIG)
Putting Out Fires with Content Strategy (STC Academic SIG)John Collins
 
Internationalization in Rails 2.2
Internationalization in Rails 2.2Internationalization in Rails 2.2
Internationalization in Rails 2.2Nicolas Jacobeus
 
Pycon 2012 What Python can learn from Java
Pycon 2012 What Python can learn from JavaPycon 2012 What Python can learn from Java
Pycon 2012 What Python can learn from Javajbellis
 
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)John Collins
 

Viewers also liked (20)

Bank Account Of Life
Bank Account Of LifeBank Account Of Life
Bank Account Of Life
 
Glossary
GlossaryGlossary
Glossary
 
Building Quality Experiences for Users in Any Language
Building Quality Experiences for Users in Any LanguageBuilding Quality Experiences for Users in Any Language
Building Quality Experiences for Users in Any Language
 
Linguistic Potluck: Crowdsourcing localization with Rails
Linguistic Potluck: Crowdsourcing localization with RailsLinguistic Potluck: Crowdsourcing localization with Rails
Linguistic Potluck: Crowdsourcing localization with Rails
 
mobile development platforms
mobile development platformsmobile development platforms
mobile development platforms
 
Open Software Platforms for Mobile Digital Broadcasting
Open Software Platforms for Mobile Digital BroadcastingOpen Software Platforms for Mobile Digital Broadcasting
Open Software Platforms for Mobile Digital Broadcasting
 
2008 Fourth Quarter Real Estate Commentary
2008 Fourth Quarter Real Estate Commentary2008 Fourth Quarter Real Estate Commentary
2008 Fourth Quarter Real Estate Commentary
 
My trans kit checklist gw1 ds1_gw3
My trans kit checklist gw1 ds1_gw3My trans kit checklist gw1 ds1_gw3
My trans kit checklist gw1 ds1_gw3
 
Strategies for Friendly English and Successful Localization
Strategies for Friendly English and Successful LocalizationStrategies for Friendly English and Successful Localization
Strategies for Friendly English and Successful Localization
 
My Valentine Gift - YOU Decide
My Valentine Gift - YOU DecideMy Valentine Gift - YOU Decide
My Valentine Gift - YOU Decide
 
Stc 2014 unraveling the mysteries of localization kits
Stc 2014 unraveling the mysteries of localization kitsStc 2014 unraveling the mysteries of localization kits
Stc 2014 unraveling the mysteries of localization kits
 
Sample of instructions
Sample of instructionsSample of instructions
Sample of instructions
 
Designing for Multiple Mobile Platforms
Designing for Multiple Mobile PlatformsDesigning for Multiple Mobile Platforms
Designing for Multiple Mobile Platforms
 
Sample email submission
Sample email submissionSample email submission
Sample email submission
 
Putting Out Fires with Content Strategy (InfoDevDC meetup)
Putting Out Fires with Content Strategy (InfoDevDC meetup)Putting Out Fires with Content Strategy (InfoDevDC meetup)
Putting Out Fires with Content Strategy (InfoDevDC meetup)
 
Putting Out Fires with Content Strategy (STC Academic SIG)
Putting Out Fires with Content Strategy (STC Academic SIG)Putting Out Fires with Content Strategy (STC Academic SIG)
Putting Out Fires with Content Strategy (STC Academic SIG)
 
Internationalization in Rails 2.2
Internationalization in Rails 2.2Internationalization in Rails 2.2
Internationalization in Rails 2.2
 
Pycon 2012 What Python can learn from Java
Pycon 2012 What Python can learn from JavaPycon 2012 What Python can learn from Java
Pycon 2012 What Python can learn from Java
 
Shrunken Head
 Shrunken Head  Shrunken Head
Shrunken Head
 
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)
Strategies for Friendly English and Successful Localization (InfoDevWorld 2014)
 

Similar to Django’s nasal passage

Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slidesericholscher
 
Making the most of your Test Suite
Making the most of your Test SuiteMaking the most of your Test Suite
Making the most of your Test Suiteericholscher
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdfHans Jones
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everythingnoelrap
 
Continuous Integration Testing in Django
Continuous Integration Testing in DjangoContinuous Integration Testing in Django
Continuous Integration Testing in DjangoKevin Harvey
 
Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Holger Grosse-Plankermann
 
Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Holger Grosse-Plankermann
 
Testing for Pragmatic People
Testing for Pragmatic PeopleTesting for Pragmatic People
Testing for Pragmatic Peopledavismr
 
The Best (and Worst) of Django
The Best (and Worst) of DjangoThe Best (and Worst) of Django
The Best (and Worst) of DjangoJacob Kaplan-Moss
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With PythonSiddhi
 
Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Mark Niebergall
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytestHector Canto
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testingpleeps
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
Angular Intermediate
Angular IntermediateAngular Intermediate
Angular IntermediateLinkMe Srl
 

Similar to Django’s nasal passage (20)

Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slides
 
Making the most of your Test Suite
Making the most of your Test SuiteMaking the most of your Test Suite
Making the most of your Test Suite
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdf
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everything
 
Continuous Integration Testing in Django
Continuous Integration Testing in DjangoContinuous Integration Testing in Django
Continuous Integration Testing in Django
 
Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018
 
Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018
 
Testing for Pragmatic People
Testing for Pragmatic PeopleTesting for Pragmatic People
Testing for Pragmatic People
 
Django tricks (2)
Django tricks (2)Django tricks (2)
Django tricks (2)
 
The Best (and Worst) of Django
The Best (and Worst) of DjangoThe Best (and Worst) of Django
The Best (and Worst) of Django
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With Python
 
UPC Testing talk 2
UPC Testing talk 2UPC Testing talk 2
UPC Testing talk 2
 
Practical Celery
Practical CeleryPractical Celery
Practical Celery
 
Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytest
 
Scala test
Scala testScala test
Scala test
 
Scala test
Scala testScala test
Scala test
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Angular Intermediate
Angular IntermediateAngular Intermediate
Angular Intermediate
 

More from Erik Rose

Fathom Overview and Future, San Francisco 2018
Fathom Overview and Future, San Francisco 2018Fathom Overview and Future, San Francisco 2018
Fathom Overview and Future, San Francisco 2018Erik Rose
 
What happens when firefox crashes?
What happens when firefox crashes?What happens when firefox crashes?
What happens when firefox crashes?Erik Rose
 
Es part 2 pdf no build
Es part 2 pdf no buildEs part 2 pdf no build
Es part 2 pdf no buildErik Rose
 
Fluid, Fluent APIs
Fluid, Fluent APIsFluid, Fluent APIs
Fluid, Fluent APIsErik Rose
 
WebLion Hosting: Leveraging Laziness, Impatience, and Hubris
WebLion Hosting: Leveraging Laziness, Impatience, and HubrisWebLion Hosting: Leveraging Laziness, Impatience, and Hubris
WebLion Hosting: Leveraging Laziness, Impatience, and HubrisErik Rose
 
WebLion Hosting Lightning Talk
WebLion Hosting Lightning TalkWebLion Hosting Lightning Talk
WebLion Hosting Lightning TalkErik Rose
 
Protecting Plone from the Big, Bad Internet
Protecting Plone from the Big, Bad InternetProtecting Plone from the Big, Bad Internet
Protecting Plone from the Big, Bad InternetErik Rose
 

More from Erik Rose (9)

Fathom Overview and Future, San Francisco 2018
Fathom Overview and Future, San Francisco 2018Fathom Overview and Future, San Francisco 2018
Fathom Overview and Future, San Francisco 2018
 
What happens when firefox crashes?
What happens when firefox crashes?What happens when firefox crashes?
What happens when firefox crashes?
 
Poetic APIs
Poetic APIsPoetic APIs
Poetic APIs
 
Es part 2 pdf no build
Es part 2 pdf no buildEs part 2 pdf no build
Es part 2 pdf no build
 
Fluid, Fluent APIs
Fluid, Fluent APIsFluid, Fluent APIs
Fluid, Fluent APIs
 
Stackful
StackfulStackful
Stackful
 
WebLion Hosting: Leveraging Laziness, Impatience, and Hubris
WebLion Hosting: Leveraging Laziness, Impatience, and HubrisWebLion Hosting: Leveraging Laziness, Impatience, and Hubris
WebLion Hosting: Leveraging Laziness, Impatience, and Hubris
 
WebLion Hosting Lightning Talk
WebLion Hosting Lightning TalkWebLion Hosting Lightning Talk
WebLion Hosting Lightning Talk
 
Protecting Plone from the Big, Bad Internet
Protecting Plone from the Big, Bad InternetProtecting Plone from the Big, Bad Internet
Protecting Plone from the Big, Bad Internet
 

Recently uploaded

The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Strongerpanagenda
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsRavi Sanghani
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Scott Andery
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesThousandEyes
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Manual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditManual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditSkynet Technologies
 

Recently uploaded (20)

The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and Insights
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Manual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditManual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance Audit
 

Django’s nasal passage

  • 1. Nasal django’s Passage by @ErikRose Scale up your project. Streamline your tests.
  • 2.
  • 3.
  • 4. OK! 1729 tests, 0 failures, 0 errors, 13 skips in 436.4s
  • 6. Django Testing Pain Points • Crowded
  • 7. Django Testing Pain Points • Crowded • Slow
  • 8. Django Testing Pain Points • Crowded • Slow • Overbroad
  • 9. Django Testing Pain Points • Crowded INSTALLED_APPS = [... • Slow 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.auth', • Overbroad 'django.contrib.contenttypes', 'django.contrib.humanize', 'django.contrib.markup', 'django.contrib.messages', 'django.contrib.redirects', 'django.contrib.sessions', 'django.contrib.sitemaps', 'django.contrib.sites', 'django.contrib.staticfiles', 'django_crypto', 'django_extensions', ...]
  • 10. Django Testing Pain Points • Crowded • Slow • Overbroad • Rough
  • 11. Django Testing Pain Points • Crowded • Slow • Overbroad • Rough • Extensible but not scalably so
  • 14. Installation pip install django-nose INSTALLED_APPS = ( ... 'django_nose', ... ) TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
  • 15. Installation pip install django-nose INSTALLED_APPS = ( ... 'django_nose', ... ) TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' ./manage.py test ...
  • 18. Discovery project/frob/tests/__init__.py: from project.frob.tests.test_forms import * from project.frob.tests.test_models import * from project.frob.tests.test_views import * from project.frob.tests.test_tarts import * from project.frob.tests.test_urls import * from project.frob.tests.test_zoos import *
  • 22. Discovery (?:^|[b_./-])[Tt]est test_something test_things.py testSomething thing_tests.py
  • 23. Discovery (?:^|[b_./-])[Tt]est test_something test_things.py testSomething thing_tests.py @nottest @istest def testimonial() def check_boogie()
  • 24. Discovery (?:^|[b_./-])[Tt]est test_something test_things.py testSomething thing_tests.py @nottest @istest def testimonial() def check_boogie() class SomeCase(TestCase)
  • 26. Discovery • No more accidental shadowing
  • 27. Discovery • No more accidental shadowing • No more… SocialCampaignAccountComputationTestCase SocialCampaignsItemUserUtilsTestCase SelfServiceCandidateRequestConfirmationTests
  • 28. Discovery • No more accidental shadowing • No more… SocialCampaignAccountComputationTestCase SocialCampaignsItemUserUtilsTestCase SelfServiceCandidateRequestConfirmationTests • No more forgetting to import
  • 29. Discovery INSTALLED_APPS = [... 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.humanize', 'django.contrib.markup', 'django.contrib.messages', 'django.contrib.redirects', 'django.contrib.sessions', 'django.contrib.sitemaps', 'django.contrib.sites', 'django.contrib.staticfiles', 'django_crypto', 'django_extensions', ...]
  • 31. Discovery some_app/tests/__init__.py /test_models.py /test_views.py /test_frobbers.py another_app/tests/__init__.py /test_forms.py /test_views.py /sample_data.dat
  • 32. Discovery some_app/tests/__init__.py /test_models.py /test_views.py /test_frobbers.py another_app/tests/__init__.py /test_forms.py /test_views.py /sample_data.dat frob_app/tests/__init__.py /model_tests.py /view_tests.py
  • 34. Discovery ./manage.py test myapp.CacheUtilsTestCase.test_incr ./manage.py test myapp.tests.test_cache:CacheUtilsTestCase.test_incr
  • 35. Discovery ./manage.py test myapp.CacheUtilsTestCase.test_incr ./manage.py test myapp.tests.test_cache:CacheUtilsTestCase.test_incr ./manage.py test myapp.tests.test_cache
  • 36. power tools hello, toys
  • 38. Functions as Tests # Look, ma: no class! def test_height_initted(): """We should be able to get a height even on no-tty Terminals.""" t = Terminal(stream=StringIO()) eq_(type(t.height), int)
  • 39. Functions as Tests # Look, ma: no class! def test_height_initted(): """We should be able to get a height even on no-tty Terminals.""" t = Terminal(stream=StringIO()) eq_(type(t.height), int)
  • 40. Functions as Tests # Look, ma: no class! def test_height_initted(): """We should be able to get a height even on no-tty Terminals.""" t = Terminal(stream=StringIO()) eq_(type(t.height), int) @with_setup(setup_func, teardown_func) def test_woobie(): ...
  • 41. Test Generators data = [('thing1', 'result1'), ('Steven', 'Steve'), ...]
  • 42. Test Generators data = [('thing1', 'result1'), ('Steven', 'Steve'), ...] def test_munge(): """ Test munging independently on several pieces of data. """ for t, r in data: yield check_datum, t, r def check_datum(t, r): assert munge(t) == r
  • 43. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ...
  • 44. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium
  • 45. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium ./manage.py test -a '!selenium'
  • 46. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium ./manage.py test -a '!selenium' @attr(speed='slow') class MyTestCase: def test_long_integration(self): ... def test_end_to_end_something(self): ...
  • 47. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium ./manage.py test -a '!selenium' @attr(speed='slow') class MyTestCase: def test_long_integration(self): ... def test_end_to_end_something(self): ... ./manage.py test speed=slow
  • 48. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium ./manage.py test -a '!selenium' @attr(speed='slow') class MyTestCase: def test_long_integration(self): ... def test_end_to_end_something(self): ... ./manage.py test speed=slow ./manage.py test -a selenium,speed=slow # and
  • 49. Test Attributes from nose.plugins.attrib import attr @attr('selenium') def test_click_around(): ... ./manage.py test -a selenium ./manage.py test -a '!selenium' @attr(speed='slow') class MyTestCase: def test_long_integration(self): ... def test_end_to_end_something(self): ... ./manage.py test speed=slow ./manage.py test -a selenium,speed=slow # and ./manage.py test -a selenium -a speed=slow # or
  • 52. XML Output ./manage.py test --with-xunit --xunit-file=funky.xml
  • 53. XML Output ./manage.py test --with-xunit --xunit-file=funky.xml <?xml version="1.0" encoding="UTF-8"?> <testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0"> <testcase classname="path_to_test_suite.TestSomething" name="test_it" time="0"> <error type="exceptions.ValueError" message="oops, too many gerbils"> Traceback (most recent call last): ... ValueError: oops, too many gerbils </error> </testcase> </testsuite>
  • 56. Word of Warning ./manage.py test -s alias t='./manage.py test --with-progressive -s'
  • 58. More Goodies • Custom error classes
  • 59. More Goodies • Custom error classes • Extensibility
  • 60. More Goodies • Custom error classes • Extensibility • Plugins
  • 61. More Goodies • Custom error classes • Extensibility • Plugins • Parallelization
  • 62. speed
  • 65. django-nose Speed support.mozilla.org
  • 66. django-nose Speed support.mozilla.org • 1200 tests
  • 67. django-nose Speed support.mozilla.org • 1200 tests • 20 minutes to test on Jenkins
  • 68. django-nose Speed support.mozilla.org • 1200 tests • 20 minutes to test on Jenkins • 5 minutes locally
  • 70. django-nose Speed TESTING!
  • 72. django-nose Speed • Swordfighting • Switching contexts • Not running the tests
  • 73. django-nose Speed • Swordfighting • Switching contexts • Not running the tests • Breaking the build
  • 74.
  • 75.
  • 76. % time ./manage.py test Creating test database for alias 'default'... ... blah blah blah ./manage.py test 50.75s user 6.01s system 30% cpu 302.594 total
  • 78. FastFixtureTestCase [ { "pk": 1, "model": "forums.forum", "fields": { "name": "Test forum", "slug": "test-forum", "last_post": 25 } }, { "pk": 2, "model": "forums.forum", "fields": { "name": "Another Forum", "slug": "another-forum" } }, { "pk": 3, "model": "forums.forum", "fields": { "name": "Restricted Forum", "slug": "restricted-forum" } }, { "pk": 4, "model": "forums.forum", "fields": { "name": "Visible Restricted Forum", "slug": "visible",
  • 79. "created": "2010-05-04 13:08:28", FastFixtureTestCase "thread": 5, "author": 47963 } }, { "pk": 26, "model": "forums.post", "fields": { "content": "test", "updated": "2010-05-02 16:08:28", "created": "2010-05-02 16:08:28", "thread": 4, "author": 118533 } }, { "pk": 25, "model": "forums.post", "fields": { "content": "test", "updated": "2010-05-21 16:08:28", "created": "2010-05-21 16:08:28", "thread": 4, "author": 118533 } }, { "pk": 1, "model": "forums.post", "fields": { "content": "This is a test post!", "updated": "2010-04-28 16:06:03", "created": "2010-04-28 16:05:13", "thread": 1, "author": 118533 } } ]
  • 80. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json']
  • 82. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json']
  • 83. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 84. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 85. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 86. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 87. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 88. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 89. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 90. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 91. FastFixtureTestCase class ForumTestCase(TestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] def test_new_post_updates_thread(self): ... def test_new_post_updates_forum(self): ... def test_replies_count(self): ... def test_sticky_threads_first(self): ...
  • 92. FastFixtureTestCase class FastFixtureTestCase(TestCase): """Loads fixtures just once per class.""" def setup_class(self): load_fixtures() commit() def run_test(self): run_the_test() rollback() def teardown_class(self): remove_fixtures() commit()
  • 93. FastFixtureTestCase class FastFixtureTestCase(TestCase): """Loads fixtures just once per class.""" def setup_class(self): load_fixtures() commit() def run_test(self): run_the_test() rollback() def teardown_class(self): remove_fixtures() commit()
  • 94. FastFixtureTestCase class FastFixtureTestCase(TestCase): """Loads fixtures just once per class.""" def setup_class(self): load_fixtures() commit() def run_test(self): run_the_test() rollback() def teardown_class(self): remove_fixtures() commit()
  • 95. FastFixtureTestCase class FastFixtureTestCase(TestCase): """Loads fixtures just once per class.""" def setup_class(self): load_fixtures() commit() def run_test(self): run_the_test() rollback() def teardown_class(self): remove_fixtures() commit()
  • 96. FastFixtureTestCase class FastFixtureTestCase(TestCase): """Loads fixtures just once per class.""" """A copy of Django 1.3.0's stock loaddata.py, adapted so that, instead of loading any data, it returns the tables referenced by a set of fixtures so we def setup_class(self): can truncate them (and no others) quickly after we're finished with them.""" import os import gzip load_fixtures() import zipfile from django.conf import settings from django.core import serializers commit() from django.db import router, DEFAULT_DB_ALIAS from django.db.models import get_apps from django.utils.itercompat import product try: import bz2 has_bz2 = True except ImportError: has_bz2 = False def run_test(self): def tables_used_by_fixtures(fixture_labels, using=DEFAULT_DB_ALIAS): """Act like Django's stock loaddata command, but, instead of loading data, return an iterable of the names of the tables into which data would be run_the_test() loaded.""" # Keep a count of the installed objects and fixtures fixture_count = 0 loaded_object_count = 0 rollback() fixture_object_count = 0 tables = set() class SingleZipReader(zipfile.ZipFile): def __init__(self, *args, **kwargs): zipfile.ZipFile.__init__(self, *args, **kwargs) if settings.DEBUG: assert len(self.namelist()) == 1, "Zip-compressed fixtures must contain only one file." def teardown_class(self): def read(self): return zipfile.ZipFile.read(self, self.namelist()[0]) compression_types = { remove_fixtures() None: file, 'gz': gzip.GzipFile, 'zip': SingleZipReader } commit() if has_bz2: compression_types['bz2'] = bz2.BZ2File app_module_paths = [] for app in get_apps(): if hasattr(app, '__path__'): # It's a 'models/' subpackage for path in app.__path__: app_module_paths.append(path) else: # It's a models.py module app_module_paths.append(app.__file__) app_fixtures = [os.path.join(os.path.dirname(path), 'fixtures') for path in app_module_paths] for fixture_label in fixture_labels: parts = fixture_label.split('.') if len(parts) > 1 and parts[-1] in compression_types:
  • 98. FastFixtureTestCase Queries Stock Fixtures Per-Class Fixtures 0 10,000 20,000 30,000 40,000
  • 99. FastFixtureTestCase Queries Stock Fixtures 37,583 Per-Class Fixtures 0 10,000 20,000 30,000 40,000
  • 100. FastFixtureTestCase Queries Stock Fixtures 37,583 Per-Class Fixtures 4,116 0 10,000 20,000 30,000 40,000
  • 102. FastFixtureTestCase Seconds Stock Fixtures Per-Class Fixtures 0 100 200 300 400
  • 103. FastFixtureTestCase Seconds Stock Fixtures 302 Per-Class Fixtures 0 100 200 300 400
  • 104. FastFixtureTestCase Seconds Stock Fixtures 302 Per-Class Fixtures 97 0 100 200 300 400
  • 105. Fixture Bundling class ThreadsTemplateTests(FastFixtureTestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] ... class ForumsTemplateTests(FastFixtureTestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] ... class NewThreadTests(FastFixtureTestCase): fixtures = ['users.json', 'posts.json', 'forums_permissions.json'] ...
  • 106. Fixture Bundling fixtures TestCase1→ A B C TestCase2→ A B C D TestCase3→ A B C TestCase4→ B D TestCase5→ A B C D TestCase6→ A B C
  • 107. Fixture Bundling fixtures TestCase1→ A B C TestCase3→ A B C TestCase6→ A B C TestCase2→ A B C D TestCase5→ A B C D TestCase4→ B D
  • 108. Fixture Bundling fixtures ☚ load ABC TestCase1→ A B C TestCase3→ A B C TestCase6→ A B C TestCase2→ A B C D TestCase5→ A B C D TestCase4→ B D
  • 109. Fixture Bundling fixtures ☚ load ABC TestCase1→ A B C TestCase3→ A B C TestCase6→ A B C ☚ load ABCD TestCase2→ A B C D TestCase5→ A B C D TestCase4→ B D
  • 110. Fixture Bundling fixtures ☚ load ABC TestCase1→ A B C TestCase3→ A B C TestCase6→ A B C ☚ load ABCD TestCase2→ A B C D TestCase5→ A B C D ☚ load BD TestCase4→ B D
  • 112. Fixture Bundling Seconds Per-Class Fixtures Fixture Bundling 0 25 50 75 100
  • 113. Fixture Bundling Seconds Per-Class Fixtures 97 Fixture Bundling 0 25 50 75 100
  • 114. Fixture Bundling Seconds Per-Class Fixtures 97 Fixture Bundling 74 0 25 50 75 100
  • 115. Fixture Bundling --with-fixture-bundling Seconds Per-Class Fixtures 97 Fixture Bundling 74 0 25 50 75 100
  • 118. Database Reuse Seconds Fixture Bundling DB Reuse 0 20 40 60 80
  • 119. Database Reuse Seconds Fixture Bundling 74 DB Reuse 0 20 40 60 80
  • 120. Database Reuse Seconds Fixture Bundling 74 DB Reuse 62 0 20 40 60 80
  • 121. Database Reuse REUSE_DB=1 ./manage.py test Seconds Fixture Bundling 74 DB Reuse 62 0 20 40 60 80
  • 123. Speed Wrap-up Seconds Stock Django Per-Class Fixtures Fixture Bundling DB Reuse 0 100 200 300 400
  • 124. Speed Wrap-up Seconds Stock Django 302 Per-Class Fixtures Fixture Bundling DB Reuse 0 100 200 300 400
  • 125. Speed Wrap-up Seconds Stock Django 302 Per-Class Fixtures 97 Fixture Bundling DB Reuse 0 100 200 300 400
  • 126. Speed Wrap-up Seconds Stock Django 302 Per-Class Fixtures 97 Fixture Bundling 74 DB Reuse 0 100 200 300 400
  • 127. Speed Wrap-up Seconds Stock Django 302 Per-Class Fixtures 97 Fixture Bundling 74 DB Reuse 62 0 100 200 300 400
  • 128.
  • 132. Hygienic TransactionTestCases class MyNiceTestCase(TransactionTestCase): cleans_up_after_itself = True def _fixture_setup(self): ... # Don't flush.
  • 133.
  • 134. nose-progressive a decent testing ui
  • 139.
  • 140.
  • 141. nose-progressive pip install nose-progressive ./manage.py test --with-progressive
  • 142. Ponies to Come • Test models • Coverage • Profiling • Better 1.4 layout support ./manage.py test ! ! --traverse-namespace myproj.myapp
  • 144. ErikRose erik@votizen.com django-nose sprint tomorrow, 9am-2pm
  • 145. ? ErikRose erik@votizen.com django-nose sprint tomorrow, 9am-2pm Image Credits: • Anatomical diagram by Patrick J. Lynch, medical illustrator, edited by user DiebBuche: http://commons.wikimedia.org/wiki/File:Mouth_anatomy-de.svg • “Testing!” comic adapted from http://xkcd.com/303/ • Memory hierarchy diagram: http://i.imgur.com/X1Hi1.gif

Editor's Notes

  1. Welcome! It&amp;#x2019;s great to see so many of you here!\n\nI&amp;#x2019;m going to take you on a tour of Django&amp;#x2019;s nasal passage, a journey from its stock test equipment to a fast, boilerplate-free wonderland that you can enjoy right now.\n
  2. First, a little context. I work at Votizen, a startup that aims to reduce the influence of money in politics by substituting social media for expensive channels like TV and phone calls. Through this, we&amp;#x2019;re trying to give third parties, grass-roots movements a chance.\n\nNow, when you try to model the political process and develop stuff really fast, things get hairy quickly.\n\nSo: fairly heavyweight testing. 1700 tests, and we&amp;#x2019;ve found that the stock Django test framework is easy to get started with, but you start to hit your head on the ceiling as your project grows.\n
  3. First, a little context. I work at Votizen, a startup that aims to reduce the influence of money in politics by substituting social media for expensive channels like TV and phone calls. Through this, we&amp;#x2019;re trying to give third parties, grass-roots movements a chance.\n\nNow, when you try to model the political process and develop stuff really fast, things get hairy quickly.\n\nSo: fairly intense testing. 1700 tests, and we&amp;#x2019;ve found that the stock Django test framework is easy to get started with, but you start to hit your head on the ceiling as your project grows.\n
  4. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  5. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  6. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  7. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  8. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  9. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  10. Almost immediately get too many tests for one tests.py file. So you turn it into a package. Then you have to import each thing into __init__. This is annoying and error-prone.\n\nSlow: full flush for each TTC. Full fixture reload for each test. Creates fresh DBs at every invocation.\n\nOverbroad: Everything in INSTALLED_APPS gets tested: slow and pointless. At best, you&amp;#x2019;re testing whether any third-party reusable apps are configured right. In the typical case, you&amp;#x2019;re just wasting your time running tests on third-party stuff that&apos;s already known to work.\n\nRough UI. You don&amp;#x2019;t get any tracebacks till everything&amp;#x2019;s done. You don&amp;#x2019;t know when it&amp;#x2019;ll be done. There&amp;#x2019;s a lot of trash in the output.\n\nExtensibility is unscalable. If you make one subclass to do XML output, you can&amp;#x2019;t just mix in somebody else&amp;#x2019;s that limits testing to just your apps.\n\n&amp;#x201C;Nose is going to help us solve all of that.&amp;#x201D;\n\nTODO: more bubbles showing visuals: FS layout for tests/ folder, test output for &amp;#x201C;Rough&amp;#x201D;\n
  11. Before we dive into its capabilities, here&amp;#x2019;s how you install nose for use with Django.\n\npip install django-nose&amp;#x2014;a shim. It implements a Django test runner that invokes nose. nose itself is a requirement of the django-nose package, so it gets installed automatically.\n\nsettings\n\n./manage.py test, nose runs instead\n\nNow, let&amp;#x2019;s see what you get.\n
  12. Before we dive into its capabilities, here&amp;#x2019;s how you install nose for use with Django.\n\npip install django-nose&amp;#x2014;a shim. It implements a Django test runner that invokes nose. nose itself is a requirement of the django-nose package, so it gets installed automatically.\n\nsettings\n\n./manage.py test, nose runs instead\n\nNow, let&amp;#x2019;s see what you get.\n
  13. Before we dive into its capabilities, here&amp;#x2019;s how you install nose for use with Django.\n\npip install django-nose&amp;#x2014;a shim. It implements a Django test runner that invokes nose. nose itself is a requirement of the django-nose package, so it gets installed automatically.\n\nsettings\n\n./manage.py test, nose runs instead\n\nNow, let&amp;#x2019;s see what you get.\n
  14. Before we dive into its capabilities, here&amp;#x2019;s how you install nose for use with Django.\n\npip install django-nose&amp;#x2014;a shim. It implements a Django test runner that invokes nose. nose itself is a requirement of the django-nose package, so it gets installed automatically.\n\nsettings\n\n./manage.py test, nose runs instead\n\nNow, let&amp;#x2019;s see what you get.\n
  15. Before we dive into its capabilities, here&amp;#x2019;s how you install nose for use with Django.\n\npip install django-nose&amp;#x2014;a shim. It implements a Django test runner that invokes nose. nose itself is a requirement of the django-nose package, so it gets installed automatically.\n\nsettings\n\n./manage.py test, nose runs instead\n\nNow, let&amp;#x2019;s see what you get.\n
  16. \n
  17. So where Django&amp;#x2019;s stock testrunner makes you import everything into tests/__init__ like this.\n\nNose lets it look like this.\n\nHow? Nose finds your tests by name&amp;#x2026;.\n
  18. So where Django&amp;#x2019;s stock testrunner makes you import everything into tests/__init__ like this.\n\nNose lets it look like this.\n\nHow? Nose finds your tests by name&amp;#x2026;.\n
  19. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  20. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  21. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  22. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  23. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  24. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  25. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  26. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  27. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  28. Basically, if nose finds a class or function matching this pattern inside a module matching this pattern, it considers that a test.\n\nThese are some test-like names. \n\nAnd, if you have something that doesn&amp;#x2019;t fit the pattern, you can use a decorator.\n\nDon&amp;#x2019;t like pattern? Pass -m. Or write short discovery plugin.\n\nOf course, subclasses of unittest.TestCase are always considered tests, so all your old Django tests continue to work.\n
  29. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  30. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  31. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  32. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  33. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  34. In addition to making things shorter, this eliminates several classes of errors.\n\n&amp;#x2022; accidental shadowing. If you happen to have duplicate names between modules, things get shadowed, and tests you think are getting run don&amp;#x2019;t get run.\n&amp;#x2022; corollary: absurdly long names to maintain uniqueness\n&amp;#x2022; We&amp;#x2019;ve also just flat-out forgot to import a module (or a name, if not using *).\n\nBonus: Since you don&amp;#x2019;t have to import up into __init__, you&amp;#x2019;re free to import down from it, using __init__ as a utility module without fear of import cycles. I like to put my test base classes there.\n
  35. As I hinted earlier, django_nose limits its discovery to your own project dir.\n\nThat means it tests only your code, saving you a bunch of time every time you test.\n\nIncidentally: means you can have code in your Django project that doesn&amp;#x2019;t live in an app and yet is still tested.\n\nSo what do you end up with?\n
  36. You end up with freedom. But there&amp;#x2019;s no point throwing the baby out with the bathwater.\n\nI still like to nestle my tests in a &amp;#x201C;tests&amp;#x201D; package in each app. The __init__ is either blank or full of base classes and utility functions. Tests go in &amp;#x201C;test_whatever&amp;#x201D;. That naming convention is nice for making tests contrast with other artifacts in the tests folder, like static sample data.\n\nAnother perfectly reasonable convention is to put the high-entropy word first: &amp;#x201C;model_tests&amp;#x201D;, &amp;#x201C;view_tests&amp;#x201D;, etc. That&amp;#x2019;s better for type-to-select.\n\nEither way, it&amp;#x2019;s faster, less error-prone, and fewer lines of code.\n\n-----\nAt Votizen, we toyed around with splitting tests into a deeper hierarchy, putting the tests for each model in their own file e.g., but we ended up going back to this: longer files but less digging around in the filesystem.\n
  37. when you run tests: spelling different\n\nDjango way\n\nnose way: longer, more general\n\nconsolation: can run a module\n\nWraps up the boilerplate-killing portion of our program\n
  38. when you run tests: spelling different\n\nDjango way\n\nnose way: longer, more general\n\nconsolation: can run a module\n\nWraps up the boilerplate-killing portion of our program\n
  39. Here are some ways nose lets you go beyond unittest in capability.\n
  40. If you don&amp;#x2019;t need any setup or teardown, just make a function.\n\neq_. Is assertEqual. Btw, why not just assert? -O\n\nBut there are allowances for setup and teardown.\n\nSo, if you find yourself trying to figure out which class to shoehorn a test into, just don&amp;#x2019;t.\n\nPackage-, module-level setup and teardown.\n
  41. If you don&amp;#x2019;t need any setup or teardown, just make a function.\n\neq_. Is assertEqual. Btw, why not just assert? -O\n\nBut there are allowances for setup and teardown.\n\nSo, if you find yourself trying to figure out which class to shoehorn a test into, just don&amp;#x2019;t.\n\nPackage-, module-level setup and teardown.\n
  42. If you don&amp;#x2019;t need any setup or teardown, just make a function.\n\neq_. Is assertEqual. Btw, why not just assert? -O\n\nBut there are allowances for setup and teardown.\n\nSo, if you find yourself trying to figure out which class to shoehorn a test into, just don&amp;#x2019;t.\n\nPackage-, module-level setup and teardown.\n
  43. Data-driven tests. Test something intricately mathematical, not just a couple of branches that can be easily enumerated in well-partitioned tests. Fuzzy matchers.\n\nunittest in a loop &amp;#x2192; first one fails, eats the rest\n\nnose &amp;#x2192; can keep going\n\nyield callables &amp; args\n\nCan&amp;#x2019;t use it in TestCase subclasses, but works everywhere else.\n\n-----\nSometimes it feels nice to generate several similar assertions, like in a for loop.\n
  44. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  45. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  46. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  47. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  48. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  49. Sometimes it&amp;#x2019;s nice to be able to split up your tests into sets.\n\ne.g. selenium\n\nto run selenium\nto run other\n\nvalued\nselect according to values\nbool and &amp; or\n
  50. Jenkins, CruiseControl\n\nnosetests.xml\n\ncustomize file name\n\nresult like this\n
  51. Jenkins, CruiseControl\n\nnosetests.xml\n\ncustomize file name\n\nresult like this\n
  52. Jenkins, CruiseControl\n\nnosetests.xml\n\ncustomize file name\n\nresult like this\n
  53. -s\n\neats prints\neats pdb\n\nalias\n
  54. -s\n\neats prints\neats pdb\n\nalias\n
  55. TODO to represent TDD tests that are committed but whose functionality isn&amp;#x2019;t written yet.\n
  56. TODO to represent TDD tests that are committed but whose functionality isn&amp;#x2019;t written yet.\n
  57. TODO to represent TDD tests that are committed but whose functionality isn&amp;#x2019;t written yet.\n
  58. TODO to represent TDD tests that are committed but whose functionality isn&amp;#x2019;t written yet.\n
  59. Earlier when I said django-nose was just a shim, I lied. It sure started out that way, but now it has all kinds of crazy performance-enhancing features.\n\nAlmost all implemented as nose plugins&amp;#x2014;no evulz.\n\nTo demonstrate these speed features, I&amp;#x2019;m going to use the example of support.mozilla.org (affectionately nicknamed &amp;#x201C;SUMO&amp;#x201D;)&amp;#x2026;\n
  60. &amp;#x2026;which is short for &amp;#x201C;support.mozilla.org&amp;#x201D;. With about 1200 tests&amp;#x2026;\n
  61. and 1B hits/mo, moderate-sized site at Mozilla\n\nOver time, the tests had grown to take 20 minutes on our build server&amp;#x2014;5 minutes on my local box. Now, 5 minutes might not sound like long, so it&amp;#x2019;s worth saying a few words about what faster tests buy you.\n\nFirst, and most obviously, you save the swordfighting time while the tests run.\n
  62. and 1B hits/mo, moderate-sized site at Mozilla\n\nOver time, the tests had grown to take 20 minutes on our build server&amp;#x2014;5 minutes on my local box. Now, 5 minutes might not sound like long, so it&amp;#x2019;s worth saying a few words about what faster tests buy you.\n\nFirst, and most obviously, you save the swordfighting time while the tests run.\n
  63. and 1B hits/mo, moderate-sized site at Mozilla\n\nOver time, the tests had grown to take 20 minutes on our build server&amp;#x2014;5 minutes on my local box. Now, 5 minutes might not sound like long, so it&amp;#x2019;s worth saying a few words about what faster tests buy you.\n\nFirst, and most obviously, you save the swordfighting time while the tests run.\n
  64. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  65. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  66. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  67. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  68. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  69. (2) more importantly, time lost context switching, reestablishing flow\n(3) not running the tests at all (or not running all of them).\n(4) &amp;#x2026;which leads to breaking the build and slowing down your teammates\n\nBut, by using django-nose&amp;#x2019;s speed optimizations, we can solve all those problems and cut the total runtime from 5 minutes to just 1.\n\nSo where does django-nose go looking for that speed? There&amp;#x2019;s generally only one answer to that question, and that&amp;#x2019;s IO.\n\n-------------\n\n\n&amp;#x2022; CI box will run\n&amp;#x2022; 20 minutes later\n&amp;#x2022; Too long for feedback. you&apos;ve long since kicked that feature out of your head and are on to the next.\n
  70. this little chart represents 1ns as a single pixel.\n\ndwarfs\n\n(Incidentally, an SSD has on the order of a 100ns access time, but it still bottlenecks decidedly on writes: FS overhead, write amplification, etc.)\n\nA little digging with tools like the UNIX time command confirms almost all of that time to be in IO&amp;#x2014;specifically DB IO.\n\nThat&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n
  71. this little chart represents 1ns as a single pixel.\n\ndwarfs\n\n(Incidentally, an SSD has on the order of a 100ns access time, but it still bottlenecks decidedly on writes: FS overhead, write amplification, etc.)\n\nA little digging with tools like the UNIX time command confirms almost all of that time to be in IO&amp;#x2014;specifically DB IO.\n\nThat&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n
  72. this little chart represents 1ns as a single pixel.\n\ndwarfs\n\n(Incidentally, an SSD has on the order of a 100ns access time, but it still bottlenecks decidedly on writes: FS overhead, write amplification, etc.)\n\nA little digging with tools like the UNIX time command confirms almost all of that time to be in IO&amp;#x2014;specifically DB IO.\n\nThat&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n
  73. this little chart represents 1ns as a single pixel.\n\ndwarfs\n\n(Incidentally, an SSD has on the order of a 100ns access time, but it still bottlenecks decidedly on writes: FS overhead, write amplification, etc.)\n\nA little digging with tools like the UNIX time command confirms almost all of that time to be in IO&amp;#x2014;specifically DB IO.\n\nThat&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n
  74. We can confirm our rule of thumb with some profiling tools.\n\nThe Python profiler doesn&amp;#x2019;t tell you anything about I/O time; everything is in terms of CPU.\n\nHowever, the handy UNIX `time` command measures CPU and clock time of a command.\n\nWe can then look at the CPU percentage&amp;#x2014;and see that our tests are spending by far most of their time in I/O (or at least delegating to other processes).\n\nA little more digging reveals almost all of that time to be in DB IO. So, that&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n-------------\n\n&lt;possible expansion point. see Practical Large-Scale Tests.&gt;\n
  75. We can confirm our rule of thumb with some profiling tools.\n\nThe Python profiler doesn&amp;#x2019;t tell you anything about I/O time; everything is in terms of CPU.\n\nHowever, the handy UNIX `time` command measures CPU and clock time of a command.\n\nWe can then look at the CPU percentage&amp;#x2014;and see that our tests are spending by far most of their time in I/O (or at least delegating to other processes).\n\nA little more digging reveals almost all of that time to be in DB IO. So, that&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n-------------\n\n&lt;possible expansion point. see Practical Large-Scale Tests.&gt;\n
  76. We can confirm our rule of thumb with some profiling tools.\n\nThe Python profiler doesn&amp;#x2019;t tell you anything about I/O time; everything is in terms of CPU.\n\nHowever, the handy UNIX `time` command measures CPU and clock time of a command.\n\nWe can then look at the CPU percentage&amp;#x2014;and see that our tests are spending by far most of their time in I/O (or at least delegating to other processes).\n\nA little more digging reveals almost all of that time to be in DB IO. So, that&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n-------------\n\n&lt;possible expansion point. see Practical Large-Scale Tests.&gt;\n
  77. We can confirm our rule of thumb with some profiling tools.\n\nThe Python profiler doesn&amp;#x2019;t tell you anything about I/O time; everything is in terms of CPU.\n\nHowever, the handy UNIX `time` command measures CPU and clock time of a command.\n\nWe can then look at the CPU percentage&amp;#x2014;and see that our tests are spending by far most of their time in I/O (or at least delegating to other processes).\n\nA little more digging reveals almost all of that time to be in DB IO. So, that&amp;#x2019;s why django nose provides 4 optimizations for reducing it.\n\n-------------\n\n&lt;possible expansion point. see Practical Large-Scale Tests.&gt;\n
  78. The first of these is FastFixtureTestCase.\n
  79. Test fixture data goes into JSON files like this one. This is an actual fixture from forum app within SUMO. This one&amp;#x2019;s on the small side: 39 objects. Trouble is: SQL is 39 statements. Can&amp;#x2019;t use bulk inserts because of possible post-save hooks, so need SQL statement for each.\n
  80. One of SUMO&amp;#x2019;s test classes used this fixture along with two other similarly-sized ones, and it took 4 minutes to run. Seems like a long time to load a few dozen rows, no? What&amp;#x2019;s going on?\n
  81. The trouble became clear when I turned on logging in MySQL: it&amp;#x2019;s just a matter of logging in as the mysql root user and typing SET GLOBAL general_log = &apos;ON&apos;, and then tailing the log file it lays down. What became quickly evident&amp;#x2026;\n
  82. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  83. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  84. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  85. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  86. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  87. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  88. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  89. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  90. Fixtures reloaded before each test!\n\nEach test&amp;#x2026;\n&amp;#x2022; begins a transaction\n&amp;#x2022; loads fixtures\n&amp;#x2022; runs\n&amp;#x2022; rolls back the transaction.\n\nVery tidy, but very inefficient\nload, rollback,\nload, rollback,\nload, rollback,\nload, rollback.\n\nOn the SUMO tests, this was 37,583 queries. It seemed to me we could do a lot better.\n
  91. Here&amp;#x2019;s a conceptual mockup\n\n1st def\n2nd def\n3rd def\n\nHow do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n\nHere&amp;#x2019;s a conceptual mockup of an alternate base TestClass, harnessing that same rollback mechanism to do a class worth of fixture setup once, and then reusing that each test in a class. Before the first test of the class, we set up the fixtures and&amp;#x2014;here&amp;#x2019;s the difference&amp;#x2014;we commit. Then, for each test, we run it, and then we do a rollback afterward. Where in the old world, rolling back took us to a pristine database, it now takes us to an already-set-up fixture set. Finally, after all the tests in the class have been run, we explicitly remove the fixtures&amp;#x2014;remember, they&amp;#x2019;ve been committed, so we can&amp;#x2019;t just roll back&amp;#x2014;and commit. How do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n
  92. Here&amp;#x2019;s a conceptual mockup\n\n1st def\n2nd def\n3rd def\n\nHow do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n\nHere&amp;#x2019;s a conceptual mockup of an alternate base TestClass, harnessing that same rollback mechanism to do a class worth of fixture setup once, and then reusing that each test in a class. Before the first test of the class, we set up the fixtures and&amp;#x2014;here&amp;#x2019;s the difference&amp;#x2014;we commit. Then, for each test, we run it, and then we do a rollback afterward. Where in the old world, rolling back took us to a pristine database, it now takes us to an already-set-up fixture set. Finally, after all the tests in the class have been run, we explicitly remove the fixtures&amp;#x2014;remember, they&amp;#x2019;ve been committed, so we can&amp;#x2019;t just roll back&amp;#x2014;and commit. How do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n
  93. Here&amp;#x2019;s a conceptual mockup\n\n1st def\n2nd def\n3rd def\n\nHow do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n\nHere&amp;#x2019;s a conceptual mockup of an alternate base TestClass, harnessing that same rollback mechanism to do a class worth of fixture setup once, and then reusing that each test in a class. Before the first test of the class, we set up the fixtures and&amp;#x2014;here&amp;#x2019;s the difference&amp;#x2014;we commit. Then, for each test, we run it, and then we do a rollback afterward. Where in the old world, rolling back took us to a pristine database, it now takes us to an already-set-up fixture set. Finally, after all the tests in the class have been run, we explicitly remove the fixtures&amp;#x2014;remember, they&amp;#x2019;ve been committed, so we can&amp;#x2019;t just roll back&amp;#x2014;and commit. How do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n
  94. Here&amp;#x2019;s a conceptual mockup\n\n1st def\n2nd def\n3rd def\n\nHow do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n\nHere&amp;#x2019;s a conceptual mockup of an alternate base TestClass, harnessing that same rollback mechanism to do a class worth of fixture setup once, and then reusing that each test in a class. Before the first test of the class, we set up the fixtures and&amp;#x2014;here&amp;#x2019;s the difference&amp;#x2014;we commit. Then, for each test, we run it, and then we do a rollback afterward. Where in the old world, rolling back took us to a pristine database, it now takes us to an already-set-up fixture set. Finally, after all the tests in the class have been run, we explicitly remove the fixtures&amp;#x2014;remember, they&amp;#x2019;ve been committed, so we can&amp;#x2019;t just roll back&amp;#x2014;and commit. How do we do that last bit? Well, we run a modified version of Django&amp;#x2019;s stock fixture-loading routine&amp;#x2014;one that keeps track of what was loaded so we can undo it.\n
  95. With the stock Django fixture loading, SUMO fired off 37,583 queries during the course of its tests. With per-class fixtures&amp;#x2026;only 4,116. That&amp;#x2019;s 9 times less traffic to MySQL. Or, to look at it in terms of time&amp;#x2026;\n
  96. With the stock Django fixture loading, SUMO fired off 37,583 queries during the course of its tests. With per-class fixtures&amp;#x2026;only 4,116. That&amp;#x2019;s 9 times less traffic to MySQL. Or, to look at it in terms of time&amp;#x2026;\n
  97. With the stock Django fixture loading, SUMO fired off 37,583 queries during the course of its tests. With per-class fixtures&amp;#x2026;only 4,116. That&amp;#x2019;s 9 times less traffic to MySQL. Or, to look at it in terms of time&amp;#x2026;\n
  98. With the stock Django fixture loading, SUMO fired off 37,583 queries during the course of its tests. With per-class fixtures&amp;#x2026;only 4,116. That&amp;#x2019;s 9 times less traffic to MySQL. Or, to look at it in terms of time&amp;#x2026;\n
  99. stock fixtures: 302 seconds: just over 5 minutes\nper-class fixtures: &amp;#x2248;1.5 mins\n\nTo get these improvements, just subclass FFTC instead of TestCase. Caveat: post-save.\n\nIn fact, additional 4 seconds by reusing a single DB connection. Total: 93s. \n\nSo, big improvement! And all thanks to getting rid of I/O. But, there are additional speed optimizations we can enjoy.\n
  100. stock fixtures: 302 seconds: just over 5 minutes\nper-class fixtures: &amp;#x2248;1.5 mins\n\nTo get these improvements, just subclass FFTC instead of TestCase. Caveat: post-save.\n\nIn fact, additional 4 seconds by reusing a single DB connection. Total: 93s. \n\nSo, big improvement! And all thanks to getting rid of I/O. But, there are additional speed optimizations we can enjoy.\n
  101. stock fixtures: 302 seconds: just over 5 minutes\nper-class fixtures: &amp;#x2248;1.5 mins\n\nTo get these improvements, just subclass FFTC instead of TestCase. Caveat: post-save.\n\nIn fact, additional 4 seconds by reusing a single DB connection. Total: 93s. \n\nSo, big improvement! And all thanks to getting rid of I/O. But, there are additional speed optimizations we can enjoy.\n
  102. stock fixtures: 302 seconds: just over 5 minutes\nper-class fixtures: &amp;#x2248;1.5 mins\n\nTo get these improvements, just subclass FFTC instead of TestCase. Caveat: post-save.\n\nIn fact, additional 4 seconds by reusing a single DB connection. Total: 93s. \n\nSo, big improvement! And all thanks to getting rid of I/O. But, there are additional speed optimizations we can enjoy.\n
  103. 3 actual test cases from SUMO\nsame fixtures\nmerge into one class? can&amp;#x2019;t organize as we like\nBut, we can get speed anyway\n\nHere are 3 actual test cases from SUMO. They all use the same set of fixtures. Now, how might we make these faster? Since we&amp;#x2019;re loading fixtures once per class, we could merge the classes together, taking all their tests and throwing them into one enormous class. Then the fixtures would load once at the top of the class&amp;#x2026;and unload once at the bottom. That&amp;#x2019;s great, performance-wise, but it takes away our ability to organize our tests into classes as we see fit. It hurts our understandability.\n\nSo here&amp;#x2019;s what we do instead. We take advantage of nose&amp;#x2019;s prepareTest hook that lets us mess with tests before they&amp;#x2019;re run.\n
  104. Typically when nose runs your test classes, it runs them basically in alphabetical order, like shown here.\n\nthe trouble\n\nBut, by using nose&amp;#x2019;s prepareTest hook, we can write a plugin to dynamically re-order them, at test time, according to which fixtures they use. I call this &amp;#x201C;fixture bundling&amp;#x201D;.\n
  105. (explain) dink dink dink\n\nHow does this work? advisory bits\n\nwhether first\nwhether last\n\nThroughout all of this, test independence is preserved; we&amp;#x2019;re just factoring out pointlessly repeated setup.\n\nfuture: subsets\n\nTroubleshooting: you have order dependencies:\nsingletons, locale, other thread-locals\n
  106. (explain) dink dink dink\n\nHow does this work? advisory bits\n\nwhether first\nwhether last\n\nThroughout all of this, test independence is preserved; we&amp;#x2019;re just factoring out pointlessly repeated setup.\n\nfuture: subsets\n\nTroubleshooting: you have order dependencies:\nsingletons, locale, other thread-locals\n
  107. (explain) dink dink dink\n\nHow does this work? advisory bits\n\nwhether first\nwhether last\n\nThroughout all of this, test independence is preserved; we&amp;#x2019;re just factoring out pointlessly repeated setup.\n\nfuture: subsets\n\nTroubleshooting: you have order dependencies:\nsingletons, locale, other thread-locals\n
  108. So what impact did fixture bundling have on SUMO? Well, before bundling, we had 114 classes with fixtures, so we did the loading and unloading 114 times. However, there were only 11 distinct sets of fixtures, so with bundling we do it 11 times. In our case, it took about another quarter off our already improved test run.\n\nYou can use this by subclassing FastFixtureTestCase and passing --with-fixture-bundling\n
  109. So what impact did fixture bundling have on SUMO? Well, before bundling, we had 114 classes with fixtures, so we did the loading and unloading 114 times. However, there were only 11 distinct sets of fixtures, so with bundling we do it 11 times. In our case, it took about another quarter off our already improved test run.\n\nYou can use this by subclassing FastFixtureTestCase and passing --with-fixture-bundling\n
  110. So what impact did fixture bundling have on SUMO? Well, before bundling, we had 114 classes with fixtures, so we did the loading and unloading 114 times. However, there were only 11 distinct sets of fixtures, so with bundling we do it 11 times. In our case, it took about another quarter off our already improved test run.\n\nYou can use this by subclassing FastFixtureTestCase and passing --with-fixture-bundling\n
  111. So what impact did fixture bundling have on SUMO? Well, before bundling, we had 114 classes with fixtures, so we did the loading and unloading 114 times. However, there were only 11 distinct sets of fixtures, so with bundling we do it 11 times. In our case, it took about another quarter off our already improved test run.\n\nYou can use this by subclassing FastFixtureTestCase and passing --with-fixture-bundling\n
  112. So what impact did fixture bundling have on SUMO? Well, before bundling, we had 114 classes with fixtures, so we did the loading and unloading 114 times. However, there were only 11 distinct sets of fixtures, so with bundling we do it 11 times. In our case, it took about another quarter off our already improved test run.\n\nYou can use this by subclassing FastFixtureTestCase and passing --with-fixture-bundling\n
  113. waiting for DB setup sucks, esp. for trivial test\n\nwait through 15s of DB creation and initialization of basic Django metadata&amp;#x2014;stuff which was already perfectly valid at the end of the previous test run \n
  114. So, building on some work we had already done in this direction, I decided to skip the teardown of the test DB and, symmetrically, the setup on future runs.\n\ndink dink\n\nCaveat:\n&amp;#x2022; It&amp;#x2019;s not very observant, so, if you make a schema change, you have to remember to omit the reuse flag.\n\nAnd, at last, we are within a whisker of the mythical 1-minute test run.\n
  115. So, building on some work we had already done in this direction, I decided to skip the teardown of the test DB and, symmetrically, the setup on future runs.\n\ndink dink\n\nCaveat:\n&amp;#x2022; It&amp;#x2019;s not very observant, so, if you make a schema change, you have to remember to omit the reuse flag.\n\nAnd, at last, we are within a whisker of the mythical 1-minute test run.\n
  116. So, building on some work we had already done in this direction, I decided to skip the teardown of the test DB and, symmetrically, the setup on future runs.\n\ndink dink\n\nCaveat:\n&amp;#x2022; It&amp;#x2019;s not very observant, so, if you make a schema change, you have to remember to omit the reuse flag.\n\nAnd, at last, we are within a whisker of the mythical 1-minute test run.\n
  117. So, building on some work we had already done in this direction, I decided to skip the teardown of the test DB and, symmetrically, the setup on future runs.\n\ndink dink\n\nCaveat:\n&amp;#x2022; It&amp;#x2019;s not very observant, so, if you make a schema change, you have to remember to omit the reuse flag.\n\nAnd, at last, we are within a whisker of the mythical 1-minute test run.\n
  118. So, building on some work we had already done in this direction, I decided to skip the teardown of the test DB and, symmetrically, the setup on future runs.\n\ndink dink\n\nCaveat:\n&amp;#x2022; It&amp;#x2019;s not very observant, so, if you make a schema change, you have to remember to omit the reuse flag.\n\nAnd, at last, we are within a whisker of the mythical 1-minute test run.\n
  119. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  120. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  121. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  122. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  123. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  124. Here&amp;#x2019;s our pursuit of speed so far. dink dink dink dink\n\nWe&amp;#x2019;re saving something like 4 minutes per test run. That may not sound like much, but it adds up. At Mozilla I had a team of 4, and if we conservatively say we each ran the suite 4 times a day, and we save 4 minutes each time, we save 64 minutes a day. That comes out to 261 hours&amp;#x2014;or 32 working days&amp;#x2014;per year.\n\nThat&amp;#x2019;s enough for every team member to take an extra week off.\n\nSo, if you have a lot of fixture-heavy tests, be sure to grab django-nose and turn this stuff on.\n\nBut there&amp;#x2019;s just one more thing.\n
  125. We just talked about the most expensive DB operation you can possibly do: dropping the whole thing and recreating it from scratch.\n\nHorribly enough, this is what happens before every TTC, because TTCs are ones that can commit, for real.\n\nNow, the need for a DB flush is evident enough: if the testrunner is going to guarantee a clean DB for the next test, it has to either track and back out or nuke from orbit.\n\nBut reasons that have been lost to history, TTCs flush the DB before they run rather than after. Now you can imagine what would happen if a TTC ran and then a normal TC. ...\n\nThat&amp;#x2019;s why Django runs all TTCs last. so they can all make a mess&amp;#x2026;\n\nBut, wouldn&amp;#x2019;t it be nice to be able to write TTCs that are more responsible. Hygienic&amp;#x2014;if you will&amp;#x2014;and by making that guarantee, opt out of the flushing madness.\n\nThat could save you 30s of flushing PER TEST.\n
  126. We just talked about the most expensive DB operation you can possibly do: dropping the whole thing and recreating it from scratch.\n\nHorribly enough, this is what happens before every TTC, because TTCs are ones that can commit, for real.\n\nNow, the need for a DB flush is evident enough: if the testrunner is going to guarantee a clean DB for the next test, it has to either track and back out or nuke from orbit.\n\nBut reasons that have been lost to history, TTCs flush the DB before they run rather than after. Now you can imagine what would happen if a TTC ran and then a normal TC. ...\n\nThat&amp;#x2019;s why Django runs all TTCs last. so they can all make a mess&amp;#x2026;\n\nBut, wouldn&amp;#x2019;t it be nice to be able to write TTCs that are more responsible. Hygienic&amp;#x2014;if you will&amp;#x2014;and by making that guarantee, opt out of the flushing madness.\n\nThat could save you 30s of flushing PER TEST.\n
  127. We just talked about the most expensive DB operation you can possibly do: dropping the whole thing and recreating it from scratch.\n\nHorribly enough, this is what happens before every TTC, because TTCs are ones that can commit, for real.\n\nNow, the need for a DB flush is evident enough: if the testrunner is going to guarantee a clean DB for the next test, it has to either track and back out or nuke from orbit.\n\nBut reasons that have been lost to history, TTCs flush the DB before they run rather than after. Now you can imagine what would happen if a TTC ran and then a normal TC. ...\n\nThat&amp;#x2019;s why Django runs all TTCs last. so they can all make a mess&amp;#x2026;\n\nBut, wouldn&amp;#x2019;t it be nice to be able to write TTCs that are more responsible. Hygienic&amp;#x2014;if you will&amp;#x2014;and by making that guarantee, opt out of the flushing madness.\n\nThat could save you 30s of flushing PER TEST.\n
  128. We just talked about the most expensive DB operation you can possibly do: dropping the whole thing and recreating it from scratch.\n\nHorribly enough, this is what happens before every TTC, because TTCs are ones that can commit, for real.\n\nNow, the need for a DB flush is evident enough: if the testrunner is going to guarantee a clean DB for the next test, it has to either track and back out or nuke from orbit.\n\nBut reasons that have been lost to history, TTCs flush the DB before they run rather than after. Now you can imagine what would happen if a TTC ran and then a normal TC. ...\n\nThat&amp;#x2019;s why Django runs all TTCs last. so they can all make a mess&amp;#x2026;\n\nBut, wouldn&amp;#x2019;t it be nice to be able to write TTCs that are more responsible. Hygienic&amp;#x2014;if you will&amp;#x2014;and by making that guarantee, opt out of the flushing madness.\n\nThat could save you 30s of flushing PER TEST.\n
  129. Mark your TTCs with cleans_up_after_itself.\n\ndjango-nose will run it before any of those nasty, trash-spewing TTCs, so they don&amp;#x2019;t have to pre-flush.\n\nWith a large schema, this can save minutes of IO!\n\nAtm, you have to bring your own overrides to not flush. Soon, it&amp;#x2019;ll have a superclass for that.\n
  130. That wraps up the computer speed portion of our program.\n\nBut what about human speed?\n\nWhat about a decent UI&amp;#x2026;\n
  131. Django&apos;s database fixtures a good example of a piece of test setup that gets out of control, but the speed problems are actually the most harmless manifestation. There&amp;#x2019;s actually a much more insidious, more general problem here.\n
  132. &amp;#x2026;which is that setup is evil.\n
  133. standard unitttest testcase / bunch of setup in setUp() / nice. DRY\n\nThe problem is this: you start with a manageably small test fixture&amp;#x2026; (and on it goes&amp;#x2026;)\n\nsetUp &amp; fixtures the same Whether this takes the form of a Django-style DB fixture or a unittest setUp() routine matters not at all. What matters is that you have some common setup, and then you write some tests that depend on all of it.\nstory: forums fixture\n\nThen you add some more tests, ones that use parts of the setup but not all. Pretty soon, you have 30 tests depending on your setup routine. Then your requirements change, and you need to modify the setup slightly.\n\nQuick quiz: which of your tests have you invalidated?\n\nWhich of them don&amp;#x2019;t care about the bits you changed and are still okay? There&amp;#x2019;s simply no way to know without going back and rethinking your way through each test again, and you run the risk of invalidating them.\n
  134. describe / my assertion is that this way is better\n\nhelper methods / call them when you need a particular piece of setup\nThese are simplified to fit on the slide: imagine them setting up many interrelated objects&amp;#x2014;like many setups do&amp;#x2014;and returning them.\n\nequally efficient setUp() is called once for each test anyway.\nmore efficient In fact, it&amp;#x2019;s more efficient, because you never set up state that you don&amp;#x2019;t need\n\nfuture: memoized properties\n
  135. equivalent thing often happened to us with Django fixtures. On one occasion, we had this test, which makes sure user in a specific group can&amp;#x2019;t change the locked status on a forum that he doesn&amp;#x2019;t have permission on. Well, thank goodness for the docstring because I would have no idea whatsoever what was going on otherwise: id num / hidden data / fixture file off to the side.\n\nAnd can you imagine going in to edit that fixture? What if you had another test that used some of those objects, and you want to make a tweak to their state for the sake of that test? How do you find out what you shouldn&amp;#x2019;t change to keep this one happy?\n\ncoupling disaster\n
  136. uses model makers\n\nsets attrs how you say and the rest how they have to be\n\nIn this case, we&amp;#x2019;re making sure a wiki document whose title starts with &amp;#x201C;Template&amp;#x201D; gets marked as a template in the DB. All we care about in the test is the title. Now, there are all sorts of other invariants going on behind the scenes: a language has to be chosen for the document, it has to have a category, etc. model maker takes care of that\n\nno special-purpose setup at all. document() used all over our tests.\n
  137. nesting model makers\nlexically nest and reflect structure of objects, even if inside out\n\nno DB hits\n\nso simple, no library to write them&amp;#x2026;\n\n
  138. actual model maker from the wiki. Nothing really to factor out. this a complicated one.\n\nBest practices seem to be (1) make the minimal valid object (2) Don&amp;#x2019;t assume things you don&amp;#x2019;t pass as a kwarg. (3) Don&amp;#x2019;t save\n\n@with_save\n\nfactory-boy factory-girl\n
  139. So, I say any setup() routine or fixture that shares more than a little state between tests is an antipattern. It couples your tests together needlessly. It makes them brittle. And it makes them hard to understand&amp;#x2014;which bits of the data do they depend on, and which are don&amp;#x2019;t-cares, there just to satisfy some invariant from some unrelated part of the code? Furthermore, the setup is lexically far from the test code. Wouldn&amp;#x2019;t it be preferable&amp;#x2014;all other things being equal&amp;#x2014;to have it nearby so you could just read straight through a test method and walk away enlightened?\n
  140. So, I say any setup() routine or fixture that shares more than a little state between tests is an antipattern. It couples your tests together needlessly. It makes them brittle. And it makes them hard to understand&amp;#x2014;which bits of the data do they depend on, and which are don&amp;#x2019;t-cares, there just to satisfy some invariant from some unrelated part of the code? Furthermore, the setup is lexically far from the test code. Wouldn&amp;#x2019;t it be preferable&amp;#x2014;all other things being equal&amp;#x2014;to have it nearby so you could just read straight through a test method and walk away enlightened?\n
  141. So, I say any setup() routine or fixture that shares more than a little state between tests is an antipattern. It couples your tests together needlessly. It makes them brittle. And it makes them hard to understand&amp;#x2014;which bits of the data do they depend on, and which are don&amp;#x2019;t-cares, there just to satisfy some invariant from some unrelated part of the code? Furthermore, the setup is lexically far from the test code. Wouldn&amp;#x2019;t it be preferable&amp;#x2014;all other things being equal&amp;#x2014;to have it nearby so you could just read straight through a test method and walk away enlightened?\n
  142. So, I say any setup() routine or fixture that shares more than a little state between tests is an antipattern. It couples your tests together needlessly. It makes them brittle. And it makes them hard to understand&amp;#x2014;which bits of the data do they depend on, and which are don&amp;#x2019;t-cares, there just to satisfy some invariant from some unrelated part of the code? Furthermore, the setup is lexically far from the test code. Wouldn&amp;#x2019;t it be preferable&amp;#x2014;all other things being equal&amp;#x2014;to have it nearby so you could just read straight through a test method and walk away enlightened?\n
  143. So, I say any setup() routine or fixture that shares more than a little state between tests is an antipattern. It couples your tests together needlessly. It makes them brittle. And it makes them hard to understand&amp;#x2014;which bits of the data do they depend on, and which are don&amp;#x2019;t-cares, there just to satisfy some invariant from some unrelated part of the code? Furthermore, the setup is lexically far from the test code. Wouldn&amp;#x2019;t it be preferable&amp;#x2014;all other things being equal&amp;#x2014;to have it nearby so you could just read straight through a test method and walk away enlightened?\n
  144. &amp;#x201C;Then you&amp;#x2019;d have&amp;#x201D;\n\ndink dink dink dink\n\nalso&amp;#x2026;\nfreedom to refactor without breaking tests\n\nfreedom to organize into testcases by higher-level criteria, not just shared setup\n
  145. &amp;#x201C;Then you&amp;#x2019;d have&amp;#x201D;\n\ndink dink dink dink\n\nalso&amp;#x2026;\nfreedom to refactor without breaking tests\n\nfreedom to organize into testcases by higher-level criteria, not just shared setup\n
  146. &amp;#x201C;Then you&amp;#x2019;d have&amp;#x201D;\n\ndink dink dink dink\n\nalso&amp;#x2026;\nfreedom to refactor without breaking tests\n\nfreedom to organize into testcases by higher-level criteria, not just shared setup\n
  147. &amp;#x201C;Then you&amp;#x2019;d have&amp;#x201D;\n\ndink dink dink dink\n\nalso&amp;#x2026;\nfreedom to refactor without breaking tests\n\nfreedom to organize into testcases by higher-level criteria, not just shared setup\n
  148. &amp;#x201C;Then you&amp;#x2019;d have&amp;#x201D;\n\ndink dink dink dink\n\nalso&amp;#x2026;\nfreedom to refactor without breaking tests\n\nfreedom to organize into testcases by higher-level criteria, not just shared setup\n
  149. &amp;#x2026;that makes tests informative to humans.\n\nwhile &amp; after running\n\nhelps you diagnose &amp; debug?\n
  150. This is the standard Django test display. It&amp;#x2019;s basically what standard unittest spits out: TextTestRunner. Took the liberty of trimming out some of the time-consuming setup.\n\nBut look at this. Dots. Dot after dot after dot. Errors&amp;#x2014;can&amp;#x2019;t do anything about them. Can&amp;#x2019;t even see what they are. Don&amp;#x2019;t know how long to wait. Should I get a drink? A sandwich? A spouse? In fact, this suite goes on for over 2 minutes. Start thinking of questions.\n\nAnd finally we&amp;#x2019;re left with this mess. Wrapped around, full of garbage like &amp;#x201C;File&amp;#x201D; and 8&amp;#x201C;Traceback (most recent line last)&amp;#x201D; as if we&amp;#x2019;ve never read a traceback before. Unless you&amp;#x2019;re using a fancy IDE, you know well the routine of squinting at the traceback, finding what file went wrong, loading it up in your editor, and then typing in the line number to go to. What a waste of time!\n
  151. Wouldn&amp;#x2019;t something like this be better? I&amp;#x2019;ve put together an alternative testrunner called nose-progressive. It works with your existing tests without you having to do anything. Here&amp;#x2019;s what it does. (K to pause)\n\nprogress bar\nnames of tests\nprompt tracebacks\ntraceback formatting is spectacular:\nno wasted lines\nno wasted columns. paths relativized\ntest frame first, omit junk frames (eq_ too)\ncolored function names. easy to scan down the stack\ncopy &amp; paste test name to re-run\nbest of all, editor shortcuts\n
  152. One you know where you want to go, just triple-click that line, do a quick copy-paste, and you end up right in your editor, at the right line.\n\nworks with all editors\n
  153. To get this in your project&amp;#x2026;\npip install nose-progressive\n./manage.py test --with-progressive\n\nAlso works great when you&amp;#x2019;re not using Django.\n
  154. \n
  155. \n
  156. There&amp;#x2019;s lots more potential, and I&amp;#x2019;m behind on the review queue for django-nose. We&amp;#x2019;re having a short sprint tomorrow 9am-2pm on, to see how far we can get merging patches.\n\nThat brings us to the end. Thank you very much!\n\nburning questions\n
  157. There&amp;#x2019;s lots more potential, and I&amp;#x2019;m behind on the review queue for django-nose. We&amp;#x2019;re having a short sprint tomorrow 9am-2pm on, to see how far we can get merging patches.\n\nThat brings us to the end. Thank you very much!\n\nburning questions\n
  158. There&amp;#x2019;s lots more potential, and I&amp;#x2019;m behind on the review queue for django-nose. We&amp;#x2019;re having a short sprint tomorrow 9am-2pm on, to see how far we can get merging patches.\n\nThat brings us to the end. Thank you very much!\n\nburning questions\n