├── __init__.py ├── bar ├── __init__.py ├── views.py ├── models.py ├── tests │ ├── unit │ │ ├── test_bar.py │ │ └── __init__.py │ ├── functional │ │ ├── test_bar.py │ │ └── __init__.py │ ├── integration │ │ ├── test_bar.py │ │ └── __init__.py │ └── __init__.py └── tests.py ├── apps └── foo │ ├── __init__.py │ ├── views.py │ ├── models.py │ ├── tests │ ├── unit │ │ ├── test_foo.py │ │ └── __init__.py │ ├── functional │ │ ├── test_foo.py │ │ └── __init__.py │ ├── integration │ │ ├── test_foo.py │ │ └── __init__.py │ └── __init__.py │ └── tests.py ├── unclebob ├── views.py ├── models.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── test.py ├── version.py ├── __init__.py ├── options.py ├── monkey.py └── runners.py ├── requirements.txt ├── .travis.yml ├── .gitignore ├── manage.py ├── urls.py ├── tests ├── __init__.py ├── unit │ ├── __init__.py │ └── test_nose_runner.py ├── functional │ └── __init__.py └── integration │ └── __init__.py ├── bourbon.py ├── Makefile ├── settings.py ├── setup.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bar/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/foo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bar/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /apps/foo/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /unclebob/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /unclebob/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | south 3 | nose 4 | sure 5 | mock 6 | coverage 7 | -------------------------------------------------------------------------------- /bar/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/foo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /bar/tests/unit/test_bar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | def test_bar_should_be_found(): 5 | "tests under bar/tests/unit/test*.py should be found" 6 | 7 | -------------------------------------------------------------------------------- /apps/foo/tests/unit/test_foo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | def test_foo_should_be_found(): 6 | "tests under apps/foo/tests/unit/test*.py should be found" 7 | 8 | -------------------------------------------------------------------------------- /bar/tests/functional/test_bar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | def test_bar_should_be_found(): 5 | "tests under bar/tests/functional/test*.py should be found" 6 | 7 | -------------------------------------------------------------------------------- /bar/tests/integration/test_bar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | def test_bar_should_be_found(): 5 | "tests under bar/tests/integration/test*.py should be found" 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | # command to install dependencies 6 | install: 7 | - pip install -r requirements.txt 8 | # command to run tests 9 | script: make 10 | -------------------------------------------------------------------------------- /apps/foo/tests/functional/test_foo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | def test_foo_should_be_found(): 6 | "tests under apps/foo/tests/functional/test*.py should be found" 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .coverage 3 | ./tests/functional/2nd_feature_dir/step_definitions/__init__.py 4 | docs/_build/ 5 | docs/_build 6 | lettuce.egg-info/ 7 | build/ 8 | dist/ 9 | .DS_Store 10 | *.swp 11 | uncle.bob 12 | unclebob.egg-info/ 13 | unclebob*.tar.gz 14 | -------------------------------------------------------------------------------- /apps/foo/tests/integration/test_foo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.conf import settings 4 | 5 | 6 | def test_foo_should_be_found(): 7 | "tests under apps/foo/tests/integration/test*.py should be found" 8 | 9 | 10 | def test_bourbon_is_loaded(): 11 | "Making sure BOURBON is loaded" 12 | assert settings.BOURBON_LOADED_TIMES is 1, \ 13 | 'should be 1 but got %d' % settings.BOURBON_LOADED_TIMES 14 | -------------------------------------------------------------------------------- /bar/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /apps/foo/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Examples: 9 | # url(r'^$', 'django_unclebob.views.home', name='home'), 10 | # url(r'^django_unclebob/', include('django_unclebob.foo.urls')), 11 | 12 | # Uncomment the admin/doc line below to enable admin documentation: 13 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | # url(r'^admin/', include(admin.site.urls)), 17 | ) 18 | -------------------------------------------------------------------------------- /unclebob/management/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bar/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /apps/foo/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bar/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /unclebob/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /apps/foo/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /unclebob/version.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | version = '0.4.0' 28 | -------------------------------------------------------------------------------- /bar/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bar/tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /apps/foo/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /apps/foo/tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bourbon.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011-2012> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | from django.conf import settings 28 | settings.BOURBON_LOADED_TIMES += 1 29 | -------------------------------------------------------------------------------- /unclebob/__init__.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | def take_care_of_my_tests(): 29 | from unclebob import monkey 30 | monkey.patch() 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: check_dependencies unit functional integration 2 | 3 | 4 | filename=unclebob-`python -c 'import unclebob.version;print unclebob.version.version'`.tar.gz 5 | 6 | export UNCLEBOB_DEPENDENCIES:= nose django south sure 7 | export DJANGO_SETTINGS_MODULE:= djangounclebob.settings 8 | export PYTHONPATH:= ${PWD} 9 | 10 | check_dependencies: 11 | @echo "Checking for dependencies to run tests ..." 12 | @rm -f .coverage 13 | @for dependency in `echo $$UNCLEBOB_DEPENDENCIES`; do \ 14 | python -c "import $$dependency" 2>/dev/null || (echo "You must install $$dependency in order to run unclebob's tests" && exit 3) ; \ 15 | done 16 | 17 | unit: clean 18 | @echo "Running unit tests ..." 19 | @python manage.py test --unit -v2 20 | @nosetests -s --verbosity=2 --with-coverage --cover-inclusive tests/unit --cover-package=unclebob 21 | 22 | functional: clean 23 | @echo "Running functional tests ..." 24 | @python manage.py test --functional -v2 25 | @nosetests -s --verbosity=2 --cover-erase tests/functional 26 | 27 | integration: clean 28 | @echo "Running integration tests ..." 29 | @python manage.py test --integration -v2 30 | @nosetests -s --verbosity=2 --cover-erase tests/integration 31 | 32 | clean: 33 | @printf "Cleaning up files that are already in .gitignore... " 34 | @for pattern in `cat .gitignore`; do find . -name "$$pattern" -delete; done 35 | @echo "OK!" 36 | 37 | release: clean unit functional integration 38 | @make clean 39 | @printf "Exporting to $(filename)... " 40 | @python setup.py sdist register upload 41 | @echo "DONE!" 42 | -------------------------------------------------------------------------------- /unclebob/options.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | from optparse import make_option 27 | 28 | 29 | def add_option(kind): 30 | msg = 'Look for {0} tests on appname/tests/{0}/*test*.py' 31 | return make_option( 32 | '--%s' % kind, action='store_true', 33 | dest='is_%s' % kind, default=False, 34 | help=msg.format(kind)) 35 | 36 | basic = [ 37 | add_option('unit'), 38 | add_option('functional'), 39 | add_option('integration'), 40 | ] 41 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import unclebob 6 | 7 | from os.path import dirname, abspath, join 8 | LOCAL_FILE = lambda *path: join(abspath(dirname(__file__)), *path) 9 | sys.path.append(LOCAL_FILE('apps')) 10 | 11 | DEBUG = True 12 | TEMPLATE_DEBUG = DEBUG 13 | 14 | ADMINS = ( 15 | (u'Gabriel Falcão', 'gabriel@lettuce.it'), 16 | ) 17 | 18 | MANAGERS = ADMINS 19 | 20 | DATABASES = { 21 | 'default': { 22 | 'ENGINE': 'django.db.backends.sqlite3', 23 | 'NAME': LOCAL_FILE('uncle.bob'), 24 | } 25 | } 26 | 27 | TIME_ZONE = 'America/Chicago' 28 | LANGUAGE_CODE = 'en-us' 29 | SITE_ID = 1 30 | USE_I18N = True 31 | USE_L10N = True 32 | MEDIA_ROOT = '' 33 | MEDIA_URL = '' 34 | STATIC_ROOT = '' 35 | STATIC_URL = '/static/' 36 | ADMIN_MEDIA_PREFIX = '/static/admin/' 37 | STATICFILES_DIRS = ( 38 | ) 39 | 40 | STATICFILES_FINDERS = ( 41 | 'django.contrib.staticfiles.finders.FileSystemFinder', 42 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 43 | ) 44 | 45 | 46 | TEMPLATE_LOADERS = ( 47 | 'django.template.loaders.filesystem.Loader', 48 | 'django.template.loaders.app_directories.Loader', 49 | ) 50 | 51 | MIDDLEWARE_CLASSES = ( 52 | 'django.middleware.common.CommonMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'django.middleware.csrf.CsrfViewMiddleware', 55 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 56 | 'django.contrib.messages.middleware.MessageMiddleware', 57 | ) 58 | 59 | ROOT_URLCONF = 'urls' 60 | 61 | TEMPLATE_DIRS = ( 62 | ) 63 | 64 | INSTALLED_APPS = ( 65 | 'django.contrib.auth', 66 | 'django.contrib.contenttypes', 67 | 'django.contrib.sessions', 68 | 'django.contrib.sites', 69 | 'django.contrib.messages', 70 | 'django.contrib.staticfiles', 71 | 'south', 72 | 'foo', 73 | 'bar', 74 | ) 75 | TEST_RUNNER = 'unclebob.runners.Nose' 76 | unclebob.take_care_of_my_tests() 77 | 78 | BOURBON_LOADED_TIMES = 0 79 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011-2012> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | import os 28 | from setuptools import setup 29 | 30 | 31 | def get_packages(): 32 | # setuptools can't do the job :( 33 | packages = [] 34 | for root, dirnames, filenames in os.walk('unclebob'): 35 | if '__init__.py' in filenames: 36 | packages.append(".".join(os.path.split(root)).strip(".")) 37 | 38 | return packages 39 | 40 | setup(name='unclebob', 41 | version='0.4.0', 42 | description='Django testing tool set', 43 | author=u'Gabriel Falcao', 44 | author_email='gabriel@nacaolivre.org', 45 | url='http://github.com/gabrielfalcao/unclebob', 46 | install_requires=['nose', 'sure', 'Django'], 47 | packages=get_packages(), 48 | ) 49 | -------------------------------------------------------------------------------- /unclebob/monkey.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | from functools import wraps 27 | from unclebob.options import basic 28 | from django.core import management 29 | 30 | 31 | def patch(): 32 | "monkey patches the django test command" 33 | def patch_get_commands(get_commands): 34 | @wraps(get_commands) 35 | def the_patched(*args, **kw): 36 | res = get_commands(*args, **kw) 37 | tester = res.get('test', None) 38 | if tester is None: 39 | return res 40 | if isinstance(tester, basestring): 41 | tester = management.load_command_class('django.core', 'test') 42 | 43 | new_options = basic[:] 44 | 45 | ignored_opts = ('--unit', '--functional', '--integration') 46 | for opt in tester.option_list: 47 | if opt.get_opt_string() not in ignored_opts: 48 | new_options.insert(0, opt) 49 | 50 | tester.option_list = tuple(new_options) 51 | res['test'] = tester 52 | return res 53 | 54 | return the_patched 55 | 56 | management.get_commands = patch_get_commands(management.get_commands) 57 | -------------------------------------------------------------------------------- /unclebob/management/commands/test.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | import sys 28 | from optparse import make_option 29 | from django.conf import settings 30 | from django.core.management.commands import test 31 | from django.core.management import call_command 32 | 33 | 34 | try: 35 | from south.management.commands import patch_for_test_db_setup 36 | USE_SOUTH = getattr(settings, "SOUTH_TESTS_MIGRATE", True) 37 | except: 38 | USE_SOUTH = False 39 | 40 | 41 | def add_option(kind): 42 | msg = 'Look for {0} tests on appname/tests/{0}/*test*.py' 43 | return make_option( 44 | '--%s' % kind, action='store_true', 45 | dest='is_%s' % kind, default=True, 46 | help=msg.format(kind)) 47 | 48 | 49 | class Command(test.Command): 50 | option_list = test.Command.option_list + ( 51 | add_option('unit'), 52 | add_option('functional'), 53 | add_option('integration'), 54 | ) 55 | 56 | def handle(self, *test_labels, **options): 57 | from django.conf import settings 58 | from django.test.utils import get_runner 59 | 60 | verbosity = int(options.get('verbosity', 1)) 61 | interactive = options.get('interactive', True) 62 | failfast = options.get('failfast', False) 63 | 64 | TestRunner = get_runner(settings) 65 | 66 | if USE_SOUTH: 67 | patch_for_test_db_setup() 68 | call_command('migrate', interactive=False, verbosity=0) 69 | 70 | test_runner = TestRunner( 71 | verbosity=verbosity, 72 | interactive=interactive, 73 | failfast=failfast, 74 | ) 75 | 76 | failures = test_runner.run_tests(test_labels, **options) 77 | if failures: 78 | sys.exit(bool(failures)) 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unclebob 2 | > Version 0.4.0 3 | 4 | # What 5 | 6 | UncleBob is a simple django app that allow you writting per-app tests within these paths: 7 | 8 | * `/tests/unit/test*.py` - for unit tests 9 | * `/tests/functional/test*.py` - for functional tests 10 | * `/tests/integration/test*.py` - for integration tests 11 | 12 | # installing 13 | 14 | ## first of all 15 | 16 | pip install unclebob 17 | 18 | ## add it to your django project 19 | 20 | on settings.py 21 | 22 | ```python 23 | INSTALLED_APPS = ( 24 | ... 25 | 'unclebob', 26 | ... 27 | ) 28 | 29 | TEST_RUNNER = 'unclebob.runners.Nose' 30 | import unclebob 31 | unclebob.take_care_of_my_tests() 32 | ``` 33 | 34 | # running 35 | 36 | just use the regular **test** command: 37 | 38 | python manage.py test 39 | 40 | ## running only the unit tests 41 | 42 | python manage.py test --unit 43 | 44 | ## running only the functional tests 45 | 46 | python manage.py test --functional 47 | 48 | ## running only the integration tests 49 | 50 | python manage.py test --integration 51 | 52 | 53 | ## running only a specific path 54 | 55 | python manage.py test path/to/app/tests 56 | 57 | or 58 | 59 | python manage.py test path/to/app/tests/unit 60 | 61 | # warning: 62 | 63 | if you run only the `unit` tests, then unclebob is NOT going to setup 64 | the test database. Since 65 | [unit tests](http://en.wikipedia.org/wiki/Unit_testing) are supposed 66 | to be "unwired", what I mean is that unit tests MUST NOT make use of 67 | actual database, filesystem or network at all. 68 | 69 | Instead, they must test isolated parts of your code. 70 | 71 | For that reason, ma'am/sir, Uncle Bob is gonna break your neck in case you 72 | decide to use those, so called, "external resources" in your unit 73 | tests. 74 | 75 | # although you can tell unclebob to never touch the database at all 76 | 77 | in your `settings.py` 78 | 79 | ```python 80 | UNCLEBOB_NO_DATABASE = True 81 | ``` 82 | 83 | # other aspects 84 | 85 | ## 1. it provides an environment variable, so that you'll know when unclebob is running 86 | 87 | When unclebob is running tests, it sets the environment variable 88 | `UNCLEBOB_RUNNING` to the current working directory. 89 | 90 | You can use it, for example, in your codebase fr avoiding logging 91 | during the tests. 92 | 93 | # Motivation 94 | 95 | [nose](http://code.google.com/p/python-nose/) is such a really nice 96 | tool for writting tests on python. 97 | 98 | Instead of using the unittest framework, which is builtin python thou 99 | is less fun to use. 100 | 101 | And you know, the most joyable is the writting/running test 102 | experience, more developers will write tests for the project. And as 103 | much tests, better. 104 | 105 | # Naming 106 | 107 | This project was named after [Uncle Bob Martin](http://en.wikipedia.org/wiki/Robert_Cecil_Martin), 108 | one of the [agile manifesto](http://agilemanifesto.org/) chaps that 109 | brought code cleaness techniques and advices to the rest of us. 110 | -------------------------------------------------------------------------------- /unclebob/runners.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | import os 27 | import imp 28 | import nose 29 | 30 | from os import path as os_path 31 | from os.path import dirname, join 32 | from optparse import OptionParser 33 | 34 | from django.conf import settings 35 | from django.core import management 36 | from django.test.simple import DjangoTestSuiteRunner 37 | 38 | from unclebob.options import basic 39 | 40 | 41 | def unique(lst): 42 | l = [] 43 | for item in lst: 44 | if item not in l: 45 | l.append(item) 46 | return l 47 | 48 | 49 | class Nose(DjangoTestSuiteRunner): 50 | IGNORED_APPS = ['unclebob', 'south'] 51 | 52 | def get_setting_or_list(self, name): 53 | return getattr(settings, name, []) 54 | 55 | def get_ignored_apps(self): 56 | apps = self.IGNORED_APPS[:] 57 | apps.extend(self.get_setting_or_list('UNCLEBOB_IGNORED_APPS')) 58 | return apps 59 | 60 | def get_argv_options(self): 61 | parser = OptionParser() 62 | map(parser.add_option, basic) 63 | command = management.get_commands()['test'] 64 | 65 | if isinstance(command, basestring): 66 | command = management.load_command_class('django.core', 'test') 67 | 68 | for opt in command.option_list: 69 | if opt.get_opt_string() not in ( 70 | '--unit', 71 | '--functional', 72 | '--integration', 73 | ): 74 | parser.add_option(opt) 75 | 76 | (_options, _) = parser.parse_args() 77 | 78 | options = dict( 79 | is_unit=_options.is_unit, 80 | is_functional=_options.is_functional, 81 | is_integration=_options.is_integration, 82 | ) 83 | return options 84 | 85 | def get_nose_argv(self, covered_package_names=None): 86 | packages_to_cover = covered_package_names or [] 87 | 88 | args = [ 89 | 'nosetests', '-s', 90 | '--verbosity=%d' % int(self.verbosity), 91 | '--exe', 92 | '--logging-clear-handlers', 93 | '--cover-inclusive', 94 | '--cover-erase', 95 | ] 96 | args.extend(self.get_setting_or_list('UNCLEBOB_EXTRA_NOSE_ARGS')) 97 | 98 | def cover_these(package): 99 | return '--cover-package="%s"' % package 100 | 101 | def for_packages(package): 102 | try: 103 | imp.find_module(package) 104 | return True 105 | except ImportError: 106 | return False 107 | 108 | args.extend(map(cover_these, filter(for_packages, packages_to_cover))) 109 | return args 110 | 111 | def get_apps(self): 112 | IGNORED_APPS = self.get_ignored_apps() 113 | 114 | def not_builtin(name): 115 | return not name.startswith('django.') 116 | 117 | def not_ignored(name): 118 | return name not in IGNORED_APPS 119 | 120 | return filter(not_ignored, 121 | filter(not_builtin, settings.INSTALLED_APPS)) 122 | 123 | def get_paths_for(self, appnames, appending=None): 124 | paths = [] 125 | 126 | for name in appnames: 127 | try: 128 | params = imp.find_module(name) 129 | module = imp.load_module(name, *params) 130 | module_filename = module.__file__ 131 | module_path = dirname(module_filename) 132 | except ImportError: 133 | module_path = name 134 | if os_path.exists(module_path): 135 | paths.append(module_path) 136 | 137 | appendees = [] 138 | if isinstance(appending, (list, tuple)): 139 | appendees = appending 140 | 141 | path = join(os_path.abspath(module_path), *appendees) 142 | 143 | if os_path.exists(path): 144 | paths.append(path) 145 | 146 | return unique(paths) 147 | 148 | def migrate_to_south_if_needed(self): 149 | should_migrate = getattr(settings, 'SOUTH_TESTS_MIGRATE', False) 150 | if 'south' in settings.INSTALLED_APPS and should_migrate: 151 | print "Uncle Bob is running the database migrations..." 152 | management.call_command('migrate') 153 | 154 | def sip_some_bourbon(self): 155 | try: 156 | import bourbon 157 | except Exception: 158 | pass 159 | 160 | def run_tests(self, test_labels, extra_tests=None, **kwargs): 161 | # Pretend it's a production environment. 162 | settings.DEBUG = False 163 | os.environ['UNCLEBOB_RUNNING'] = os.getcwdu() 164 | 165 | app_names = test_labels or self.get_apps() 166 | nose_argv = self.get_nose_argv(covered_package_names=app_names) 167 | 168 | old_config = None 169 | 170 | options = self.get_argv_options() 171 | 172 | is_unit = options['is_unit'] 173 | is_functional = options['is_functional'] 174 | is_integration = options['is_integration'] 175 | 176 | not_unitary = not is_unit or (is_functional or is_integration) 177 | specific_kind = is_unit or is_functional or is_integration 178 | 179 | apps = [] 180 | 181 | for kind in ('unit', 'functional', 'integration'): 182 | if options['is_%s' % kind] is True: 183 | apps.extend(self.get_paths_for(app_names, 184 | appending=['tests', kind])) 185 | 186 | if not specific_kind: 187 | apps.extend(self.get_paths_for(app_names, appending=['tests'])) 188 | 189 | nose_argv.extend(apps) 190 | 191 | eligible_for_test_db = not getattr( 192 | settings, 'UNCLEBOB_NO_DATABASE', False) 193 | 194 | if eligible_for_test_db and not_unitary: 195 | # eligible_for_test_db means the user did not set the 196 | # settings.UNCLEBOB_NO_DATABASE = True 197 | 198 | # and 199 | 200 | # not unitary means that should create a test database and 201 | # migrate if needed (support only south now) 202 | old_verbosity = self.verbosity 203 | self.verbosity = 0 204 | print "Uncle Bob is preparing the test database..." 205 | self.setup_test_environment() 206 | old_config = self.setup_databases() 207 | self.migrate_to_south_if_needed() 208 | self.verbosity = old_verbosity 209 | 210 | print "Uncle Bob will run the tests now..." 211 | 212 | self.sip_some_bourbon() # loading the "bourbon.py" file 213 | passed = nose.run(argv=unique(nose_argv)) 214 | 215 | if eligible_for_test_db and not_unitary: 216 | self.teardown_databases(old_config) 217 | self.teardown_test_environment() 218 | 219 | if passed: 220 | return 0 221 | else: 222 | return 1 223 | -------------------------------------------------------------------------------- /tests/unit/test_nose_runner.py: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) <2011> Gabriel Falcão 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation 8 | # files (the "Software"), to deal in the Software without 9 | # restriction, including without limitation the rights to use, 10 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following 13 | # conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | import os 27 | import sys 28 | import imp 29 | import mock 30 | import nose 31 | 32 | from StringIO import StringIO 33 | from django.conf import settings 34 | from django.core import management 35 | from sure import that, that_with_context 36 | 37 | from unclebob.runners import Nose 38 | 39 | 40 | def get_settings(obj): 41 | attrtuple = lambda x: (x, getattr(obj, x)) 42 | normalattrs = lambda x: not x.startswith("_") 43 | return dict(map(attrtuple, filter(normalattrs, dir(obj)))) 44 | 45 | 46 | def prepare_stuff(context, *args, **kw): 47 | context.runner = Nose() 48 | context.old_settings = get_settings(settings) 49 | context.options = { 50 | 'is_unit': False, 51 | 'is_functional': False, 52 | 'is_integration': False, 53 | } 54 | context.old_argv = sys.argv[:] 55 | sys.stdout = StringIO() 56 | sys.stderr = StringIO() 57 | context.runner.get_argv_options = lambda: context.options 58 | 59 | 60 | def and_cleanup_the_mess(context, *args, **kw): 61 | del context.runner 62 | sys.stdout = sys.__stdout__ 63 | sys.stderr = sys.__stderr__ 64 | sys.argv = context.old_argv 65 | 66 | original_settings = get_settings(context.old_settings) 67 | for attr in get_settings(settings): 68 | if attr not in original_settings: 69 | delattr(settings, attr) 70 | 71 | for attr, value in original_settings.items(): 72 | try: 73 | setattr(settings, attr, value) 74 | except AttributeError: 75 | pass 76 | 77 | 78 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 79 | def test_nosetestrunner_should_have_some_basic_ignored_apps(context): 80 | u"Nose should have some basic ignored apps" 81 | assert that(context.runner.get_ignored_apps()).equals([ 82 | 'unclebob', 83 | 'south', 84 | ]) 85 | 86 | 87 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 88 | def test_get_ignored_apps_gets_extended_by_settings(context): 89 | u"should support extending the ignored apps through settings" 90 | settings.UNCLEBOB_IGNORED_APPS = ['foo', 'bar'] 91 | assert that(context.runner.get_ignored_apps()).equals([ 92 | 'unclebob', 93 | 'south', 94 | 'foo', 95 | 'bar', 96 | ]) 97 | 98 | 99 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 100 | def test_should_have_a_base_nose_argv(context): 101 | u"Nose.get_nose_argv have a bases to start from" 102 | 103 | assert that(context.runner.get_nose_argv()).equals([ 104 | 'nosetests', '-s', '--verbosity=1', '--exe', 105 | '--logging-clear-handlers', 106 | '--cover-inclusive', '--cover-erase', 107 | ]) 108 | 109 | 110 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 111 | def test_should_allow_extending_base_argv_thru_settings(context): 112 | u"Nose.get_nose_argv support extending base args thru settings" 113 | 114 | settings.UNCLEBOB_EXTRA_NOSE_ARGS = [ 115 | '--cover-package="some_module"', 116 | ] 117 | assert that(context.runner.get_nose_argv()).equals([ 118 | 'nosetests', '-s', '--verbosity=1', '--exe', 119 | '--logging-clear-handlers', 120 | '--cover-inclusive', '--cover-erase', 121 | '--cover-package="some_module"', 122 | ]) 123 | 124 | 125 | @mock.patch.object(imp, 'find_module') 126 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 127 | def test_should_allow_extending_covered_packages(context, find_module): 128 | u"Nose.get_nose_argv easily support extending covered packages" 129 | 130 | arguments = context.runner.get_nose_argv(covered_package_names=[ 131 | 'one_app', 132 | 'otherapp', 133 | ]) 134 | 135 | arguments.should.equal([ 136 | 'nosetests', '-s', '--verbosity=1', '--exe', 137 | '--logging-clear-handlers', 138 | '--cover-inclusive', '--cover-erase', 139 | '--cover-package="one_app"', 140 | '--cover-package="otherapp"', 141 | ]) 142 | 143 | find_module.assert_has_calls([ 144 | mock.call('one_app'), 145 | mock.call('otherapp'), 146 | ]) 147 | 148 | 149 | @mock.patch.object(imp, 'find_module') 150 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 151 | def test_get_nose_argv_when_imp_raises(context, find_module): 152 | u"Nose.get_nose_argv ignores given package names that raise ImportError" 153 | 154 | def raise_importerror_if_one_app(package): 155 | if package == 'one_app': 156 | raise ImportError('oooops') 157 | 158 | find_module.side_effect = raise_importerror_if_one_app 159 | 160 | arguments = context.runner.get_nose_argv(covered_package_names=[ 161 | 'one_app', 162 | 'otherapp', 163 | ]) 164 | 165 | assert that(arguments).equals([ 166 | 'nosetests', '-s', '--verbosity=1', '--exe', 167 | '--logging-clear-handlers', 168 | '--cover-inclusive', '--cover-erase', 169 | '--cover-package="otherapp"', 170 | ]) 171 | find_module.assert_has_calls([ 172 | mock.call('one_app'), 173 | mock.call('otherapp'), 174 | ]) 175 | 176 | 177 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 178 | def test_should_fetch_the_apps_names_thru_get_apps_method(context): 179 | u"Nose.get_apps filters django builtin apps" 180 | settings.INSTALLED_APPS = ( 181 | 'django.contrib.sites', 182 | 'django.contrib.messages', 183 | 'django.contrib.staticfiles', 184 | 'foo', 185 | 'bar', 186 | 'django.contrib.auth', 187 | 'django.contrib.contenttypes', 188 | 'django.contrib.sessions', 189 | ) 190 | 191 | assert that(context.runner.get_apps()).equals(( 192 | 'foo', 193 | 'bar', 194 | )) 195 | 196 | 197 | @mock.patch.object(management, 'call_command') 198 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 199 | def test_migrate_to_south_calls_migrate_if_properly_set(context, call_command): 200 | u"migrate_to_south_if_needed migrates on settings.SOUTH_TESTS_MIGRATE=True" 201 | 202 | settings.INSTALLED_APPS = ( 203 | 'django.contrib.sites', 204 | 'django.contrib.messages', 205 | 'django.contrib.staticfiles', 206 | 'south', 207 | ) 208 | settings.SOUTH_TESTS_MIGRATE = True 209 | 210 | context.runner.migrate_to_south_if_needed() 211 | 212 | call_command.assert_called_once_with('migrate') 213 | 214 | 215 | @mock.patch.object(management, 'call_command') 216 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 217 | def test_doesnt_migrate_without_south_on_installed_apps(context, call_command): 218 | u"migrate_to_south_if_needed doesn't migrate is south is not installed" 219 | 220 | msg = "call_command('migrate') is being called even without " \ 221 | "'south' on settings.INSTALLED_APPS" 222 | 223 | call_command.side_effect = AssertionError(msg) 224 | 225 | settings.INSTALLED_APPS = ( 226 | 'django.contrib.sites', 227 | 'django.contrib.messages', 228 | 'django.contrib.staticfiles', 229 | ) 230 | settings.SOUTH_TESTS_MIGRATE = True 231 | 232 | context.runner.migrate_to_south_if_needed() 233 | 234 | 235 | @mock.patch.object(management, 'call_command') 236 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 237 | def test_doesnt_migrate_without_south_tests_migrate(context, call_command): 238 | u"do not migrate if settings.SOUTH_TESTS_MIGRATE is False" 239 | 240 | msg = "call_command('migrate') is being called even with " \ 241 | "settings.SOUTH_TESTS_MIGRATE=False" 242 | 243 | call_command.side_effect = AssertionError(msg) 244 | 245 | settings.INSTALLED_APPS = ( 246 | 'django.contrib.sites', 247 | 'django.contrib.messages', 248 | 'django.contrib.staticfiles', 249 | 'south', 250 | ) 251 | settings.SOUTH_TESTS_MIGRATE = False 252 | 253 | context.runner.migrate_to_south_if_needed() 254 | 255 | 256 | @mock.patch.object(os.path, 'exists') 257 | @mock.patch.object(imp, 'load_module') 258 | @mock.patch.object(imp, 'find_module') 259 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 260 | def test_get_paths_for_imports_the_module_and_returns_its_path(context, 261 | find_module, 262 | load_module, 263 | exists): 264 | u"get_paths_for retrieves the module dirname" 265 | 266 | module_mock = mock.Mock() 267 | module_mock.__file__ = '/path/to/file.py' 268 | 269 | find_module.return_value = ('file', 'pathname', 'description') 270 | load_module.return_value = module_mock 271 | exists.return_value = True 272 | 273 | expected_path = context.runner.get_paths_for(['bazfoobar']) 274 | assert that(expected_path).equals(['/path/to']) 275 | 276 | find_module.assert_called_once_with('bazfoobar') 277 | load_module.assert_called_once_with( 278 | 'bazfoobar', 'file', 'pathname', 'description', 279 | ) 280 | 281 | 282 | @mock.patch.object(os.path, 'exists') 283 | @mock.patch.object(imp, 'load_module') 284 | @mock.patch.object(imp, 'find_module') 285 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 286 | def test_get_paths_appends_more_paths(context, find_module, load_module, 287 | exists): 288 | u"get_paths_for retrieves the module dirname and appends stuff" 289 | 290 | module_mock = mock.Mock() 291 | module_mock.__file__ = '/path/to/file.py' 292 | 293 | find_module.return_value = ('file', 'pathname', 'description') 294 | load_module.return_value = module_mock 295 | exists.return_value = True 296 | 297 | expected_path = context.runner.get_paths_for( 298 | ['bazfoobar'], 299 | appending=['one', 'more', 'place'], 300 | ) 301 | assert that(expected_path).equals(['/path/to/one/more/place']) 302 | 303 | find_module.assert_called_once_with('bazfoobar') 304 | load_module.assert_called_once_with( 305 | 'bazfoobar', 'file', 'pathname', 'description', 306 | ) 307 | 308 | 309 | @mock.patch.object(os.path, 'exists') 310 | @mock.patch.object(imp, 'load_module') 311 | @mock.patch.object(imp, 'find_module') 312 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 313 | def test_get_paths_ignore_paths_that_doesnt_exist(context, 314 | find_module, 315 | load_module, 316 | exists): 317 | u"get_paths_for ignore paths that doesn't exist" 318 | 319 | module_mock = mock.Mock() 320 | module_mock.__file__ = '/path/to/file.py' 321 | 322 | find_module.return_value = ('file', 'pathname', 'description') 323 | load_module.return_value = module_mock 324 | exists.return_value = False 325 | 326 | expected_path = context.runner.get_paths_for( 327 | ['bazfoobar'], 328 | appending=['one', 'more', 'place'], 329 | ) 330 | assert that(expected_path).equals([]) 331 | 332 | find_module.assert_called_once_with('bazfoobar') 333 | load_module.assert_called_once_with( 334 | 'bazfoobar', 'file', 'pathname', 'description', 335 | ) 336 | 337 | 338 | @mock.patch.object(nose, 'run') 339 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 340 | def test_run_tests_simple_with_labels(context, nose_run): 341 | u"ability to run tests just with labels" 342 | 343 | context.runner.get_nose_argv = mock.Mock() 344 | context.runner.get_nose_argv.return_value = ['i', 'am', 'the', 'argv'] 345 | 346 | context.runner.get_paths_for = mock.Mock() 347 | context.runner.get_paths_for.return_value = [ 348 | '/path/to/app', 349 | '/and/another/4/labels', 350 | ] 351 | 352 | context.runner.setup_test_environment = mock.Mock() 353 | context.runner.teardown_test_environment = mock.Mock() 354 | 355 | context.runner.setup_databases = mock.Mock() 356 | context.runner.setup_databases.return_value = 'input 4 teardown databases' 357 | context.runner.teardown_databases = mock.Mock() 358 | 359 | context.runner.migrate_to_south_if_needed = mock.Mock() 360 | 361 | nose_run.return_value = 0 362 | context.runner.run_tests(['app', 'labels']) 363 | 364 | context.runner.get_nose_argv.assert_called_once_with( 365 | covered_package_names=['app', 'labels'], 366 | ) 367 | context.runner.get_paths_for.assert_called_once_with( 368 | ['app', 'labels'], 369 | appending=['tests'], 370 | ) 371 | nose_run.assert_called_once_with(argv=[ 372 | 'i', 'am', 'the', 'argv', 373 | '/path/to/app', 374 | '/and/another/4/labels', 375 | ]) 376 | context.runner.teardown_databases.assert_called_once_with( 377 | 'input 4 teardown databases', 378 | ) 379 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 380 | 381 | 382 | @mock.patch.object(nose, 'run') 383 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 384 | def test_run_without_labels_gets_the_installed_apps(context, nose_run): 385 | u"ability to run tests without labels will look for INSTALLED_APPS" 386 | 387 | context.runner.get_apps = mock.Mock() 388 | context.runner.get_apps.return_value = ['app1', 'app_two'] 389 | 390 | context.runner.get_nose_argv = mock.Mock() 391 | context.runner.get_nose_argv.return_value = ['i', 'am', 'the', 'argv'] 392 | 393 | context.runner.get_paths_for = mock.Mock() 394 | context.runner.get_paths_for.return_value = [ 395 | '/path/to/app1/tests', 396 | '/and/another/path/to/app_two/tests', 397 | ] 398 | 399 | context.runner.setup_test_environment = mock.Mock() 400 | context.runner.teardown_test_environment = mock.Mock() 401 | 402 | context.runner.setup_databases = mock.Mock() 403 | context.runner.setup_databases.return_value = 'input 4 teardown databases' 404 | context.runner.teardown_databases = mock.Mock() 405 | 406 | context.runner.migrate_to_south_if_needed = mock.Mock() 407 | 408 | nose_run.return_value = 0 409 | context.runner.run_tests([]) 410 | 411 | context.runner.get_nose_argv.assert_called_once_with( 412 | covered_package_names=['app1', 'app_two'], 413 | ) 414 | context.runner.get_paths_for.assert_called_once_with( 415 | ['app1', 'app_two'], 416 | appending=['tests'], 417 | ) 418 | nose_run.assert_called_once_with(argv=[ 419 | 'i', 'am', 'the', 'argv', 420 | '/path/to/app1/tests', 421 | '/and/another/path/to/app_two/tests', 422 | ]) 423 | context.runner.teardown_databases.assert_called_once_with( 424 | 'input 4 teardown databases', 425 | ) 426 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 427 | 428 | 429 | @mock.patch.object(nose, 'run') 430 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 431 | def test_when_nose_run_fails(context, nose_run): 432 | u"testing when the nose.run fails" 433 | 434 | context.runner.get_apps = mock.Mock() 435 | context.runner.get_apps.return_value = ['app1', 'app_two'] 436 | 437 | context.runner.get_nose_argv = mock.Mock() 438 | context.runner.get_nose_argv.return_value = ['i', 'am', 'the', 'argv'] 439 | 440 | context.runner.get_paths_for = mock.Mock() 441 | context.runner.get_paths_for.return_value = [ 442 | '/path/to/app1/tests', 443 | '/and/another/path/to/app_two/tests', 444 | ] 445 | 446 | context.runner.setup_test_environment = mock.Mock() 447 | context.runner.teardown_test_environment = mock.Mock() 448 | 449 | context.runner.setup_databases = mock.Mock() 450 | context.runner.setup_databases.return_value = 'input 4 teardown databases' 451 | context.runner.teardown_databases = mock.Mock() 452 | 453 | context.runner.migrate_to_south_if_needed = mock.Mock() 454 | 455 | nose_run.return_value = 1 456 | context.runner.run_tests([]) 457 | 458 | context.runner.get_nose_argv.assert_called_once_with( 459 | covered_package_names=['app1', 'app_two'], 460 | ) 461 | context.runner.get_paths_for.assert_called_once_with( 462 | ['app1', 'app_two'], 463 | appending=['tests'], 464 | ) 465 | nose_run.assert_called_once_with(argv=[ 466 | 'i', 'am', 'the', 'argv', 467 | '/path/to/app1/tests', 468 | '/and/another/path/to/app_two/tests', 469 | ]) 470 | context.runner.teardown_databases.assert_called_once_with( 471 | 'input 4 teardown databases', 472 | ) 473 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 474 | 475 | 476 | @mock.patch.object(nose, 'run') 477 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 478 | def test_running_unit_tests_without_app_labels(context, nose_run): 479 | u"running with --unit without app labels won't touch the database at all" 480 | 481 | context.options['is_unit'] = True 482 | 483 | context.runner.get_apps = mock.Mock() 484 | context.runner.get_apps.return_value = ['john', 'doe'] 485 | 486 | context.runner.get_nose_argv = mock.Mock() 487 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 488 | 489 | context.runner.get_paths_for = mock.Mock() 490 | context.runner.get_paths_for.return_value = [ 491 | '/apps/john/tests/unit', 492 | '/apps/doe/tests/unit', 493 | ] 494 | 495 | context.runner.setup_test_environment = mock.Mock() 496 | context.runner.teardown_test_environment = mock.Mock() 497 | 498 | context.runner.setup_databases = mock.Mock() 499 | context.runner.teardown_databases = mock.Mock() 500 | context.runner.migrate_to_south_if_needed = mock.Mock() 501 | 502 | nose_run.return_value = 0 503 | context.runner.run_tests([]) 504 | 505 | context.runner.get_nose_argv.assert_called_once_with( 506 | covered_package_names=['john', 'doe'], 507 | ) 508 | context.runner.get_paths_for.assert_called_once_with( 509 | ['john', 'doe'], 510 | appending=['tests', 'unit'], 511 | ) 512 | nose_run.assert_called_once_with(argv=[ 513 | 'nose', 'argv', 514 | '/apps/john/tests/unit', 515 | '/apps/doe/tests/unit', 516 | ]) 517 | 518 | assert that(context.runner.setup_databases.call_count).equals(0) 519 | assert that(context.runner.teardown_databases.call_count).equals(0) 520 | assert that(context.runner.migrate_to_south_if_needed.call_count).equals(0) 521 | 522 | 523 | @mock.patch.object(nose, 'run') 524 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 525 | def test_running_unit_n_functional_without_labels(context, nose_run): 526 | u"if get --unit but also --functional then it should use a test database" 527 | 528 | context.options['is_unit'] = True 529 | context.options['is_functional'] = True 530 | 531 | context.runner.get_apps = mock.Mock() 532 | context.runner.get_apps.return_value = ['john', 'doe'] 533 | 534 | context.runner.get_nose_argv = mock.Mock() 535 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 536 | 537 | context.runner.get_paths_for = mock.Mock() 538 | 539 | expected_kinds = ['unit', 'functional'] 540 | 541 | def mock_get_paths_for(names, appending): 542 | k = expected_kinds.pop(0) 543 | assert that(names).is_a(list) 544 | assert that(appending).is_a(list) 545 | assert that(names).equals(['john', 'doe']) 546 | assert that(appending).equals(['tests', k]) 547 | return [ 548 | '/apps/john/tests/%s' % k, 549 | '/apps/doe/tests/%s' % k, 550 | ] 551 | 552 | context.runner.get_paths_for.side_effect = mock_get_paths_for 553 | 554 | context.runner.setup_test_environment = mock.Mock() 555 | context.runner.teardown_test_environment = mock.Mock() 556 | 557 | context.runner.setup_databases = mock.Mock() 558 | context.runner.setup_databases.return_value = "TEST DB CONFIG" 559 | 560 | context.runner.teardown_databases = mock.Mock() 561 | context.runner.migrate_to_south_if_needed = mock.Mock() 562 | 563 | nose_run.return_value = 0 564 | context.runner.run_tests([]) 565 | 566 | context.runner.get_nose_argv.assert_called_once_with( 567 | covered_package_names=['john', 'doe'], 568 | ) 569 | 570 | get_paths_for = context.runner.get_paths_for 571 | 572 | assert that(get_paths_for.call_count).equals(2) 573 | 574 | get_paths_for.assert_has_calls([ 575 | mock.call(['john', 'doe'], appending=['tests', 'unit']), 576 | mock.call(['john', 'doe'], appending=['tests', 'functional']), 577 | ]) 578 | 579 | nose_run.assert_called_once_with(argv=[ 580 | 'nose', 'argv', 581 | '/apps/john/tests/unit', 582 | '/apps/doe/tests/unit', 583 | '/apps/john/tests/functional', 584 | '/apps/doe/tests/functional', 585 | ]) 586 | 587 | context.runner.setup_databases.assert_called_once_with() 588 | context.runner.teardown_databases.assert_called_once_with("TEST DB CONFIG") 589 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 590 | 591 | 592 | @mock.patch.object(nose, 'run') 593 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 594 | def test_running_unit_n_integration_without_labels(context, nose_run): 595 | u"if get --unit but also --integration then it should use a test database" 596 | 597 | context.options['is_unit'] = True 598 | context.options['is_integration'] = True 599 | 600 | context.runner.get_apps = mock.Mock() 601 | context.runner.get_apps.return_value = ['john', 'doe'] 602 | 603 | context.runner.get_nose_argv = mock.Mock() 604 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 605 | 606 | context.runner.get_paths_for = mock.Mock() 607 | 608 | expected_kinds = ['unit', 'integration'] 609 | 610 | def mock_get_paths_for(names, appending): 611 | k = expected_kinds.pop(0) 612 | assert that(names).is_a(list) 613 | assert that(appending).is_a(list) 614 | assert that(names).equals(['john', 'doe']) 615 | assert that(appending).equals(['tests', k]) 616 | return [ 617 | '/apps/john/tests/%s' % k, 618 | '/apps/doe/tests/%s' % k, 619 | ] 620 | 621 | context.runner.get_paths_for.side_effect = mock_get_paths_for 622 | 623 | context.runner.setup_test_environment = mock.Mock() 624 | context.runner.teardown_test_environment = mock.Mock() 625 | 626 | context.runner.setup_databases = mock.Mock() 627 | context.runner.setup_databases.return_value = "TEST DB CONFIG" 628 | 629 | context.runner.teardown_databases = mock.Mock() 630 | context.runner.migrate_to_south_if_needed = mock.Mock() 631 | 632 | nose_run.return_value = 0 633 | context.runner.run_tests([]) 634 | 635 | context.runner.get_nose_argv.assert_called_once_with( 636 | covered_package_names=['john', 'doe'], 637 | ) 638 | 639 | get_paths_for = context.runner.get_paths_for 640 | 641 | get_paths_for.call_count.should.equal(2) 642 | 643 | get_paths_for.assert_has_calls([ 644 | mock.call(['john', 'doe'], appending=['tests', 'unit']), 645 | mock.call(['john', 'doe'], appending=['tests', 'integration']), 646 | ]) 647 | 648 | nose_run.assert_called_once_with(argv=[ 649 | 'nose', 'argv', 650 | '/apps/john/tests/unit', 651 | '/apps/doe/tests/unit', 652 | '/apps/john/tests/integration', 653 | '/apps/doe/tests/integration', 654 | ]) 655 | 656 | context.runner.setup_databases.assert_called_once_with() 657 | context.runner.teardown_databases.assert_called_once_with("TEST DB CONFIG") 658 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 659 | 660 | 661 | @mock.patch.object(nose, 'run') 662 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 663 | def test_running_unit_func_n_integration_without_labels(context, nose_run): 664 | u"if get --unit --functional and --integration" 665 | 666 | context.options['is_unit'] = True 667 | context.options['is_functional'] = True 668 | context.options['is_integration'] = True 669 | 670 | context.runner.get_apps = mock.Mock() 671 | context.runner.get_apps.return_value = ['john', 'doe'] 672 | 673 | context.runner.get_nose_argv = mock.Mock() 674 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 675 | 676 | context.runner.get_paths_for = mock.Mock() 677 | 678 | expected_kinds = ['unit', 'functional', 'integration'] 679 | 680 | def mock_get_paths_for(names, appending): 681 | k = expected_kinds.pop(0) 682 | assert that(names).is_a(list) 683 | assert that(appending).is_a(list) 684 | assert that(names).equals(['john', 'doe']) 685 | assert that(appending).equals(['tests', k]) 686 | return [ 687 | '/apps/john/tests/%s' % k, 688 | '/apps/doe/tests/%s' % k, 689 | ] 690 | 691 | context.runner.get_paths_for.side_effect = mock_get_paths_for 692 | 693 | context.runner.setup_test_environment = mock.Mock() 694 | context.runner.teardown_test_environment = mock.Mock() 695 | 696 | context.runner.setup_databases = mock.Mock() 697 | context.runner.setup_databases.return_value = "TEST DB CONFIG" 698 | 699 | context.runner.teardown_databases = mock.Mock() 700 | context.runner.migrate_to_south_if_needed = mock.Mock() 701 | 702 | nose_run.return_value = 0 703 | context.runner.run_tests([]) 704 | 705 | context.runner.get_nose_argv.assert_called_once_with( 706 | covered_package_names=['john', 'doe'], 707 | ) 708 | 709 | get_paths_for = context.runner.get_paths_for 710 | 711 | assert that(get_paths_for.call_count).equals(3) 712 | 713 | get_paths_for.assert_has_calls([ 714 | mock.call(['john', 'doe'], appending=['tests', 'unit']), 715 | mock.call(['john', 'doe'], appending=['tests', 'functional']), 716 | mock.call(['john', 'doe'], appending=['tests', 'integration']), 717 | ]) 718 | 719 | nose_run.assert_called_once_with(argv=[ 720 | 'nose', 'argv', 721 | '/apps/john/tests/unit', 722 | '/apps/doe/tests/unit', 723 | '/apps/john/tests/functional', 724 | '/apps/doe/tests/functional', 725 | '/apps/john/tests/integration', 726 | '/apps/doe/tests/integration', 727 | ]) 728 | 729 | context.runner.setup_databases.assert_called_once_with() 730 | context.runner.teardown_databases.assert_called_once_with("TEST DB CONFIG") 731 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 732 | 733 | 734 | @mock.patch.object(os.path, 'exists') 735 | @mock.patch.object(imp, 'load_module') 736 | @mock.patch.object(imp, 'find_module') 737 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 738 | def test_get_paths_for_accept_paths_as_parameter_checking_if_exists( 739 | context, 740 | find_module, 741 | load_module, 742 | exists): 743 | u"get_paths_for also takes paths, and check if exists" 744 | 745 | find_module.side_effect = ImportError('no module named /some/path') 746 | 747 | exists.side_effect = lambda x: x == '/path/to/file.py' 748 | 749 | expected_paths = context.runner.get_paths_for( 750 | ['/path/to/file.py'], 751 | appending=['more', 'members'], 752 | ) 753 | 754 | assert that(expected_paths).equals(['/path/to/file.py']) 755 | 756 | find_module.assert_called_once_with('/path/to/file.py') 757 | assert that(load_module.call_count).equals(0) 758 | 759 | 760 | @mock.patch.object(os.path, 'exists') 761 | @mock.patch.object(imp, 'load_module') 762 | @mock.patch.object(imp, 'find_module') 763 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 764 | def test_get_paths_for_never_return_duplicates( 765 | context, 766 | find_module, 767 | load_module, 768 | exists): 769 | u"get_paths_for never return duplicates" 770 | 771 | module_mock = mock.Mock() 772 | module_mock.__file__ = '/path/to/file.py' 773 | 774 | find_module.return_value = ('file', 'pathname', 'description') 775 | load_module.return_value = module_mock 776 | 777 | exists.return_value = True 778 | 779 | expected_paths = context.runner.get_paths_for( 780 | ['/path/to/file.py', '/path/to/file.py'], 781 | appending=['more', 'members'], 782 | ) 783 | 784 | assert that(expected_paths).equals(['/path/to/more/members']) 785 | 786 | 787 | @mock.patch.object(nose, 'run') 788 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 789 | def test_nose_is_called_with_unique_args(context, nose_run): 790 | u"testing when the nose.run fails" 791 | 792 | context.runner.get_apps = mock.Mock() 793 | context.runner.get_apps.return_value = ['app1', 'app_two'] 794 | 795 | context.runner.get_nose_argv = mock.Mock() 796 | context.runner.get_nose_argv.return_value = [ 797 | 'repeated', 798 | 'repeated', 799 | 'repeated', 800 | ] 801 | 802 | context.runner.get_paths_for = mock.Mock() 803 | context.runner.get_paths_for.return_value = [] 804 | 805 | context.runner.setup_test_environment = mock.Mock() 806 | context.runner.teardown_test_environment = mock.Mock() 807 | 808 | context.runner.setup_databases = mock.Mock() 809 | context.runner.setup_databases.return_value = 'input 4 teardown databases' 810 | context.runner.teardown_databases = mock.Mock() 811 | 812 | context.runner.migrate_to_south_if_needed = mock.Mock() 813 | 814 | nose_run.return_value = 0 815 | context.runner.run_tests([]) 816 | 817 | context.runner.get_nose_argv.assert_called_once_with( 818 | covered_package_names=['app1', 'app_two'], 819 | ) 820 | context.runner.get_paths_for.assert_called_once_with( 821 | ['app1', 'app_two'], 822 | appending=['tests'], 823 | ) 824 | nose_run.assert_called_once_with(argv=[ 825 | 'repeated', 826 | ]) 827 | context.runner.teardown_databases.assert_called_once_with( 828 | 'input 4 teardown databases', 829 | ) 830 | context.runner.migrate_to_south_if_needed.assert_called_once_with() 831 | 832 | 833 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 834 | def test_get_argv_options_simple(context): 835 | u"Nose should parse sys.argv" 836 | sys.argv = ['./manage.py', 'test'] 837 | runner = Nose() 838 | 839 | opts = runner.get_argv_options() 840 | assert that(opts['is_unit']).equals(False) 841 | assert that(opts['is_functional']).equals(False) 842 | assert that(opts['is_integration']).equals(False) 843 | 844 | 845 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 846 | def test_get_argv_options_unit(context): 847 | u"Nose should parse sys.argv and figure out whether to run as unit" 848 | sys.argv = ['./manage.py', 'test', '--unit'] 849 | runner = Nose() 850 | 851 | opts = runner.get_argv_options() 852 | assert that(opts['is_unit']).equals(True) 853 | assert that(opts['is_functional']).equals(False) 854 | assert that(opts['is_integration']).equals(False) 855 | 856 | 857 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 858 | def test_get_argv_options_functional(context): 859 | u"Nose should parse sys.argv and figure out whether to run as functional" 860 | sys.argv = ['./manage.py', 'test', '--functional'] 861 | runner = Nose() 862 | 863 | opts = runner.get_argv_options() 864 | assert that(opts['is_unit']).equals(False) 865 | assert that(opts['is_functional']).equals(True) 866 | assert that(opts['is_integration']).equals(False) 867 | 868 | 869 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 870 | def test_get_argv_options_integration(context): 871 | u"Nose should parse sys.argv and figure out whether to run as integration" 872 | sys.argv = ['./manage.py', 'test', '--integration'] 873 | runner = Nose() 874 | 875 | opts = runner.get_argv_options() 876 | assert that(opts['is_unit']).equals(False) 877 | assert that(opts['is_functional']).equals(False) 878 | assert that(opts['is_integration']).equals(True) 879 | 880 | 881 | @mock.patch.object(management, 'get_commands') 882 | @mock.patch.object(management, 'load_command_class') 883 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 884 | def test_should_try_loading_test_cmd_class(context, 885 | load_command_class, 886 | get_commands): 887 | u"Nose should try loading the 'test' command class" 888 | sys.argv = ['./manage.py', 'test', 889 | '--unit', '--functional', '--integration'] 890 | runner = Nose() 891 | 892 | command_mock = mock.Mock() 893 | get_commands.return_value = {'test': 'string to load'} 894 | load_command_class.return_value = command_mock 895 | command_mock.option_list = [] 896 | 897 | opts = runner.get_argv_options() 898 | assert that(opts['is_unit']).equals(True) 899 | assert that(opts['is_functional']).equals(True) 900 | assert that(opts['is_integration']).equals(True) 901 | 902 | get_commands.assert_called_once_with() 903 | load_command_class.assert_called_once_with('django.core', 'test') 904 | 905 | 906 | @mock.patch.object(imp, 'find_module') 907 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 908 | def test_should_ignore_packages_that_are_not_packages(context, find_module): 909 | u"Nose.get_nose_argv support extending base args thru settings" 910 | 911 | find_module.side_effect = ImportError('no module called some_module') 912 | 913 | assert that(context.runner.get_nose_argv()).equals([ 914 | 'nosetests', '-s', '--verbosity=1', '--exe', 915 | '--logging-clear-handlers', 916 | '--cover-inclusive', '--cover-erase', 917 | ]) 918 | 919 | 920 | @mock.patch.object(nose, 'run') 921 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 922 | def test_setting_NO_DATABASE_has_priority_functional(context, nose_run): 923 | u"it should respect when user doesn't want a test db, even for functional" 924 | 925 | settings.UNCLEBOB_NO_DATABASE = True 926 | context.options['is_functional'] = True 927 | 928 | context.runner.get_apps = mock.Mock() 929 | context.runner.get_apps.return_value = ['john', 'doe'] 930 | 931 | context.runner.get_nose_argv = mock.Mock() 932 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 933 | 934 | context.runner.get_paths_for = mock.Mock() 935 | context.runner.get_paths_for.return_value = [ 936 | '/apps/john/tests/unit', 937 | '/apps/doe/tests/unit', 938 | ] 939 | 940 | context.runner.setup_test_environment = mock.Mock() 941 | context.runner.teardown_test_environment = mock.Mock() 942 | 943 | context.runner.setup_databases = mock.Mock() 944 | context.runner.teardown_databases = mock.Mock() 945 | context.runner.migrate_to_south_if_needed = mock.Mock() 946 | 947 | nose_run.return_value = 0 948 | context.runner.run_tests([]) 949 | 950 | context.runner.get_nose_argv.assert_called_once_with( 951 | covered_package_names=['john', 'doe'], 952 | ) 953 | context.runner.get_paths_for.assert_called_once_with( 954 | ['john', 'doe'], 955 | appending=['tests', 'functional'], 956 | ) 957 | nose_run.assert_called_once_with(argv=[ 958 | 'nose', 'argv', 959 | '/apps/john/tests/unit', 960 | '/apps/doe/tests/unit', 961 | ]) 962 | 963 | assert context.runner.setup_databases.call_count == 0, \ 964 | "setup_databases was called when it shouldn't" 965 | 966 | assert context.runner.teardown_databases.call_count == 0, \ 967 | "teardown_databases was called when it shouldn't" 968 | 969 | assert context.runner.migrate_to_south_if_needed.call_count == 0, \ 970 | "migrate_to_south_if_needed was called when it shouldn't" 971 | 972 | 973 | @mock.patch.object(nose, 'run') 974 | @that_with_context(prepare_stuff, and_cleanup_the_mess) 975 | def test_setting_NO_DATABASE_has_priority_integration(context, nose_run): 976 | u"it should respect when user doesn't want a test db, even for integration" 977 | 978 | settings.UNCLEBOB_NO_DATABASE = True 979 | context.options['is_integration'] = True 980 | 981 | context.runner.get_apps = mock.Mock() 982 | context.runner.get_apps.return_value = ['john', 'doe'] 983 | 984 | context.runner.get_nose_argv = mock.Mock() 985 | context.runner.get_nose_argv.return_value = ['nose', 'argv'] 986 | 987 | context.runner.get_paths_for = mock.Mock() 988 | context.runner.get_paths_for.return_value = [ 989 | '/apps/john/tests/unit', 990 | '/apps/doe/tests/unit', 991 | ] 992 | 993 | context.runner.setup_test_environment = mock.Mock() 994 | context.runner.teardown_test_environment = mock.Mock() 995 | 996 | context.runner.setup_databases = mock.Mock() 997 | context.runner.teardown_databases = mock.Mock() 998 | context.runner.migrate_to_south_if_needed = mock.Mock() 999 | 1000 | nose_run.return_value = 0 1001 | context.runner.run_tests([]) 1002 | 1003 | context.runner.get_nose_argv.assert_called_once_with( 1004 | covered_package_names=['john', 'doe'], 1005 | ) 1006 | context.runner.get_paths_for.assert_called_once_with( 1007 | ['john', 'doe'], 1008 | appending=['tests', 'integration'], 1009 | ) 1010 | nose_run.assert_called_once_with(argv=[ 1011 | 'nose', 'argv', 1012 | '/apps/john/tests/unit', 1013 | '/apps/doe/tests/unit', 1014 | ]) 1015 | 1016 | assert context.runner.setup_databases.call_count == 0, \ 1017 | "setup_databases was called when it shouldn't" 1018 | 1019 | assert context.runner.teardown_databases.call_count == 0, \ 1020 | "teardown_databases was called when it shouldn't" 1021 | 1022 | assert context.runner.migrate_to_south_if_needed.call_count == 0, \ 1023 | "migrate_to_south_if_needed was called when it shouldn't" 1024 | --------------------------------------------------------------------------------