Django
Examen de la unidad
Buscar..
Pruebas - un ejemplo completo
Esto supone que ha leído la documentación sobre cómo iniciar un nuevo proyecto de Django. Supongamos que la aplicación principal de su proyecto se llama td (abreviatura de prueba). Para crear su primera prueba, cree un archivo llamado test_view.py y copie y pegue el siguiente contenido en él.
from django.test import Client, TestCase
class ViewTest(TestCase):
def test_hello(self):
c = Client()
resp = c.get('/hello/')
self.assertEqual(resp.status_code, 200)
Puede ejecutar esta prueba por
./manage.py test
¡Y fallará naturalmente! Verás un error similar al siguiente.
Traceback (most recent call last):
File "/home/me/workspace/td/tests_view.py", line 9, in test_hello
self.assertEqual(resp.status_code, 200)
AssertionError: 200 != 404
¿Por qué sucede eso? ¡Porque no hemos definido una vista para eso! Hagamoslo. Cree un archivo llamado views.py y coloque en él el siguiente código
from django.http import HttpResponse
def hello(request):
return HttpResponse('hello')
Luego mapéelo a / hello / editando las direcciones URL como sigue:
from td import views
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^hello/', views.hello),
....
]
Ahora ejecuta la prueba otra vez ./manage.py test
otra vez y viola !!
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Probando Modelos Django Efectivamente
Asumiendo una clase
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('view_author', args=[str(self.id)])
class Book(models.Model):
author = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
private = models.BooleanField(default=false)
publish_date = models.DateField()
def get_absolute_url(self):
return reverse('view_book', args=[str(self.id)])
def __str__(self):
return self.name
Ejemplos de prueba
from django.test import TestCase
from .models import Book, Author
class BaseModelTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(BaseModelTestCase, cls).setUpClass()
cls.author = Author(name='hawking')
cls.author.save()
cls.first_book = Book(author=cls.author, name="short_history_of_time")
cls.first_book.save()
cls.second_book = Book(author=cls.author, name="long_history_of_time")
cls.second_book.save()
class AuthorModelTestCase(BaseModelTestCase):
def test_created_properly(self):
self.assertEqual(self.author.name, 'hawking')
self.assertEqual(True, self.first_book in self.author.book_set.all())
def test_absolute_url(self):
self.assertEqual(self.author.get_absolute_url(), reverse('view_author', args=[str(self.author.id)]))
class BookModelTestCase(BaseModelTestCase):
def test_created_properly(self:
...
self.assertEqual(1, len(Book.objects.filter(name__startswith='long'))
def test_absolute_url(self):
...
Algunos puntos
-
created_properly
pruebascreated_properly
se utilizan para verificar las propiedades de estado de los modelos de django. Ayudan a detectar situaciones en las que hemos cambiado los valores predeterminados, file_upload_paths, etc. -
absolute_url
puede parecer trivial, pero he descubierto que me ayudó a evitar algunos errores al cambiar las rutas de URL - De manera similar, escribo casos de prueba para todos los métodos implementados dentro de un modelo (utilizando objetos
mock
, etc.) - Al definir una
BaseModelTestCase
común, podemos configurar las relaciones necesarias entre los modelos para garantizar una prueba adecuada.
Finalmente, ante la duda, escribe una prueba. Los cambios de comportamiento triviales se captan prestando atención a los detalles y los fragmentos de código olvidados no terminan causando problemas innecesarios.
Pruebas de control de acceso en Django Views
tl; dr : crea una clase base que define dos objetos de usuario (por ejemplo, user
y another_user
). Crea tus otros modelos y define tres instancias de Client
.
-
self.client
: Representandouser
registrado en el navegador -
self.another_client
: Representandoanother_user
cliente deanother_user
-
self.unlogged_client
: Representa a una persona no registrada
Ahora acceda a todas sus direcciones URL públicas y privadas desde estos tres objetos de cliente y dicte la respuesta que espera. A continuación, muestro la estrategia para un objeto Book
que puede ser private
(propiedad de unos pocos usuarios privilegiados) o public
(visible para todos).
from django.test import TestCase, RequestFactory, Client
from django.core.urlresolvers import reverse
class BaseViewTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(BaseViewTestCase, cls).setUpClass()
cls.client = Client()
cls.another_client = Client()
cls.unlogged_client = Client()
cls.user = User.objects.create_user(
'dummy',password='dummy'
)
cls.user.save()
cls.another_user = User.objects.create_user(
'dummy2', password='dummy2'
)
cls.another_user.save()
cls.first_book = Book.objects.create(
name='first',
private = true
)
cls.first_book.readers.add(cls.user)
cls.first_book.save()
cls.public_book = Template.objects.create(
name='public',
private=False
)
cls.public_book.save()
def setUp(self):
self.client.login(username=self.user.username, password=self.user.username)
self.another_client.login(username=self.another_user.username, password=self.another_user.username)
"""
Only cls.user owns the first_book and thus only he should be able to see it.
Others get 403(Forbidden) error
"""
class PrivateBookAccessTestCase(BaseViewTestCase):
def setUp(self):
super(PrivateBookAccessTestCase, self).setUp()
self.url = reverse('view_book',kwargs={'book_id':str(self.first_book.id)})
def test_user_sees_own_book(self):
response = self.client.get(self.url)
self.assertEqual(200, response.status_code)
self.assertEqual(self.first_book.name,response.context['book'].name)
self.assertTemplateUsed('myapp/book/view_template.html')
def test_user_cant_see_others_books(self):
response = self.another_client.get(self.url)
self.assertEqual(403, response.status_code)
def test_unlogged_user_cant_see_private_books(self):
response = self.unlogged_client.get(self.url)
self.assertEqual(403, response.status_code)
"""
Since book is public all three clients should be able to see the book
"""
class PublicBookAccessTestCase(BaseViewTestCase):
def setUp(self):
super(PublicBookAccessTestCase, self).setUp()
self.url = reverse('view_book',kwargs={'book_id':str(self.public_book.id)})
def test_user_sees_book(self):
response = self.client.get(self.url)
self.assertEqual(200, response.status_code)
self.assertEqual(self.public_book.name,response.context['book'].name)
self.assertTemplateUsed('myapp/book/view_template.html')
def test_another_user_sees_public_books(self):
response = self.another_client.get(self.url)
self.assertEqual(200, response.status_code)
def test_unlogged_user_sees_public_books(self):
response = self.unlogged_client.get(self.url)
self.assertEqual(200, response.status_code)
La base de datos y las pruebas
Django usa una configuración especial de la base de datos cuando realiza pruebas, de modo que las pruebas pueden usar la base de datos normalmente, pero se ejecutan por defecto en una base de datos vacía. Los cambios en la base de datos en una prueba no serán vistos por otra. Por ejemplo, ambas de las siguientes pruebas pasarán:
from django.test import TestCase
from myapp.models import Thing
class MyTest(TestCase):
def test_1(self):
self.assertEqual(Thing.objects.count(), 0)
Thing.objects.create()
self.assertEqual(Thing.objects.count(), 1)
def test_2(self):
self.assertEqual(Thing.objects.count(), 0)
Thing.objects.create(attr1="value")
self.assertEqual(Thing.objects.count(), 1)
Accesorios
Si desea que los objetos de la base de datos utilicen varias pruebas, puede crearlos en el método de setUp
del caso de prueba. Además, si ha definido los accesorios en su proyecto de django, se pueden incluir así:
class MyTest(TestCase):
fixtures = ["fixture1.json", "fixture2.json"]
Por defecto, django está buscando accesorios en el directorio de fixtures
en cada aplicación. Se pueden configurar otros directorios utilizando la configuración FIXTURE_DIRS
:
# myapp/settings.py
FIXTURE_DIRS = [
os.path.join(BASE_DIR, 'path', 'to', 'directory'),
]
Supongamos que ha creado un modelo de la siguiente manera:
# models.py
from django.db import models
class Person(models.Model):
"""A person defined by his/her first- and lastname."""
firstname = models.CharField(max_length=255)
lastname = models.CharField(max_length=255)
Entonces tus accesorios .json podrían verse así:
# fixture1.json
[
{ "model": "myapp.person",
"pk": 1,
"fields": {
"firstname": "Peter",
"lastname": "Griffin"
}
},
{ "model": "myapp.person",
"pk": 2,
"fields": {
"firstname": "Louis",
"lastname": "Griffin"
}
},
]
Reutilizar la base de datos de prueba.
Para acelerar las pruebas de ejecución, puede indicar al comando de administración que reutilice la base de datos de prueba (y que evite que se cree antes y se elimine después de cada ejecución de prueba). Esto se puede hacer usando la marca keepdb (o taquigrafía -k
) de esta manera:
# Reuse the test-database (since django version 1.8)
$ python manage.py test --keepdb
Limitar el número de pruebas ejecutadas.
Es posible limitar las pruebas ejecutadas por manage.py test
especificando qué módulos debe descubrir el corredor de prueba:
# Run only tests for the app names "app1"
$ python manage.py test app1
# If you split the tests file into a module with several tests files for an app
$ python manage.py test app1.tests.test_models
# it's possible to dig down to individual test methods.
$ python manage.py test app1.tests.test_models.MyTestCase.test_something
Si desea ejecutar un montón de pruebas, puede pasar un patrón de nombres de archivos. Por ejemplo, es posible que desee ejecutar solo pruebas que involucren a sus modelos:
$ python manage.py test -p test_models*
Creating test database for alias 'default'...
.................................................
----------------------------------------------------------------------
Ran 115 tests in 3.869s
OK
Finalmente, es posible detener el conjunto de pruebas en el primer fallo, utilizando --failfast
. Este argumento permite obtener rápidamente el error potencial encontrado en la suite:
$ python manage.py test app1
...F..
----------------------------------------------------------------------
Ran 6 tests in 0.977s
FAILED (failures=1)
$ python manage.py test app1 --failfast
...F
======================================================================
[Traceback of the failing test]
----------------------------------------------------------------------
Ran 4 tests in 0.372s
FAILED (failures=1)