42
Тестирование Илья Барышев @coagulant Moscow Django Meetup 6 и Django

Тестирование и Django

Embed Size (px)

DESCRIPTION

Когда тестировать, что тестировать, как тестировать, Как ускорить тесты и упростить их написание. Отказываемся от классических фикстур в пользу динамически создаваемых моделей.

Citation preview

Page 1: Тестирование и Django

Тестирование

Илья Барышев@coagulant

Moscow Django Meetup №6

и Django

Page 2: Тестирование и Django

Защита от регрессий

Page 3: Тестирование и Django

Быстрые изменения в коде

Page 4: Тестирование и Django

Меняет подход к написанию кода

Page 5: Тестирование и Django

Пойдёт на пользу вашему проекту

Page 6: Тестирование и Django

Модульное тестирование

Page 7: Тестирование и Django

       def  test_vin_is_valid(self):                valid_vins  =  ('2G1FK1EJ7B9141175',                                            '11111111111111111',)                for  valid_vin  in  valid_vins:                        self.assertEqual(vin_validator(valid_vin),  None)

       def  test_vin_is_invalid(self):                invalid_vins  =  ('abc',  u'M05C0WDJAN60M33TUP6',)                for  invalid_vin  in  invalid_vins:                        self.assertRaises(ValidationError,  

                           vin_validator,  invalid_vin)

Page 8: Тестирование и Django

Модели

Формы

Views?

Контекст-процессоры

Middleware

Template tags, filters

Unittest

Page 9: Тестирование и Django

Тестируйте поведениеА не имплементацию

Page 10: Тестирование и Django

Функциональное тестирование

Page 11: Тестирование и Django

django.test.client.Client

def  testPostAsAuthenticatedUser(self):        data  =  self.getValidData(Article.objects.get(pk=1))        self.client.login(username="normaluser",                                              password="normaluser")        self.response  =  self.client.post("/post/",  data)                self.assertEqual(self.response.status_code,  302)        self.assertEqual(Comment.objects.count(),  1)

Page 12: Тестирование и Django

django.test.сlient.RequestFactory

def  test_post_ok(self):        request  =  RequestFactory().post(reverse('ch_location'),                                                                        {'location_id':  77})        request.cookies  =  {}

       response  =  change_location(request)

       self.assertEqual(response.cookies['LOCATION'].value,  '77')        self.assertEqual(response.status_code,  302)

Page 13: Тестирование и Django

Smoke Testing

Page 14: Тестирование и Django

def  test_password_recovery_smoke(self):        """        Урлы  восстановления  пароля.        Логика  уже  протестирована  в  django-­‐password-­‐reset        """        response_recover  =  self.client.get(reverse('pass_recover'))                self.assertEqual(response_recover.status_code,  200)

               self.assertContains(response_recover,                                                        u'Восстановление  пароля')                self.assertTemplateUsed(response_recover,                                                                'password_reset/recovery_form.html')

Page 15: Тестирование и Django

Как мы тестируем

Page 16: Тестирование и Django

ContiniousIntegration

Page 17: Тестирование и Django

Покрытие важноНо не делайте из него фетиш

Page 18: Тестирование и Django

mockhttp://www.voidspace.org.uk/python/mock/

Page 19: Тестирование и Django

>>>  real.method(3,  4,  5,  key='value')

>>>  my_mock.calledTrue

>>>  my_mock.call_count1

>>>  mock.method.assert_called_with(3,  4,  5)Traceback  (most  recent  call  last):    ...AssertionError:  Expected  call:  method(3,  4,  5)Actual  call:  method(3,  4,  5,  key='value')

>>>  real  =  SomeClass()>>>  my_mock  =  MagicMock(name='method')>>>  real.method  =  my_mock

Page 20: Тестирование и Django

@patch('twitter.Api')def  test_twitter_tag_simple_mock(self,  ApiMock):        api_instance  =  ApiMock.return_value        api_instance.GetUserTimeline.return_value  =  SOME_JSON

       output,  context  =  render_template("""{%  load  twitter_tag  %}  {%  get_tweets  for  "jresig"  as  tweets  %}""")

       api_instance.GetUserTimeline.assert_called_with(                screen_name='jresig',                  include_rts=True,                  include_entities=True)

Page 21: Тестирование и Django

from  mock  import  patchfrom  django.conf  import  settings

@patch.multiple(settings,  APPEND_SLASH=True,                                MIDDLEWARE_CLASSES=(common_middleware,))def  test_flatpage_doesnt_require_trailing_slash(self):        form  =  FlatpageForm(data=dict(url='/no_trailing_slash',                                                                      **self.form_data))        self.assertTrue(form.is_valid())

Page 22: Тестирование и Django

from  django.test.utils  import  override_settings

@override_settings(        APPEND_SLASH=False,          MIDDLEWARE_CLASSES=(common_middleware,))def  test_flatpage_doesnt_require_trailing_slash(self):        form  =  FlatpageForm(data=dict(url='/no_trailing_slash',                                                                      **self.form_data))        self.assertTrue(form.is_valid())

Page 23: Тестирование и Django

Фикстуры

Page 24: Тестирование и Django

Обычный тест с фикстурами

[ { "model": "docs.documentrelease", "pk": 1, "fields": { "lang": "en", "version": "dev", "scm": "svn", "scm_url": "http://code.djangoproject.com/svn/django/trunk/docs", "is_default": false } }, { "model": "docs.documentrelease", "pk": 2, "fields": { "lang": "en", "version": "1.0", "scm": "svn", "scm_url": "http://code.djangoproject.com/svn/django/branches/releases/1.0.X/docs", "is_default": false } }, { "model": "docs.documentrelease", "pk": 3, "fields": { "lang": "en", "version": "1.1", "scm": "svn", "scm_url": "http://code.djangoproject.com/svn/django/branches/releases/1.1.X/docs", "is_default": false } }, { "model": "docs.documentrelease", "pk": 4, "fields": { "lang": "en", "version": "1.2", "scm": "svn", "scm_url": "http://code.djangoproject.com/svn/django/branches/releases/1.2.X/docs", "is_default": false } } { "model": "docs.documentrelease", "pk": 5, "fields": { "lang": "en", "version": "1.3", "scm": "svn", "scm_url": "http://code.djangoproject.com/svn/django/trunk/docs", "is_default": true } }]

Page 25: Тестирование и Django

django-­‐anyhttps://github.com/kmmbvnr/django-­‐any

from  django_any  import  any_model

class  TestMyShop(TestCase):        def  test_order_updates_user_account(self):                account  =  any_model(Account,  amount=25,  

                           user__is_active=True)                order  =  any_model(Order,  user=account.user,  

                     amount=10)                order.proceed()

               account  =  Account.objects.get(pk=account.pk)                self.assertEquals(15,  account.amount)

Page 26: Тестирование и Django

factory_boyhttps://github.com/dnerdy/factory_boy

Page 27: Тестирование и Django

import  factoryfrom  models  import  MyUser

class  UserFactory(factory.Factory):        FACTORY_FOR  =  MyUser        first_name  =  'John'        last_name  =  'Doe'        admin  =  False

Page 28: Тестирование и Django

#  Экземпляр  User,  не  сохранённый  в  базуuser  =  UserFactory.build()

#  Инстанс,  сохранённый  в  базуuser  =  UserFactory.create()

#  Создаём  инстанс  с  конкретыми  значениямиuser  =  UserFactory.create(name=u'Василий',  age=25)

Page 29: Тестирование и Django

class  UserFactory(factory.Factory):        first_name  =  'Vasily'        last_name  =  'Pupkin'        email  =  factory.LazyAttribute(

lambda  u:  '{0}.{1}@example.com'.format(u.first_name,  u.last_name).lower())

>>>  UserFactory().email'[email protected]'

Page 30: Тестирование и Django

class  UserWithEmailFactory(UserFactory):        email  =  factory.Sequence(

lambda  n:  'person{0}@example.com'.format(n))

>>>  UserFactory().email'[email protected]'

>>>  UserFactory().email    '[email protected]'

Page 31: Тестирование и Django

Django test runnerSUCKS

Page 32: Тестирование и Django

INSTALLED_APPS  =  (        ...

       #3rd-­‐party  apps        'south',        'sorl.thumbnail',        'pytils',        'pymorphy',                  'compressor',        'django_nose',        'django_geoip',        'mptt',        'widget_tweaks',        'guardian',                ...

Несколько сотен тестов

Page 33: Тестирование и Django

/tests        __init__.py

test_archive.py        test_blog_model.py        test_modified.py        test_post_model.py        test_redactor.py        test_views.py        test_cross_post.py

#  -­‐*-­‐  coding:  utf-­‐8  -­‐*-­‐

from  test_archive  import  *from  test_blog_model  import  *from  test_modified  import  *from  test_post_model  import  *from  test_redactor  import  *from  test_views  import  *from  test_cross_post  import  *

Page 34: Тестирование и Django

django-­‐nose

https://github.com/jbalogh/django-­‐nose

Page 35: Тестирование и Django

$  pip  install  django-­‐nose

#  settings.py  INSTALLED_APPS  =  (        ...        'django_nose',        ...)

TEST_RUNNER  =  'django_nose.NoseTestSuiteRunner'

Page 36: Тестирование и Django

$  manage.py  test  -­‐-­‐with-­‐ids  -­‐-­‐failed

$  manage.py  -­‐-­‐pdb

$  manage.py  -­‐-­‐pdb-­‐failures

$  manage.py  test  apps.comments.tests

$  manage.py  test  apps.comments.tests:BlogTestCase

$  manage.py  test  apps.comments.tests:BlogTestCase.test_index

$  manage.py  test

Page 37: Тестирование и Django

from  nose.plugins.attrib  import  attr

@attr(speed='slow',  priority=1)def  test_big_download():        import  urllib        #  commence  slowness..

$  nosetests  -­‐a  speed=slow

$  nosetests  -­‐a  '!slow'

$  nosetests  -­‐A  "(priority  >  5)  and  not  slow"

Page 38: Тестирование и Django

TESTING

TESTING

Page 39: Тестирование и Django

SQLite для быстрых тестовЕсли ваш проект позволяет

Page 40: Тестирование и Django

Параллелим тестыНетрудоёмкое ускоение

Page 41: Тестирование и Django

Секунды

0 100 200 300 400

126

169

326

Ran  337  tests  in  326.664sOK  (SKIP=2)

$  ./manage.py  -­‐-­‐processes=N

1 процесс

2 процесса

3 процесса

Page 42: Тестирование и Django

Спасибо за внимание

[email protected]@coagulant http://blog.futurecolors.ru/