Тестирование и django
Post on 16-Jun-2015
1.262 Views
Preview:
DESCRIPTION
TRANSCRIPT
Тестирование
Илья Барышев@coagulant
Moscow Django Meetup №6
и 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)
Модели
Формы
Views?
Контекст-процессоры
Middleware
Template tags, filters
Unittest
Тестируйте поведениеА не имплементацию
Функциональное тестирование
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)
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)
Smoke Testing
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')
Как мы тестируем
ContiniousIntegration
Покрытие важноНо не делайте из него фетиш
mockhttp://www.voidspace.org.uk/python/mock/
>>> 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
@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)
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())
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())
Фикстуры
Обычный тест с фикстурами
[ { "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 } }]
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)
factory_boyhttps://github.com/dnerdy/factory_boy
import factoryfrom models import MyUser
class UserFactory(factory.Factory): FACTORY_FOR = MyUser first_name = 'John' last_name = 'Doe' admin = False
# Экземпляр User, не сохранённый в базуuser = UserFactory.build()
# Инстанс, сохранённый в базуuser = UserFactory.create()
# Создаём инстанс с конкретыми значениямиuser = UserFactory.create(name=u'Василий', age=25)
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'vasily.pupkin@example.com'
class UserWithEmailFactory(UserFactory): email = factory.Sequence(
lambda n: 'person{0}@example.com'.format(n))
>>> UserFactory().email'person0@example.com'
>>> UserFactory().email 'person1@example.com'
Django test runnerSUCKS
INSTALLED_APPS = ( ...
#3rd-‐party apps 'south', 'sorl.thumbnail', 'pytils', 'pymorphy', 'compressor', 'django_nose', 'django_geoip', 'mptt', 'widget_tweaks', 'guardian', ...
Несколько сотен тестов
/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 *
django-‐nose
https://github.com/jbalogh/django-‐nose
$ pip install django-‐nose
# settings.py INSTALLED_APPS = ( ... 'django_nose', ...)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
$ 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
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"
TESTING
TESTING
SQLite для быстрых тестовЕсли ваш проект позволяет
Параллелим тестыНетрудоёмкое ускоение
Секунды
0 100 200 300 400
126
169
326
Ran 337 tests in 326.664sOK (SKIP=2)
$ ./manage.py -‐-‐processes=N
1 процесс
2 процесса
3 процесса
Спасибо за внимание
baryshev@futurecolors.ru@coagulant http://blog.futurecolors.ru/
top related