In the previous post, we briefly reviewed some of the techniques for testing python code. All this also applies to Django-projects, of course, but there are a sufficient number of pitfalls and just interesting pieces, which I will try to tell.
Summary of post:
- testing websites is hard and confusing
- unit tests in django
- test database and how to deal with it
- smoke testing
- code coverage
Website Testing
The most important underwater iceberg of testing Django projects is that it is not enough to write tests for pitonokod. Layout collapses, JavaScript lives its own life, the web server could not withstand the load and turned into a pumpkin - it is somewhat more difficult to identify all these things with the help of testing than to track the wrong function result.
Therefore, website health check is usually a complex phenomenon consisting of several independent test suites, some of which (appearance checks in various browsers, for example) may involve the operator. In the absence of a QA department, the role of the tester is often assigned to the end user, who then swears at all. So do wrong.
Colonel Evidence post passed.')
We will start with (relatively) simple and clear unit tests.
Unit Tests in Django
Unit tests in Django live in the
django.utils.unittest
module and are an extension of the standard unittest module from python 2.7 (unittest2). What is added:
Test HTTP client. Simulates browser
operation , can send get- and post-requests, stores cookies between calls.
>>> from django.test.client import Client >>> c = Client() >>> response = c.post('/login/', {'username': 'admin', 'password': 'qwerty'}) >>> response.status_code 200
There are several limitations to the test client. For example, you can only request a relative path, a URL like http: / / localhost: 8000 / does not work (for obvious reasons).
Extended set of checks. In addition to the
standard set , the
django.test.TestCase
class
django.test.TestCase
contains django-specific
assert*
methods, for example:
assertContains(response, text, ...)
and other
useful things .
Testing mail. The django.core.mail module stores in the outbox variable a list of all
send_mail()
sent via
send_mail()
.
Conditional exclusion of tests. If the selected DBMS does not support (or, on the contrary, supports) transactionality, you can exclude the obviously broken tests using the decorator
@skipUnlessDBFeature('supports_transactions')
or
@skipIfDBFeature('supports_transactions')
.
Testing starts like this:
$ ./manage.py test [ ]
By default, all tests are run for all applications listed in
INSTALLED_APPS
. The start-up (
test runner in the original language) will find the units and tests in the models.py and tests.py files inside each application. To import docs from other modules, you can use the following entry:
from utils import func_a, func_b __test__ = {"func_a": func_a, "func_b": func_b}
Here
func_*
is a function (or other entity) whose docstring interests us.
For the observer, the testing process is as follows:
$ ./manage.py test main Creating test database for alias 'default'... .......... Ran 10 tests in 0.790s OK Destroying test database for alias 'default'...
Test database and how to deal with it
To run tests, Django always creates a new database to eliminate the possibility of data destruction in the working environment. Unless otherwise specified in settings.py, the test database is preceded by the word test_. Applied to MySQL, privileges are usually set like this:
GRANT ALL PRIVILEGES ON `project`.* TO 'user'@'localhost'; GRANT ALL PRIVILEGES ON `test_project`.* TO 'user'@'localhost';
You do not need to create the test_project database itself.
Mistress on the note. Everything works faster if you add a line to MySQL config
[mysqld] skip-sync-frm=OFF
It is speculative that immediately after the creation of any useful data in the database there. In order not to generate a test dataset inside each test separately, you can do it once and save it in fixture:
$ ./manage.py dumpdata > app/fixtures/test_data.json
In the code:
class HelloTestCase(TestCase): fixtures = ['test_data.json', 'moar_data.json']
And further. Try to use for development and testing the same DBMS as in the production-server. This will make your sleep 28%
* calmer.
* It is scientifically proven that 87.56% of statistics are taken from the ceiling.Smoke testing
In the ham radio environment, the term smoke test literally means the following: we plug in the power supply to the freshly assembled circuit and observe where the smoke came from it. If the smoke does not go, you can start a more scientific verification of the correct operation of the circuit.
The described approach is also practiced when testing applications. Applicable to Django it makes sense to describe entry points from URLconf in tests.py, for example, like this:
urls.py urlpatterns = patterns(None, url(r'^registration/$', registration, name='registration'), url(r'^login/$', ..., name='login'), url(r'^logout/$', logout_then_login, name='logout'), )
tests.py from django import test from django.core.urlresolvers import reverse __test__ = {"urls": """ >>> c = test.Client() >>> c.get(reverse('registration')).status_code 200 >>> c.get(reverse('login')).status_code 200 >>> c.get(reverse('logout')).status_code 302 """}
Of course, such a test will not replace the functional test registration and login.
Colonel Evidence accepted the post.Code Coverage
Code coverage is a metric that shows how much source code has been tested against the entire volume of useful source code in an application. Low code coverage indicates no tests.
Mistress of the note-2. High code coverage does not indicate the absence of errors (neither in the code, nor in the tests), it is a fiction.
To measure code coverage on python, there is
coverage.py . Google remembers many attempts to make friends of coverage.py and Django, there is even a
ticket # 4501 (he is four years old).
And at once a fly in the ointment: with Django 1.3 (and the dev version) no ready-made solution for code coverage seems to work (correct me if this is not the case). That, however, will not prevent us from running the coverage.py by hand.
$ coverage run --source=main,users manage.py test main users $ coverage html

We list only the modules we are interested in (key - source); if not specified, there will be including django, mysqldb and half the standard python delivery.
After that, in the htmlcov folder (the default path) you can see a detailed report on each line of code, coverage by modules and total for the project.
In the next issue: static analysis as a preventive measure, layout testing and JS, load testing.