Django BDD Development
- Tutorial
Programmers have a very different attitude to testing, and many do not like to write tests. The TDD process is not particularly clear for beginners - after all, instead of the program’s functionality, you first have to write a test that checks it, that is, the amount of work increases. However, over time comes the realization that automatic testing is necessary. For example, let's take the development process of even a simple django project, while a couple of views and models are simple in the project. When the application is overloaded with functions, it suddenly turns out that it’s more difficult to perform such testing - there are more clicks, you need to enter some data, etc., this is where behavior-driven development (BDD) comes to the rescue.
I want to talk about BDD as an example of creating a primitive application - ranking sites. The idea is trivial - the page displays a list of sites, the user votes for the site, the site rises in the ranking and accordingly changes the position on the page.
First, create requirements.txt in the project’s working folder, with approximately the following content:
Please note that in development I use my fork django-behave. The code from the official repository refused to work, apparently due to incompatibility with current versions of programs.
By default, the latest stable version of Django should be installed, and to start development, we need to add only a few lines to settings.py:
The first step is to display a list of sites. To develop in BDD style using the tools we have, create the folders habratest / vote / features and habratest / vote / features / steps
Here we will describe the behavior that we want to achieve from the application. In the features folder, create a habra.features file with the following contents:
Not really like a computer language, right? This is Gherkin. On it, you can describe the behavior of the program without going into implementation. Thus, a person familiar with programming can write test tasks.
We specify this URL because django-behave starts the test server on port 8081.
In the same folder, create environment.py, the code in which is executed before and after testing, and so far only ensures the test browser is working:
We launch
Nothing happened - the test environment does not understand what to do with the steps in the habra.features file. Do you see lines of yellow (well, or brown-yellow) color? Feel free to copy them to habratest / vote / features / steps / habra.feature.py, it describes the implementation of the steps, and its content should look something like this:
Run again
So, now we need to see that our test fails, and the reason is obvious - we have not written a single line of application functional code, and there is no data in the database.
Create a model in vote / models.py
We do:
In habratest / urls.py we import
and add to urlpatterns
in vote / views.py
in habratest / templates / list.html our retro style template:
When tests are run, a new database is created in memory each time, and after the end it is deleted, so we need to fill it with some data. For this, in the file habratest / habratest / populate.py write:
and add the import of this script to environment.py
Now, environment.py, in addition to ensuring the operation of the browser, is also engaged in a test base.
We start again
- excellent, the test passed. But what about the rating - we need to somehow vote for the sites.
Add a new script to habra.feature:
The test environment does not know how to complete the last two steps, so we add them to steps / habra.feature.py:
Then we run the tests again. The error in step When I click link contents "+". So - we don’t have a “+” link in the template, just as there is no reaction to it, which we will correct as follows (do not pay attention that the code is not protected from cheating in any way, this is just an illustration):
In habratest / templates /list.html add a plus:
Accordingly, we create a primitive view for addvote in vote / views.py:
add it to urls.py,
and
And the template templates / successful.html:
Run the tests - everything should succeed.
And now we will write a test to test the performance of increasing the rating. We should see the changes in the list during the voting, here we will take advantage of the fact that we know the source data (which were made in populate.py), since y habrahabr.ru rating = 6, when clicking on “+” its rating should change by one, and according to the previous scenario, its rating should be equal to "7".
Again, the last step is not performed. In order to fix this, we add the addvote view:
We check, now the test is successful.
So, continuing to write tests on gherkin (which, as I said above, even a person who is not familiar with programming can do), we will actually create part of the technical task and, at the same time, acceptance testing of our application.
I wrote this article, since in Russian there is practically no information on BDD with django, and if experts in this matter suddenly read it, do not be lazy, write in the comments about your practice. It will be useful to everyone. I will gladly accept your comments, corrections and criticism. Thanks!
What else to read:
Behave documentation
Splinter - test framework for web applications
Django Full Stack Testing and BDD with Lettuce and Splinter
I want to talk about BDD as an example of creating a primitive application - ranking sites. The idea is trivial - the page displays a list of sites, the user votes for the site, the site rises in the ranking and accordingly changes the position on the page.
First, create requirements.txt in the project’s working folder, with approximately the following content:
Django
git+git://github.com/svfat/django-behave
splinter
Please note that in development I use my fork django-behave. The code from the official repository refused to work, apparently due to incompatibility with current versions of programs.
$ pip install -r requirements.txt
$ django-admin.py startproject habratest
$ cd habratest/
$ ./manage.py startapp vote
By default, the latest stable version of Django should be installed, and to start development, we need to add only a few lines to settings.py:
INSTALLED_APPS = (
...
'vote',
'django_behave',
)
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates/'), # не забываем создать папку habratest/templates
)
TEST_RUNNER = 'django_behave.runner.DjangoBehaveTestSuiteRunner'
The first step is to display a list of sites. To develop in BDD style using the tools we have, create the folders habratest / vote / features and habratest / vote / features / steps
Here we will describe the behavior that we want to achieve from the application. In the features folder, create a habra.features file with the following contents:
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"
Not really like a computer language, right? This is Gherkin. On it, you can describe the behavior of the program without going into implementation. Thus, a person familiar with programming can write test tasks.
We specify this URL because django-behave starts the test server on port 8081.
In the same folder, create environment.py, the code in which is executed before and after testing, and so far only ensures the test browser is working:
from splinter.browser import Browser
def before_all(context):
context.browser = Browser()
def after_all(context):
context.browser.quit()
context.browser = None
We launch
$ ./manage.py test vote
Nothing happened - the test environment does not understand what to do with the steps in the habra.features file. Do you see lines of yellow (well, or brown-yellow) color? Feel free to copy them to habratest / vote / features / steps / habra.feature.py, it describes the implementation of the steps, and its content should look something like this:
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
Run again
$ ./manage.py test vote
So, now we need to see that our test fails, and the reason is obvious - we have not written a single line of application functional code, and there is no data in the database.
Create a model in 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
We do:
$ ./manage.py syncdb
In habratest / urls.py we import
from vote.views import VoteListView
and add to urlpatterns
url(r'^$', VoteListView.as_view(), name="index"),
in vote / views.py
from django.views.generic import ListView
from models import VoteItem
class VoteListView(ListView):
model=VoteItem
template_name="list.html"
in habratest / templates / list.html our retro style template:
Habra rating
{% for voteitem in object_list %}
- {{ voteitem.url }} | Rating:{{voteitem.rating}}
{% endfor %}
When tests are run, a new database is created in memory each time, and after the end it is deleted, so we need to fill it with some data. For this, in the file habratest / habratest / populate.py write:
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()
and add the import of this script to environment.py
from habratest import populate
Now, environment.py, in addition to ensuring the operation of the browser, is also engaged in a test base.
We start again
$ ./manage.py test vote
- excellent, the test passed. But what about the rating - we need to somehow vote for the sites.
Add a new script to 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
The test environment does not know how to complete the last two steps, so we add them to 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()
Then we run the tests again. The error in step When I click link contents "+". So - we don’t have a “+” link in the template, just as there is no reaction to it, which we will correct as follows (do not pay attention that the code is not protected from cheating in any way, this is just an illustration):
In habratest / templates /list.html add a plus:
{{ voteitem.url }}
| Rating:{{voteitem.rating}}
| +
Accordingly, we create a primitive view for addvote in vote / views.py:
from django.shortcuts import render_to_response
def addvote(request, pk):
return render_to_response('successful.html', context)
add it to urls.py,
from vote.views import VoteListView, addvote
and
url(r'^plus/(?P\d+)/$', addvote, name='addvote'),
And the template templates / successful.html:
Habra rating Vote successful
Run the tests - everything should succeed.
And now we will write a test to test the performance of increasing the rating. We should see the changes in the list during the voting, here we will take advantage of the fact that we know the source data (which were made in populate.py), since y habrahabr.ru rating = 6, when clicking on “+” its rating should change by one, and according to the previous scenario, its rating should be equal to "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
Again, the last step is not performed. In order to fix this, we add the addvote view:
def addvote(request, pk):
item = VoteItem.objects.get(pk=pk)
item.rating += 1
item.save()
return render_to_response('successful.html')
We check, now the test is successful.
So, continuing to write tests on gherkin (which, as I said above, even a person who is not familiar with programming can do), we will actually create part of the technical task and, at the same time, acceptance testing of our application.
I wrote this article, since in Russian there is practically no information on BDD with django, and if experts in this matter suddenly read it, do not be lazy, write in the comments about your practice. It will be useful to everyone. I will gladly accept your comments, corrections and criticism. Thanks!
What else to read:
Behave documentation
Splinter - test framework for web applications
Django Full Stack Testing and BDD with Lettuce and Splinter