📜 ⬆️ ⬇️

We write functional / integration tests for the project on django

In this exciting article I will talk about the tools with which you can write functional tests for a django project. There are a lot of different other ways to do it, but I will describe one - the one that, in my opinion, is the simplest. In the meantime, we will create a beautiful report on code coverage (subjectively - nicer than those that coverage.py does). And also, as a seasoning, there will be a little talk about testing.



Right off the bat.
')

1. Install the necessary packages


 $ pip install coverage> = 3.0
 $ pip install -e hg + http: //bitbucket.org/kmike/webtest
 $ pip install django-webtest
 $ pip install django-coverage

WebTest is a library for functional testing wsgi-applications from Ian Bicking (author pip and virtualenv). I recently added Unicode support there (which is very important for us, isn't it?), This has not yet been included in the main repository, so we install from mine so far (an installed mercurial is required, but without it you can download the zip- archive, unpack and run setup.py install). Then, I think, it will be possible to put c pypi. You can still install from there (pip install webtest), only Unicode in forms and links will not work. ( UPD: everything has been included for a long time, you can put everything with pypi quietly )

Why WebTest, not twill? Twill also does not support unicode (only there is worse, not only unicode, but even non-Latin letters in utf8, as far as I can tell - UPD comments meako that simply strings work, at least in conjunction with tddspry ), the latest release It was in 2007, there is a lot of code, outdated versions of the libraries in the delivery, and it seemed that it would take more effort to screw something in there. In general, twill is good, and html parsing is better there, so if anything - keep it in mind too (and the django-test-utils package ( or tddspry? ) Then too).

Why not the built-in jung test client? WebTest has a much more powerful API, it is easier to write functional tests with its help. Read the docstring to django.test.client.Client:

It is not intended to use it, but it is not so HTML rendered to the end-user.

Why not Selenium / windmill / ..? It does not interfere. They test another after all. For what you can use twill / WebTest, it is better to use twill / WebTest, since this will work much faster, have better integration with other code and easier setup.

2. Configure the project


A small setup is required for django-coverage. Do not be scared, not big) It should:

1. add 'django_coverage' to INSTALLED_APPS and
2. in settings.py specify where to save the html-reports. It is good to create a folder for this business.

COVERAGE_REPORT_HTML_OUTPUT_DIR = os.path.join(PROJECT_PATH, 'cover')

3. We write tests


Here, for example, is a functional test for registration / authorization:

Copy Source | Copy HTML<br/> # coding: utf-8 <br/> import re<br/> from django.core import mail<br/> from django_webtest import WebTest<br/> <br/> class AuthTest (WebTest):<br/> fixtures = [ 'users.json' ]<br/> <br/> def testLogoutAndLogin (self): <br/> page = self .app.get( '/' , user= 'kmike' )<br/> page = page.click(u '' ).follow()<br/> assert u '' not in page<br/> login_form = page.click(u '' , index= 0 ).form<br/> login_form[ 'email' ] = 'example@example.com' <br/> login_form[ 'password' ] = '123' <br/> result_page = login_form.submit().follow()<br/> assert u '' not in result_page<br/> assert u '' in result_page<br/> <br/> def testEmailRegister (self):<br/> register_form = self .app.get( '/' ).click(u '' ).form<br/> self .assertEqual( len (mail.outbox), 0 )<br/> register_form[ 'email' ] = 'example2@example.com' <br/> register_form[ 'password' ] = '123' <br/> assert u ' ' in register_form.submit().follow()<br/> self .assertEqual( len (mail.outbox), 1 )<br/> <br/> # , <br/> # <br/> mail_body = unicode (mail.outbox[ 0 ].body)<br/> activate_link = re .search( '(/activate/.*/)' , mail_body).group( 1 )<br/> activated_page = self .app.get(activate_link).follow()<br/> assert u '<h1> </h1>' in activated_page<br/> <br/>
Copy Source | Copy HTML<br/> # coding: utf-8 <br/> import re<br/> from django.core import mail<br/> from django_webtest import WebTest<br/> <br/> class AuthTest (WebTest):<br/> fixtures = [ 'users.json' ]<br/> <br/> def testLogoutAndLogin (self): <br/> page = self .app.get( '/' , user= 'kmike' )<br/> page = page.click(u '' ).follow()<br/> assert u '' not in page<br/> login_form = page.click(u '' , index= 0 ).form<br/> login_form[ 'email' ] = 'example@example.com' <br/> login_form[ 'password' ] = '123' <br/> result_page = login_form.submit().follow()<br/> assert u '' not in result_page<br/> assert u '' in result_page<br/> <br/> def testEmailRegister (self):<br/> register_form = self .app.get( '/' ).click(u '' ).form<br/> self .assertEqual( len (mail.outbox), 0 )<br/> register_form[ 'email' ] = 'example2@example.com' <br/> register_form[ 'password' ] = '123' <br/> assert u ' ' in register_form.submit().follow()<br/> self .assertEqual( len (mail.outbox), 1 )<br/> <br/> # , <br/> # <br/> mail_body = unicode (mail.outbox[ 0 ].body)<br/> activate_link = re .search( '(/activate/.*/)' , mail_body).group( 1 )<br/> activated_page = self .app.get(activate_link).follow()<br/> assert u '<h1> </h1>' in activated_page<br/> <br/>


We save it in the file tests.py of the desired application in the project. It seems everything is clear there should be. We check if a registered person can leave the site, then go to it (by entering their email and password), can they register (will the letter receive? Is the activation link correct?) And does it get to the page you need after activating the account. For convenience, a fixture was also used, in which the kmike user with email=example@example.com was already prepared (and which is used in other tests). It was possible to create this user directly in the test, this is not the point.

Pay attention to the API: we follow the links, indicating their name (.click (u'Registration '), for example), i.e. what the user actually presses on (there are other possibilities). At each transition WebTest automatically checks that code 200 or 302 has returned to us (this is configurable). To submit the forms, you do not need to construct POST requests manually, the forms are picked up from the html response code, it is enough to assign values ​​to the required fields and execute the submit () method. Redirects after POST requests are made by hand (and this is useful, because if there is no redirect - for example, an error while filling in the form, then the test will show it).

django_webtest.WebTest is the heir from Jung TestCase, it can do the same. But the main thing is that the self.app variable of the DjangoTestApp type is available (it is the heir of the webtest.TestApp) through which you can access the WebTest API. More information about what WebTest can do is better to read on their site . There is a simple and pleasant API, you can follow the links, submit forms, upload files, parse the answer (much higher level and concise than the jung test client). django_webtest adds one janga-specific feature to the API: the self.app.get and self.app.post methods accept the optional user parameter. If the user is submitted, the request (well, all subsequent clicks on links, form submissions, etc.) will be executed on behalf of the jung user with this username.

It is clear that here it was possible to test the most, and it was possible less, and here it is good to observe some kind of balance: so that tests are easy to write and maintain, so that they check everything that is needed, but do not check what is not needed. Sometimes it will be wrong to click on the link through its name, sometimes it will not be enough to simply check whether there is text on the page, sometimes even this check will be superfluous. This, I think, is called experience, when you understand how best. The way I wrote these tests is not necessarily the best way in this situation (although imho is quite adequate), consider just an example, not an example to follow, think about what you write. One of the advantages of simple API is that the programmer begins to think what to write and how best to write, and not “how to finish adding something at last ..”.

4. Run the tests


Create a file test_settings.py (at the root of the project) of approximately the same content (syntax for django 1.1):

from settings import *
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'testdb.sqlite'

And then we run the tests:

  $ python manage.py test_coverage myapp1 myapp2 myapp3 --setting = test_settings 

You can do without test_settings (just run $ python manage.py test_coverage myapp and do not create any additional files), it's just more convenient with it: you can write any test-specific settings there, for example, use another DBMS to run tests faster or replace URLOpener for urllib2, so that tests do not climb on the Internet. It is convenient to wrap the command to start tests in a shell script (or a bat file if someone has the misfortune of writing on python under windows)

5. We look pictures


The report on code coverage was saved in the folder specified earlier. Open it (file cover / index.html) and see something like this:


We follow some link and see which code we executed during the tests, but which code did not (and, therefore, could not be tested):


... many lines ...

... many lines ...

Aha It is immediately obvious that we didn’t check the situation when a person entered the email of an already registered user.

It is important to remember that functional / integration tests are not a replacement for unit tests , but an addition to them, and that 100% coverage does not guarantee the absence of errors. Unit tests are accurate, they say THAT is broken, they are extremely useful in refactoring and in difficult places of the project. Functional - rude, they say only "it seems that something has broken somewhere" and save from foolish mistakes. But even if the tests just click on all the links on the site and check if there is an exception, then these will already be very useful tests that can save you from a lot of trouble.

To illustrate the difference: in the unit test for the registration form, we would create an object of the EmailRegistrationForm class, pass different data dictionaries to it and look at what exceptions are thrown, for example. Or would check the individual methods of this form. Unit tests are as close as possible to the code (although it makes sense not to let them out of the public API), test a separate piece of it, and allow you to check that all parts of the system are working correctly. Functional / integration tests help to verify that together they also work correctly.

6. All links


docs.djangoproject.com/en/dev/topics/testing
pythonpaste.org/webtest
bitbucket.org/ianb/webtest
bitbucket.org/kmike/webtest
bitbucket.org/kmike/django-webtest
bitbucket.org/kmike/django-coverage
github.com/ericholscher/django-test-utils
twill.idyll.org
nedbatchelder.com/code/coverage

Yes, all this can be just as easily used without django, WebTest is very easily screwed to any framework that supports wsgi (and supported by “all frameworks worthy of attention”), coverage.py works great for any tests. All these django -... applications - just to make installation and configuration as easy as possible. Well, django-coverage, if anything, has nothing to do with the webtest, it’s just like that, hell.

7. Brief instructions


1. install packages
2. add 'django_coverage' to INSTALLED_APPS
3. in settings.py specify where to save the html-reports. It is good to create a folder for this business.
COVERAGE_REPORT_HTML_OUTPUT_DIR = os.path.join(PROJECT_PATH, 'cover')
4. writing tests, inheriting our test case from django_webtest.WebTest and using self.app
5. run them: $ python manage.py test_coverage myapp1 myapp2 myapp3 --settings=test_settings

If something does not work in the webtest, django-webtest and django-coverage - write in Issues on bitbucket, I will try to help.

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


All Articles