Natürlich möchte man auch gerne das Frontend der Applikation testen. Dafür gibt es zum Beispiel Werkzeuge wie Selenium.
Mit dem in Django eingebauten Testclient steht ein einfacher Testbrowser zur Verfügung, der zwar nicht alle Features von Selenium bietet, aber dafür auch einfacher einzusetzen ist.
Wir werden einige Tests mit dem Testbrowser erstellen.
Zuerst benötigen wir einige Fixtures, damit Daten im Frontend zum Testen zur Verfügung stehen.
Erstelle dazu das Verzeichnis fixtures für die Applikationen recipes und userauth:
$ mkdir recipes/fixtures
$ mkdir userauth/fixtures
Dann erstellst du eine JSON-Datei mit den Models jeder Applikation:
$ python manage.py dumpdata recipes --indent 4 > recipes/fixtures/view_tests_data.json
$ python manage.py dumpdata auth --indent 4 > userauth/fixtures/test_users.json
Mit dem folgenden Kommando können wir diese Fixtures in einen Testserver laden und uns im Browser ansehen:
$ python manage.py testserver view_tests_data.json test_users.json
Creating test database for alias 'default'...
Installed 43 object(s) from 2 fixture(s)
Validating models...
0 errors found
Django version 1.3.1, using settings 'cookbook.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Damit die Frontend-Tests auch geladen werden müssen sie in recipes/tests/__init__.py importiert werden:
from view_tests import RecipeViewsTests
Nun erstellst du die Datei recipes/tests/view_tests.py mit folgendem Inhalt:
# -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse
from django.test import TestCase
from recipes.models import Recipe
class RecipeViewsTests(TestCase):
"""Test the views for the recipes"""
fixtures = ['view_tests_data.json', 'test_users.json']
def test_index(self):
"""Test the index view"""
response = self.client.get(reverse('recipes_recipe_index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Kochbuch', count=2)
self.assertNotContains(response, 'Cookbook',
msg_prefix='Found untranslated string in response')
self.assertTemplateUsed(response, 'recipes/index.html')
self.assertEqual(map(repr, response.context['object_list']),
map(repr, Recipe.objects.all()))
Die Funktion reverse importieren wir, damit wir die Namen der URLs auch auflösen können und diese nicht “hart” in den Test eintragen müssen.
Mit dem vom Testbrowser erzeugten Response-Objekt führen wir dann die Tests durch. Wir können sowohl das generierte HTML, die verwendeten Templates als auch den Kontext testen.
Um die Testsuite für das Frontend zu erweitern kannst du noch den folgenden Import:
from django.template.defaultfilters import slugify
und diese Testmethoden zur Klasse RecipeViewsTests hinzufügen:
def test_detail(self):
"""Test the detail view"""
recipe = Recipe.objects.all()[2]
response = self.client.get(recipe.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertContains(response, recipe.title, count=2,
msg_prefix='The response must contain the recipe title two times')
self.assertTemplateUsed(response, 'recipes/detail.html')
self.assertEqual(response.context['object'], recipe)
def test_detail_404(self):
"""Test a detail view with a missing recipe"""
response = self.client.get(reverse('recipes_recipe_detail',
kwargs={'slug': 'missing_recipe'}))
self.assertEqual(response.status_code, 404)
self.assertTemplateNotUsed(response, 'recipes/detail.html')
self.assertTemplateUsed(response, '404.html')
def test_add(self):
"""Test the add view which requires a login"""
username = password = 'admin'
login = self.client.login(username=username, password=password)
self.assertTrue(login, 'Login as "%s" using password "%s" failed.' %
(username, password))
add_url = reverse('recipes_recipe_add')
response = self.client.get(add_url)
self.assertEqual(response.status_code, 200)
post_data = {
'title': u'Spätzle',
'number_of_portions': 4,
'ingredients': u'Lorem ipsum',
'preparation': u'Lorem ipsum',
'difficulty': 2,
'category': 1
}
response = self.client.post(add_url, post_data)
redirect_url = reverse('recipes_recipe_detail',
kwargs={'slug': slugify(post_data['title'])})
self.assertRedirects(response, redirect_url)
self.assertTemplateNotUsed(response, 'recipes/form.html')
def test_add_302(self):
"""Test the add view without an authenticated user"""
self.client.logout()
response = self.client.get(reverse('recipes_recipe_add'))
self.assertEqual(response.status_code, 302)
self.assertTemplateNotUsed(response, 'recipes/form.html')
Die letzten beiden Tests test_add und test_add_302 demonstrieren das Versenden von POST-Daten mit dem Testbrowser, um die Formulare und die Authentifizierung zu testen.
Die Frontend-Tests können gezielt mit diesem Befehl aufgerufen werden:
$ python manage.py test recipes.RecipeViewsTests