Разработка через тестирование в Python и Django #pyconru

4,664 views

Published on

Презентация доклада Ильи Шаляпина и Евгения Генералова в первого Pycon'а в России.

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,664
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
40
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Разработка через тестирование в Python и Django #pyconru

  1. 1. Разработка через тестированиев Python и Django Илья Шаляпин Евгений Генералов
  2. 2. 19 проектов 4 года89299 строк кода50826 строк тестов
  3. 3. Писать тесты или нет?
  4. 4. Пример из жизниПереезд с Ubuntu 8.04 на Ubuntu 12.04Python 2.5 Python 2.7Django 1.3 Django 1.4.0lxml 1.3.6 lxml 2.3.2PIL 1.1.6 PIL 1.1.7... ...
  5. 5. Перезд проекта плотно покрытого тестами
  6. 6. Перезд проекта менее плотно покрытого тестами
  7. 7. Перезд проекта без тестов
  8. 8. Преимущества- Меньше ручной работы- Спокойный рефакторинг- Код легче читать- Быстрое подключение людей к проекту- Тесты являются спецификацией
  9. 9. Недостатки- Затраты на обучение- Дополнительные настроки в проекте- Некоторые тесты сложно писать
  10. 10. TDD вид сбоку
  11. 11. $ pip install unittest2
  12. 12. # test_add.pyimport unittest2class AddTest(unittest2.TestCase): def test_add(self): self.assertEquals(add(1, 1), 2) self.assertEquals(add(5, 2), 7) self.assertEquals(add(-1, -6), -7)if __name__ == __main__: unittest2.main()
  13. 13. # test_add.pyimport unittest2def add(a, b): passclass AddTest(unittest2.TestCase): def test_add(self): self.assertEquals(add(1, 1), 2)if __name__ == __main__: unittest2.main()
  14. 14. Запуск теста$ python test_add.py
  15. 15. $ python test_add.pyF=========================================FAIL: test_add (__main__.AddTest)----------------------------------------------------------------------Traceback (most recent call last): File "test_add.py", line 11, in test_add self.assertEquals(add(1, 1), 2)AssertionError: None != 2----------------------------------------------------------------------Ran 1 test in 0.000sFAILED (failures=1)
  16. 16. # test_add.pyimport unittest2def add(a, b): return a + bclass AddTest(unittest2.TestCase): def test_add(self): self.assertEquals(add(1, 1), 2)if __name__ == __main__: unittest2.main()
  17. 17. $ python test_add.py.-------------------------------------------------Ran 1 test in 0.000sOK
  18. 18. Проект растет - тестов становится много ... ./tests/ ./tests/test_add.py ./tests/test_sub.py ./tests/test_div.py ./tests/test_mul.py ./tests/test_pi.py
  19. 19. Nose - запускалка тестов Устанавливаем nose$ pip install nose Запускаем тесты$ nosetests..--------------------------------------------Ran 100500 tests in 0.219sOK
  20. 20. Инструментыunittest2 django.testflexmock django_nosenose django_webtest
  21. 21. Тестирование в DjangoУстановить приложения $ pip install django_nose $ pip install django_webtestСоздать тестовую конфигурацию testing_settings.py
  22. 22. # testing_settings.pyfrom settings import *DATABASES = { "default": dict( ENGINE = "django.db.backends.sqlite3", NAME = ":memory:", )}INSTALLED_APPS += ( django_nose,)TEST_RUNNER = django_nose.NoseTestSuiteRunner
  23. 23. Запуск тестов в DjangoЗапуск всех тестов в папке ./blog$ manage.py test ./blog --settings project.testing_settingsЗапуск тестов в одном файле$ manage.py test ./blog/test/test_forms.py --settingsproject.testing_settings
  24. 24. Запуск тестов только для одного класса$ manage.py test ./blog/test/test_forms.py:PostFormTest--settings project.testing_settingsЗапуск только одного теста$ manage.py test ./blog/test/test_forms.py:PostFormTest.test_post_from_submit --settings project.testing_settings
  25. 25. Blog tutorial
  26. 26. Тест viewfrom django.test import TestCase, Clientclass HomePageTest(TestCase): def test_homepage_is_available(self): c = Client() response = c.get(/) self.assertEquals(response.status_code, 200)
  27. 27. class HomePageTest(TestCase): def setUp(self): self.posts = [ ] for i in range(20): post = Post.objects.create( title = "Hello %d" % i, ) self.posts.append(post) def test_homepage_contains_posts(self): pass
  28. 28. class HomePageTest(TestCase): def setUp(self): self.posts = [ ] for i in range(20): post = Post.objects.create( title = "Hello %d" % i, ) self.posts.append(post) def test_homepage_contains_posts(self): c = Client() response = c.get(/) self.assertEquals(response.status_code, 200) self.assertIn(self.posts[-1].title, response.content) self.assertIn(self.posts[-2].title, response.content)
  29. 29. class HomePageTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_homepage_contains_posts(self): pass
  30. 30. def home(request): posts = Post.objects.all()[:10] return render(request, home.html, {posts:posts})
  31. 31. from django.db import modelsclass Post(models.Model): picture = models.ImageField( upload_to=posts, blank=True, null=True) title = models.CharField(max_length=255) body = models.CharField(max_length=255) class Meta: ordering = [-id]
  32. 32. Отправка формыclass PostFormTest(TestCase): def test_post_from_submit(self): c = Client() params = {title:Hello Pycon} response = c.post(/posts/add/, params) self.assertEquals(response.status_code, 302) post = Post.objects.get(title=params[title])
  33. 33. Загрузка файловdef test_post_from_submit_with_picture(self): f = open(blog/tests/fixtures/debian-logo.png) params = { picture:f, title:My photo, } response = self.client.post(/posts/add/, params) self.assertEquals(response.status_code, 302) post = Post.objects.get(title=params[title]) self.assertIn(.png, post.picture.path)
  34. 34. $ pip install django_webtest
  35. 35. django_webtest - XPathclass HomePageWebTest(WebTest): def setUp(self): ... def test_homepage_contains_posts(self): response = self.app.get(/) self.assertEquals(response.status_int, 200) titles = response.lxml.xpath( "//*[@class=post-announce]/h2/text()" ) self.assertEquals(titles[0], self.posts[-1].title) self.assertEquals(titles[1], self.posts[-2].title)
  36. 36. django_webtest - формыfrom django_webtest import WebTestclass PostFormWebTest(WebTest): def test_post_from_submit(self): response = self.app.get(/posts/add/) self.assertEquals(response.status_int, 200) form = response.forms[add_post_form] form[title] = Hello Pycon form[body] = Wazzup! response = form.submit().follow() self.assertEquals(response.status_int, 200)
  37. 37. Тесты админкиПочти такие же как тесты других view
  38. 38. class PostAdminTest(TestCase): def setUp(self): self.user = User.objects.create_user( admin, mail@example.com, password ) self.user.is_staff = True self.user.is_superuser = True self.user.save() def test_post_form_submit(self): ...
  39. 39. class PostAdminTest(TestCase): def setUp(self): ... def test_post_form_submit(self): c = Client() c.login(username=admin, password=password) response = c.get(/admin/blog/post/add/) self.assertEquals(response.status_code, 200) params = {title: Hello Pycon, body: Text} response = c.post(/posts/add/, params) self.assertEquals(response.status_code, 302) post = Post.objects.get(title=params[title])
  40. 40. Прочее в Django- Middleware- Template tags, filters- Context processors- тестируются модульными тестами какпростые функции, аналогично спримером 1+1 = 2
  41. 41. Особенности тестов view в Django ---------------------------- middleware ----------------------------- context processors ----------------------------- template ----------------------------- view ----------------------------- models ----------------------------- network
  42. 42. Flexmock- Заменять части объектов и классов- Заменять функции, в том числевстроенные- Создавать объекты заглушки- Проверять ожидания (сколько развызван метод, с какими аргументами)
  43. 43. $ pip install flexmock
  44. 44. from flexmock import flexmockfrom blog.models import Postdef test_home_page_with_flexmock(self): posts = [ Post(title=hello flexmock), Post(title=hello flexmock), ] (flexmock(Post.objects) .should_receive(all) .and_return(posts) .once()) response = self.client.get(/) self.assertEquals(response.status_code, 200) self.assertIn(hello flexmock, response.content)
  45. 45. from flexmock import flexmockimport blog.viewsdef test_home_view_as_unittest(self): request = flexmock( GET={}, POST={}, META={HTTP_HOST:example.com} ) response = blog.views.home(request) self.assertEquals(response.status_code, 200)
  46. 46. Теория vs практика
  47. 47. Есть требования ...def get_url_content(url): # ToDo # Вернуть контент страницы # или None, в случае ошибки pass
  48. 48. Как написать тест?def test_get_url_content(self): url = http://example.com text = get_url_content(url) self.assertEquals(text, ???)
  49. 49. Тестирование реализацииПишем тест имея представление о внутренностяхdef get_url_content(url): try: response = urllib.urlopen(url) content = response.read() response.close() except IOError: return None return contentНеверно с точки зрения теории,удобно на практике
  50. 50. Тест для случая нормального выполнения def test_get_url_content(self): url = http://example.com response = StringIO("<html>") (flexmock(urllib) .should_receive(urlopen) .with_args(url) .and_return(response) .once()) text = get_url_content(url) self.assertEquals(text, "<html>")
  51. 51. Тест в случае ошибки сетиdef test_get_url_content_on_ioerror(self): url = http://example.com (flexmock(urllib) .should_receive(urlopen) .with_args(url) .and_raise(IOError("test exception")) .once()) text = get_url_content(url) self.assertEquals(text, None)
  52. 52. Примеры тестовhttps://bitbucket.org/ishalyapin/python-test-exampleshttps://bitbucket.org/ishalyapin/django-test-examples
  53. 53. Спасибо за внимание! Доклад подготовили Илья Шаляпин ishalyapin@gmail.com www.ishalyapin.ru www.bookradar.org bitbucket.org/ishalyapin github.com/un1t Евгений Генералов e.generalov@gmail.com github.com/generalov

×