SlideShare uma empresa Scribd logo
1 de 53
Baixar para ler offline
Разработка через
  тестирование
в Python и Django
             Илья Шаляпин
         Евгений Генералов
19 проектов
    4 года
89299 строк кода
50826 строк тестов
Писать тесты или нет?
Пример из жизни
Переезд с Ubuntu 8.04 на Ubuntu 12.04

Python 2.5                 Python 2.7
Django 1.3                 Django 1.4.0
lxml 1.3.6                 lxml 2.3.2
PIL 1.1.6                  PIL 1.1.7
...                        ...
Перезд проекта плотно
  покрытого тестами
Перезд проекта менее плотно
    покрытого тестами
Перезд проекта без тестов
Преимущества


- Меньше ручной работы
- Спокойный рефакторинг
- Код легче читать
- Быстрое подключение людей к проекту
- Тесты являются спецификацией
Недостатки


- Затраты на обучение
- Дополнительные настроки в проекте
- Некоторые тесты сложно писать
TDD вид сбоку
$ pip install unittest2
# test_add.py

import unittest2


class 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()
# test_add.py

import unittest2

def add(a, b):
   pass


class AddTest(unittest2.TestCase):

  def test_add(self):
    self.assertEquals(add(1, 1), 2)


if __name__ == '__main__':
    unittest2.main()
Запуск теста




$ python test_add.py
$ python test_add.py
F
=========================================
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.000s

FAILED (failures=1)
# test_add.py

import unittest2

def add(a, b):
  return a + b


class AddTest(unittest2.TestCase):

  def test_add(self):
    self.assertEquals(add(1, 1), 2)


if __name__ == '__main__':
    unittest2.main()
$ python test_add.py
.
-------------------------------------------------
Ran 1 test in 0.000s

OK
Проект растет - тестов
  становится много

 ...
 ./tests/
 ./tests/test_add.py
 ./tests/test_sub.py
 ./tests/test_div.py
 ./tests/test_mul.py
 ./tests/test_pi.py
Nose - запускалка тестов
            Устанавливаем nose
$ pip install nose


               Запускаем тесты
$ nosetests
..
--------------------------------------------
Ran 100500 tests in 0.219s

OK
Инструменты




unittest2       django.test
flexmock        django_nose
nose            django_webtest
Тестирование в Django

Установить приложения
   $ pip install django_nose
   $ pip install django_webtest


Создать тестовую конфигурацию
    testing_settings.py
# testing_settings.py
from settings import *

DATABASES = {
  "default": dict(
    ENGINE = "django.db.backends.sqlite3",
    NAME = ":memory:",
  )
}

INSTALLED_APPS += (
  'django_nose',
)

TEST_RUNNER = 'django_nose.
NoseTestSuiteRunner'
Запуск тестов в Django

Запуск всех тестов в папке ./blog

$ manage.py test ./blog --settings project.
testing_settings



Запуск тестов в одном файле

$ manage.py test ./blog/test/test_forms.py --settings
project.testing_settings
Запуск тестов только для одного класса

$ 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
Blog tutorial
Тест view

from django.test import TestCase, Client


class HomePageTest(TestCase):

  def test_homepage_is_available(self):
    c = Client()
    response = c.get('/')
    self.assertEquals(response.status_code, 200)
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
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)
class HomePageTest(TestCase):

  def setUp(self):
    pass

  def tearDown(self):
    pass

  def test_homepage_contains_posts(self):
    pass
def home(request):
  posts = Post.objects.all()[:10]
  return render(request, 'home.html', {'posts':posts})
from django.db import models


class 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']
Отправка формы

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'])
Загрузка файлов

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)
$ pip install django_webtest
django_webtest - XPath
class 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)
django_webtest - формы

from django_webtest import WebTest

class 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)
Тесты админки




Почти такие же как тесты других view
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):
    ...
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'])
Прочее в Django

- Middleware
- Template tags, filters
- Context processors


- тестируются модульными тестами как
простые функции, аналогично с
примером 1+1 = 2
Особенности тестов view в Django

     ----------------------------
     middleware
     -----------------------------
     context processors
     -----------------------------
     template
     -----------------------------
     view
     -----------------------------
     models
     -----------------------------
     network
Flexmock

- Заменять части объектов и классов
- Заменять функции, в том числе
встроенные
- Создавать объекты заглушки
- Проверять ожидания (сколько раз
вызван метод, с какими аргументами)
$ pip install flexmock
from flexmock import flexmock
from blog.models import Post

def 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)
from flexmock import flexmock
import blog.views

def 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)
Теория vs практика
Есть требования ...

def get_url_content(url):
  # ToDo
  # Вернуть контент страницы
  # или None, в случае ошибки
  pass
Как написать тест?



def test_get_url_content(self):
  url = 'http://example.com'
  text = get_url_content(url)
  self.assertEquals(text, ???)
Тестирование реализации
Пишем тест имея представление о внутренностях

def get_url_content(url):
  try:
     response = urllib.urlopen(url)
     content = response.read()
     response.close()
  except IOError:
     return None
  return content


Неверно с точки зрения теории,
удобно на практике
Тест для случая нормального
        выполнения
 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>")
Тест в случае ошибки сети

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)
Примеры тестов



https://bitbucket.org/ishalyapin/python-test-examples

https://bitbucket.org/ishalyapin/django-test-examples
Спасибо за внимание!
  Доклад подготовили

 Илья Шаляпин
 ishalyapin@gmail.com
 www.ishalyapin.ru
 www.bookradar.org
 bitbucket.org/ishalyapin
 github.com/un1t



 Евгений Генералов
 e.generalov@gmail.com
 github.com/generalov

Mais conteúdo relacionado

Mais procurados

Web осень 2013 лекция 7
Web осень 2013 лекция 7Web осень 2013 лекция 7
Web осень 2013 лекция 7
Technopark
 
Selenium: начало работы
Selenium: начало работыSelenium: начало работы
Selenium: начало работы
Paul Stashevsky
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8
Technopark
 
Практика использования Dependency Injection
Практика использования Dependency InjectionПрактика использования Dependency Injection
Практика использования Dependency Injection
Platonov Sergey
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4
Technopark
 
Javascript testing
Javascript testingJavascript testing
Javascript testing
TCS bank
 

Mais procurados (17)

XPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriverXPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriver
 
Unit testing iOS Applications
Unit testing iOS ApplicationsUnit testing iOS Applications
Unit testing iOS Applications
 
бегун
бегунбегун
бегун
 
Web осень 2013 лекция 7
Web осень 2013 лекция 7Web осень 2013 лекция 7
Web осень 2013 лекция 7
 
Selenium: начало работы
Selenium: начало работыSelenium: начало работы
Selenium: начало работы
 
Нескучное тестирование с pytest
Нескучное тестирование с pytestНескучное тестирование с pytest
Нескучное тестирование с pytest
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8
 
Drf vs Graphql
Drf vs GraphqlDrf vs Graphql
Drf vs Graphql
 
Dependency injection, phemto
Dependency injection, phemtoDependency injection, phemto
Dependency injection, phemto
 
Практика использования Dependency Injection
Практика использования Dependency InjectionПрактика использования Dependency Injection
Практика использования Dependency Injection
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4
 
Лекция 6. Классы 1.
Лекция 6. Классы 1.Лекция 6. Классы 1.
Лекция 6. Классы 1.
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.
 
Javascript testing
Javascript testingJavascript testing
Javascript testing
 
Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3
 
Selenium vs AJAX
Selenium vs AJAXSelenium vs AJAX
Selenium vs AJAX
 
Angular 2: Всех переиграл
Angular 2: Всех переигралAngular 2: Всех переиграл
Angular 2: Всех переиграл
 

Destaque

Cu06997 computation lecture3
Cu06997 computation lecture3Cu06997 computation lecture3
Cu06997 computation lecture3
Henk Massink
 
Antónimos
AntónimosAntónimos
Antónimos
ManuT17
 
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do RioPlano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
William Nogueira
 
Herramientas web 2
Herramientas web 2Herramientas web 2
Herramientas web 2
sebassevitas
 
El buen y mal uso de las redes
El buen y mal uso de las redesEl buen y mal uso de las redes
El buen y mal uso de las redes
Defray Romero
 
Propaganda Convencional Y Poder PolíTico
Propaganda Convencional Y Poder PolíTicoPropaganda Convencional Y Poder PolíTico
Propaganda Convencional Y Poder PolíTico
eduardoacuna
 
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
USB REBIUdeG
 

Destaque (20)

Cu06997 computation lecture3
Cu06997 computation lecture3Cu06997 computation lecture3
Cu06997 computation lecture3
 
Antónimos
AntónimosAntónimos
Antónimos
 
Números
NúmerosNúmeros
Números
 
Valores
ValoresValores
Valores
 
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do RioPlano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
Plano Operacional da Jornada Mundial da Juventude - Prefeitura do Rio
 
Aparell digestiu
Aparell digestiuAparell digestiu
Aparell digestiu
 
Zabbix
ZabbixZabbix
Zabbix
 
Claudia Gilardoni
Claudia GilardoniClaudia Gilardoni
Claudia Gilardoni
 
Amistad
AmistadAmistad
Amistad
 
PROYECTO OCAÑA DIGITAL UFPSO
PROYECTO OCAÑA DIGITAL UFPSOPROYECTO OCAÑA DIGITAL UFPSO
PROYECTO OCAÑA DIGITAL UFPSO
 
Herramientas web 2
Herramientas web 2Herramientas web 2
Herramientas web 2
 
Ferdy
FerdyFerdy
Ferdy
 
Menuemail[1]
Menuemail[1]Menuemail[1]
Menuemail[1]
 
Curso sumário materias
Curso sumário materiasCurso sumário materias
Curso sumário materias
 
El buen y mal uso de las redes
El buen y mal uso de las redesEl buen y mal uso de las redes
El buen y mal uso de las redes
 
Correto é: Yielde e Revenue Management
Correto é: Yielde e Revenue ManagementCorreto é: Yielde e Revenue Management
Correto é: Yielde e Revenue Management
 
Metiers.unite4.2ºeso.
Metiers.unite4.2ºeso.Metiers.unite4.2ºeso.
Metiers.unite4.2ºeso.
 
Propaganda Convencional Y Poder PolíTico
Propaganda Convencional Y Poder PolíTicoPropaganda Convencional Y Poder PolíTico
Propaganda Convencional Y Poder PolíTico
 
Administrar, Gerenciar, Dirigir - lucrando
Administrar, Gerenciar, Dirigir -  lucrandoAdministrar, Gerenciar, Dirigir -  lucrando
Administrar, Gerenciar, Dirigir - lucrando
 
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
XV Coloquio 2009 / De la "satisfacción del usuario" a la "personalización" de...
 

Semelhante a Pycon Russia 2013 - Разработка через тестирование в Python и Django

Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7
Technopark
 
Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)
Pavel Novitsky
 
TestGuy - эмулируем вашего тестировщика
TestGuy - эмулируем вашего тестировщикаTestGuy - эмулируем вашего тестировщика
TestGuy - эмулируем вашего тестировщика
davertmik
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных.
MoscowDjango
 
вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNGвебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
Andrey Rebrov
 
Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobile
UA Mobile
 

Semelhante a Pycon Russia 2013 - Разработка через тестирование в Python и Django (20)

Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7
 
"VUE.JS как реакт с человеческим лицом" Дулецкий Вольдэмар, Evrone
"VUE.JS как реакт с человеческим лицом" Дулецкий Вольдэмар, Evrone"VUE.JS как реакт с человеческим лицом" Дулецкий Вольдэмар, Evrone
"VUE.JS как реакт с человеческим лицом" Дулецкий Вольдэмар, Evrone
 
12 - Web-технологии. Django модели
12 - Web-технологии. Django модели12 - Web-технологии. Django модели
12 - Web-технологии. Django модели
 
Magento code debugging
Magento code debuggingMagento code debugging
Magento code debugging
 
Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
 
TestGuy - эмулируем вашего тестировщика
TestGuy - эмулируем вашего тестировщикаTestGuy - эмулируем вашего тестировщика
TestGuy - эмулируем вашего тестировщика
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных.
 
бегун
бегунбегун
бегун
 
Looking into WordPress Core, WordCamp Russia 2015
Looking into WordPress Core, WordCamp Russia 2015Looking into WordPress Core, WordCamp Russia 2015
Looking into WordPress Core, WordCamp Russia 2015
 
вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNGвебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
 
Первые шаги после установки WordPress
Первые шаги после установки WordPressПервые шаги после установки WordPress
Первые шаги после установки WordPress
 
Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobile
 
Производительность в Django
Производительность в DjangoПроизводительность в Django
Производительность в Django
 
TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.
 
iOS and Android Mobile Test Automation
iOS and Android Mobile Test AutomationiOS and Android Mobile Test Automation
iOS and Android Mobile Test Automation
 
Дело тестера боится: как в опытных руках могут заиграть Java и TestNg
Дело тестера боится: как в опытных руках могут заиграть Java и TestNgДело тестера боится: как в опытных руках могут заиграть Java и TestNg
Дело тестера боится: как в опытных руках могут заиграть Java и TestNg
 
Python Meetup
Python Meetup Python Meetup
Python Meetup
 
10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)10 - Web-технологии. MVC фреймворки (продолжение)
10 - Web-технологии. MVC фреймворки (продолжение)
 
Erlang tasty & useful stuff
Erlang tasty & useful stuffErlang tasty & useful stuff
Erlang tasty & useful stuff
 

Pycon Russia 2013 - Разработка через тестирование в Python и Django

  • 1. Разработка через тестирование в Python и Django Илья Шаляпин Евгений Генералов
  • 2. 19 проектов 4 года 89299 строк кода 50826 строк тестов
  • 4. Пример из жизни Переезд с Ubuntu 8.04 на Ubuntu 12.04 Python 2.5 Python 2.7 Django 1.3 Django 1.4.0 lxml 1.3.6 lxml 2.3.2 PIL 1.1.6 PIL 1.1.7 ... ...
  • 5. Перезд проекта плотно покрытого тестами
  • 6. Перезд проекта менее плотно покрытого тестами
  • 8. Преимущества - Меньше ручной работы - Спокойный рефакторинг - Код легче читать - Быстрое подключение людей к проекту - Тесты являются спецификацией
  • 9. Недостатки - Затраты на обучение - Дополнительные настроки в проекте - Некоторые тесты сложно писать
  • 11. $ pip install unittest2
  • 12. # test_add.py import unittest2 class 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. # test_add.py import unittest2 def add(a, b): pass class AddTest(unittest2.TestCase): def test_add(self): self.assertEquals(add(1, 1), 2) if __name__ == '__main__': unittest2.main()
  • 15. $ python test_add.py F ========================================= 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.000s FAILED (failures=1)
  • 16. # test_add.py import unittest2 def add(a, b): return a + b class AddTest(unittest2.TestCase): def test_add(self): self.assertEquals(add(1, 1), 2) if __name__ == '__main__': unittest2.main()
  • 18. Проект растет - тестов становится много ... ./tests/ ./tests/test_add.py ./tests/test_sub.py ./tests/test_div.py ./tests/test_mul.py ./tests/test_pi.py
  • 19. Nose - запускалка тестов Устанавливаем nose $ pip install nose Запускаем тесты $ nosetests .. -------------------------------------------- Ran 100500 tests in 0.219s OK
  • 20. Инструменты unittest2 django.test flexmock django_nose nose django_webtest
  • 21. Тестирование в Django Установить приложения $ pip install django_nose $ pip install django_webtest Создать тестовую конфигурацию testing_settings.py
  • 22. # testing_settings.py from settings import * DATABASES = { "default": dict( ENGINE = "django.db.backends.sqlite3", NAME = ":memory:", ) } INSTALLED_APPS += ( 'django_nose', ) TEST_RUNNER = 'django_nose. NoseTestSuiteRunner'
  • 23. Запуск тестов в Django Запуск всех тестов в папке ./blog $ manage.py test ./blog --settings project. testing_settings Запуск тестов в одном файле $ manage.py test ./blog/test/test_forms.py --settings project.testing_settings
  • 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
  • 26. Тест view from django.test import TestCase, Client class HomePageTest(TestCase): def test_homepage_is_available(self): c = Client() response = c.get('/') self.assertEquals(response.status_code, 200)
  • 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. 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. class HomePageTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_homepage_contains_posts(self): pass
  • 30. def home(request): posts = Post.objects.all()[:10] return render(request, 'home.html', {'posts':posts})
  • 31. from django.db import models class 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. Отправка формы 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. Загрузка файлов 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. $ pip install django_webtest
  • 35. django_webtest - XPath class 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. django_webtest - формы from django_webtest import WebTest class 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. Тесты админки Почти такие же как тесты других view
  • 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. 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. Прочее в Django - Middleware - Template tags, filters - Context processors - тестируются модульными тестами как простые функции, аналогично с примером 1+1 = 2
  • 41. Особенности тестов view в Django ---------------------------- middleware ----------------------------- context processors ----------------------------- template ----------------------------- view ----------------------------- models ----------------------------- network
  • 42. Flexmock - Заменять части объектов и классов - Заменять функции, в том числе встроенные - Создавать объекты заглушки - Проверять ожидания (сколько раз вызван метод, с какими аргументами)
  • 43. $ pip install flexmock
  • 44. from flexmock import flexmock from blog.models import Post def 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. from flexmock import flexmock import blog.views def 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)
  • 47. Есть требования ... def get_url_content(url): # ToDo # Вернуть контент страницы # или None, в случае ошибки pass
  • 48. Как написать тест? def test_get_url_content(self): url = 'http://example.com' text = get_url_content(url) self.assertEquals(text, ???)
  • 49. Тестирование реализации Пишем тест имея представление о внутренностях def get_url_content(url): try: response = urllib.urlopen(url) content = response.read() response.close() except IOError: return None return content Неверно с точки зрения теории, удобно на практике
  • 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. Тест в случае ошибки сети 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)
  • 53. Спасибо за внимание! Доклад подготовили Илья Шаляпин ishalyapin@gmail.com www.ishalyapin.ru www.bookradar.org bitbucket.org/ishalyapin github.com/un1t Евгений Генералов e.generalov@gmail.com github.com/generalov