Я хочу рассказать о BDD на примере создания примитивного приложения — рейтинга сайтов. Идея тривиальна — на странице отображается список сайтов, пользователь голосует за сайт, сайт поднимается в рейтинге и соответственно изменяет положение на странице.
Для начала в рабочей папке проекта создаем requirements.txt, с примерно таким содержанием:
Django git+git://github.com/svfat/django-behave splinter
Обратите внимание, в разработке я использую свой форк django-behave. Код из официального репозитария отказался работать, видимо по причине несовместимости с текущими версиями программ.
$ pip install -r requirements.txt $ django-admin.py startproject habratest $ cd habratest/ $ ./manage.py startapp vote
По умолчанию должна установиться последняя стабильная версия Django, и для начала разработки нам потребуется добавить лишь несколько строчек в settings.py:
INSTALLED_APPS = ( ... 'vote', 'django_behave', ) TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'templates/'), # не забываем создать папку habratest/templates ) TEST_RUNNER = 'django_behave.runner.DjangoBehaveTestSuiteRunner'
Первым этапом добьемся отображения списка сайтов. Для разработки в BDD-стиле с помощью имеющихся у нас инструментов, создаем папки habratest/vote/features и habratest/vote/features/steps
Здесь мы будем описывать поведение, которого мы хотим добиться от приложения. В папке features создаем файл habra.features с таким содержимым:
Feature: Habrarating Scenario: Show a rating Given I am a visitor When I visit url "http://localhost:8081/" Then I should see link contents url "habrahabr.ru"
Не очень похоже на компьютерный язык, да? Это — Gherkin. На нем можно описать поведение программы не вдаваясь в реализацию. Таким образом, писать тестовые задания может человек мало знакомый с программированием.
Мы указываем такой URL, потому что django-behave запускает тестовый сервер на порте 8081.
В той же папке создаем environment.py, код в котором выполняется до и после тестирования, и пока только обеспечивает работоспособность тестового браузера:
from splinter.browser import Browser def before_all(context): context.browser = Browser() def after_all(context): context.browser.quit() context.browser = None
Запускаем
$ ./manage.py test vote
Ничего не получилось — тестовое окружение не понимает, что делать с шагами в файле habra.features.Видите строки желтого (ну или коричнево-желтого) цвета? Смело копируйте их в habratest/vote/features/steps/habra.feature.py, в нем описывается реализация шагов, и его содержание должно стать примерно таким:
from behave import given, when, then @then(u'I should see link contents url "{content}"') def i_should_see_link_contents_url(context, content): msg = context.browser.find_link_by_partial_href(content).first assert msg @when(r'I visit url "{url}"') def i_visit_url(context, url): br = context.browser br.visit(url) @given(u'I am a visitor') def i_am_a_visitor(context): pass
Запускаем еще раз
$ ./manage.py test vote
Так, теперь мы должны видеть что наш тест фейлится, и причина очевидна — мы еще не написали ни строчки кода функционала приложения, да и данных в базе нет.
Создаем модельку в vote/models.py
class VoteItem(models.Model): url = models.URLField() rating = models.IntegerField(default=0) class Meta: # это для того чтобы в ListView наши сайты сортировались по рейтингу ordering = ["-rating"] def __unicode__(self): return self.url
Делаем:
$ ./manage.py syncdb
В habratest/urls.py импортируем
from vote.views import VoteListView
и добавляем в urlpatterns
url(r'^$', VoteListView.as_view(), name="index"),
в vote/views.py
from django.views.generic import ListView from models import VoteItem class VoteListView(ListView): model=VoteItem template_name="list.html"
в habratest/templates/list.html наш шаблон в стиле ретро:
<!DOCTYPE html> <html> <head> <title>Habra rating</title> </head> <body> <ol> {% for voteitem in object_list %} <li id="{{voteitem.pk}}"><a href="{{ voteitem.url }}">{{ voteitem.url }}</a> | Rating:{{voteitem.rating}}</li> {% endfor %} </ul> </body> </html>
При запуске тестов в памяти каждый раз создается новая БД, а после окончания удаляется, поэтому ее нам нужно заполнить какими-то данными. Для это в файл habratest/habratest/populate.py пишем:
from vote.models import VoteItem VoteItem(url="http://www.yandex.ru", rating=6).save() VoteItem(url="http://www.google.com", rating=5).save() VoteItem(url="http://www.habrahabr.ru", rating=6).save()
и дописываем импорт этого скрипта в environment.py
from habratest import populate
Теперь environment.py кроме обеспечения работы браузера еще и занимается тестовой базой.
Снова запускаем
$ ./manage.py test vote
— отлично, тест пройден.Но как же рейтинг — нам надо ведь как-то голосовать за сайты
Добавляем новый сценарий в habra.feature:
Scenario: Vote for a site Given I am a visitor When I visit url "http://localhost:8081/" When I click link contents "+" Then I should see "Vote successful" somewhere in page
Тестовое окружение не знает как выполнить два последних шага, поэтому дописываем их в steps/habra.feature.py:
@then(u'I should see "{text}" somewhere in page') def i_should_see_text_somwhere_in_page(context, text): assert text in context.browser.html @when(u'I click link contents "{text}"') def i_click_link_contents_text(context, text): link = context.browser.find_link_by_text(text).first assert link link.click()
После чего опять запускаем тесты. Ошибка на шаге When I click link contents "+". Так — никакой ссылки "+" у нас в шаблоне нет, как и не предусмотрена реакция на нее, что мы и исправим следующим образом (не обращайте внимание, что код никак не защищен от накрутки, это всего лишь иллюстрация):
В habratest/templates/list.html добавляем «плюс»:
<li id="{{voteitem.pk}}"><a href="{{ voteitem.url }}">{{ voteitem.url }}</a> | Rating:{{voteitem.rating}} | <a href={% url 'addvote' voteitem.pk %}>+<a></li>
Соответственно создаем примитивную вьюшку для addvote в vote/views.py:
from django.shortcuts import render_to_response def addvote(request, pk): return render_to_response('successful.html', context)
добавляем ее в urls.py,
from vote.views import VoteListView, addvote
и
url(r'^plus/(?P<pk>\d+)/$', addvote, name='addvote'),
И шаблон templates/successful.html:
<!DOCTYPE html> <html> <head> <title>Habra rating</title> </head> <body> <p>Vote successful</p> </body> </html>
Запускаем тесты — все должно пройти успешно.
А теперь напишем тест для проверки работоспособности увеличения рейтинга. Мы должны увидеть изменения в списке при голосовании, здесь воспользуемся тем, что знаем исходные данные (которые внесли в populate.py), так как y habrahabr.ru rating=6, при клике на "+" его рейтинг должен измениться на единицу, и по выполнению предыдущего сценария, его рейтинг должен стать равным «7».
Scenario: Vote for a site and look at the rating Given I am a visitor When I visit url "http://localhost:8081/" Then I should see "Rating:7" somewhere in page
Опять последний шаг не выполняется. Для того, чтобы исправить это, дописываем вьюшку addvote:
def addvote(request, pk): item = VoteItem.objects.get(pk=pk) item.rating += 1 item.save() return render_to_response('successful.html')
Проверяем, теперь тест проходит успешно.
Итак, продолжая писать тесты на gherkin (что, как я уже говорил выше, может делать даже человек не знакомый с программированием) мы фактически создадим часть технического задания и, одновременно, приемочного тестирования нашего приложения.
Я написал эту статью, так как на русском языке практически нет информации по BDD с django, и, если ее вдруг будут читать специалисты в этом вопросе, не поленитесь, напишите в комментариях о своей практике. Это будет полезно всем. С радостью приму ваши замечания, исправления и критику. Спасибо!
Что еще почитать:
Behave documentation
Splinter — test framework for web applications
Django Full Stack Testing and BDD with Lettuce and Splinter
ссылка на оригинал статьи http://habrahabr.ru/post/219665/
Добавить комментарий