📜 ⬆️ ⬇️

Django BDD development

Programmers are very different in testing, and many do not like to write tests. The TDD process for newbies is not particularly clear - you have to write a test instead of the program’s functionality, which first 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 project on django, while in the project a couple of views and models is simple. When an application becomes cluttered with functions, it suddenly turns out that it is harder to perform such testing — there are more clicks, you need to enter some data, etc., and this is where behavior-driven development (BDD) comes to the rescue.

image

I want to talk about BDD on the example of creating a primitive application - rating sites. The idea is trivial - the list of sites is displayed on the page, the user votes for the site, the site rises in the ranking and changes its position on the page accordingly.
')
To begin with, we create requirements.txt in the project's working folder, with something like this:
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 in 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 the list of sites. To develop in the BDD-style using the tools we have, we create the habratest / vote / features and habratest / vote / features / steps folders

Here we will describe the behavior that we want to achieve from the application. In the features folder, create the file habra.features 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 very similar to computer language, huh? This is Gherkin. It can describe the behavior of the program without going into the implementation. Thus, a person who is not familiar with programming can write test tasks.

We specify such a URL because django-behave runs a test server on port 8081.

In the same folder we create environment.py, the code in which is executed before and after testing, and so far only ensures the efficiency of the test browser:
 from splinter.browser import Browser def before_all(context): context.browser = Browser() def after_all(context): context.browser.quit() context.browser = None 


Run
 $ ./manage.py test vote 


Nothing happened - the test environment does not understand what to do with the steps in the file habra.features. See the lines of yellow (or brown-yellow) color? Feel free to copy them into 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 have to see that our test is filing, 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 


Import to habratest / urls.py
 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:
 <!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> 


When tests are run in memory, a new database is created each time, and after it is deleted, we need to fill it with some data. To do this in the file habratest / habratest / populate.py we 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 append the import of this script in environment.py
 from habratest import populate 


Now environment.py, in addition to ensuring the work of the browser, also deals with a test database.

Run again
 $ ./manage.py test vote 


- excellent, the test is 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 perform 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 again run the tests. Error in step When I click link contents "+". So - we don’t have any link "+" in the template, as well as the reaction to it is not provided, which we will fix as follows (do not pay attention to the fact that the code is not protected from cheating, this is just an illustration):

Add “plus” to 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> 


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<pk>\d+)/$', addvote, name='addvote'), 


And templates templates / successful.html:
 <!DOCTYPE html> <html> <head> <title>Habra rating</title> </head> <body> <p>Vote successful</p> </body> </html> 


We run tests - everything should be successful.

And now we will write a test to check the performance of the rating increase. We should see the changes in the list when voting, here we will use the fact that we know the original data (which made populate.py), because 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. 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 for gherkin (which, as I said above, even a person 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 there is practically no information on BDD with django in Russian, and if experts in this matter suddenly read it, do not be lazy, write about your practice in the comments. It will be useful to all. I am pleased to accept your comments, corrections and criticism. Thank!

What else to read:
Behave documentation
Splinter - test framework for web applications
Django Full Stack Testing and BDD with Lettuce and Splinter

Source: https://habr.com/ru/post/219665/


All Articles