Herramientas de prueba

Django proporciona un pequeño conjunto de herramientas que resultan útiles cuando se escriben pruebas.

El cliente de pruebas

El cliente de pruebas es una clase de Python que actúa como un navegador web dummy, permitiéndote probar tus vistas y interactuar con tu aplicación Django programáticamente.

Algunas de las cosas que puedes hacer con el cliente de pruebas son:

  • Simular solicitudes GET y POST en una URL y observar la respuesta – desde los detalles de HTTP (cabeceras y códigos de estado) hasta el contenido de la página.

  • Ver la cadena de redirecciones (si las hay) y comprobar la URL y el código de estado en cada paso.

  • Probar que una solicitud determinada es renderizada por un template Django específico, con un contexto del template que contiene ciertos valores.

Ten en cuenta que el cliente de pruebas no está destinado a ser una sustitución para Selenium o otros «frameworks de navegador». El cliente de pruebas de Django tiene un foco diferente. En resumen:

  • Utiliza el cliente de pruebas de Django para establecer que se está utilizando el template correcto y que se le pasa al template el contexto de datos correcto.

  • Utiliza RequestFactory para probar funciones de vistas directamente, evitando las capas de routing y middleware.

  • Utiliza frameworks de navegador como Selenium para probar HTML renderizado y la funcionalidad de páginas web, es decir, la funcionalidad JavaScript. Django también proporciona soporte especial para esos frameworks; consulta la sección sobre LiveServerTestCase para obtener más detalles.

Un conjunto de pruebas integral debería utilizar una combinación de todos estos tipos de pruebas.

Resumen y ejemplo rápido

Para utilizar el cliente de pruebas, instancia django.test.Client y recupera páginas web:

>>> from django.test import Client
>>> c = Client()
>>> response = c.post("/login/", {"username": "john", "password": "smith"})
>>> response.status_code
200
>>> response = c.get("/customer/details/")
>>> response.content
b'<!DOCTYPE html...'

Como sugiere este ejemplo, puedes instanciar Client desde dentro de una sesión del intérprete interactivo de Python.

Ten en cuenta algunas cosas importantes sobre cómo funciona el cliente de pruebas:

  • El cliente de pruebas no requiere que esté corriendo el servidor web. De hecho, funcionará correctamente sin ningún servidor web corriendo en absoluto! Eso se debe a que evita la sobrecarga de HTTP y trata directamente con el marco Django. Esto ayuda a hacer que los tests unitarios se ejecuten rápidamente.

  • Al recuperar páginas, recuerda especificar el path de la URL, no todo el dominio. Por ejemplo, esto es correcto:

    >>> c.get("/login/")
    

    Esto es incorrecto:

    >>> c.get("https://www.example.com/login/")
    

    El cliente de pruebas no puede recuperar páginas web que no están impulsadas por tu proyecto Django. Si necesitas recuperar otras páginas web, utiliza un módulo estándar de la biblioteca de Python como urllib.

  • Para resolver URLs, el cliente de pruebas utiliza la configuración URL apuntada a tu ROOT_URLCONF establecimiento.

  • Aunque el ejemplo anterior funcionaría en el intérprete interactivo de Python, algunas de las funcionalidades del cliente de pruebas, especialmente aquellas relacionadas con plantillas, solo están disponibles mientras se ejecutan los tests.

    La razón por la que se hace esto es que el ejecutor de pruebas de Django realiza un poco de magia negra para determinar qué plantilla fue cargada por una vista dada. Esta magia negra (esencialmente, una parcheación del sistema de plantillas de Django en memoria) solo sucede durante la ejecución de las pruebas.

  • Por defecto, el cliente de pruebas deshabilitará cualquier verificación CSRF realizada por tu sitio.

    Si, por alguna razón, quieres que el cliente de pruebas realice verificaciones CSRF, puedes crear una instancia del cliente de pruebas que impone las verificaciones CSRF. Para hacer esto, pasa la argumento enforce_csrf_checks cuando construyas tu cliente:

    >>> from django.test import Client
    >>> csrf_client = Client(enforce_csrf_checks=True)
    

Hacer solicitudes

Utiliza la clase django.test.Client para hacer solicitudes.

class Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults)[fuente]

Un cliente HTTP de prueba. Recibe varios argumentos que pueden personalizar el comportamiento.

headers permite especificar encabezados predeterminados que se enviarán con cada solicitud. Por ejemplo, para establecer un encabezado User-Agent:

client = Client(headers={"user-agent": "curl/7.79.1"})

query_params permite especificar la cadena de consulta predeterminada que se establecerá en cada solicitud.

Argumentos clave arbitrarios en **defaults configuran variables WSGI. Por ejemplo, para establecer el nombre del script:

client = Client(SCRIPT_NAME="/app/")

Nota

Los argumentos clave que comienzan con un prefijo HTTP_ se establecen como encabezados, pero se prefiere utilizar el parámetro headers por razones de legibilidad.

Los valores de los argumentos headers, query_params y extra pasados a get(), post() , etc., tienen precedencia sobre los valores por defecto pasados al constructor de la clase.

El argumento enforce_csrf_checks se puede utilizar para probar la protección CSRF (consulte arriba).

El argumento raise_request_exception permite controlar si las excepciones levantadas durante la solicitud también deben ser levantadas en el test. Por defecto es True.

El argumento json_encoder permite establecer un codificador de JSON personalizado para la serialización JSON descrita en post().

Se agregó el argumento query_params.

Una vez que tengas una instancia de Client, puedes llamar a cualquiera de los siguientes métodos:

get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Realiza una solicitud GET en la ruta proporcionada path y devuelve un objeto Response, que se documenta a continuación.

Los pares clave-valor en el diccionario query_params se utilizan para establecer cadenas de consulta. Por ejemplo:

>>> c = Client()
>>> c.get("/customers/details/", query_params={"name": "fred", "age": 7})

…resultará en la evaluación de una solicitud GET equivalente a:

/customers/details/?name=fred&age=7

También es posible pasar estos parámetros al parámetro data. Sin embargo, query_params es preferible ya que funciona para cualquier método HTTP.

El parámetro headers se puede utilizar para especificar encabezados que deben ser enviados en la solicitud. Por ejemplo:

>>> c = Client()
>>> c.get(
...     "/customers/details/",
...     query_params={"name": "fred", "age": 7},
...     headers={"accept": "application/json"},
... )

…enviará el encabezado HTTP HTTP_ACCEPT a la vista de detalles, lo cual es una buena forma de probar rutas de código que utilizan el método django.http.HttpRequest.accepts().

Los argumentos arbitrarios de palabra clave establecen variables WSGI environ. Por ejemplo, encabezados para establecer el nombre del script:

>>> c = Client()
>>> c.get("/", SCRIPT_NAME="/app/")

Si ya tienes los argumentos GET en forma codificada, puedes utilizar esa codificación en lugar de usar el parámetro data. Por ejemplo, la solicitud GET anterior también podría ser presentada como:

>>> c = Client()
>>> c.get("/customers/details/?name=fred&age=7")

Si proporcionas una URL con ambos datos GET codificados y un parámetro query_params o data, estos argumentos tendrán prioridad.

Si estableces follow en True, el cliente seguirá cualquier redirección y se establecerá un atributo redirect_chain en el objeto de respuesta que contiene tuplas con las URL intermedias y los códigos de estado.

Si tenías una URL /redirect_me/ que redirigía a /next/, que luego redirigía a /final/, esto es lo que verías:

>>> response = c.get("/redirect_me/", follow=True)
>>> response.redirect_chain
[('http://testserver/next/', 302), ('http://testserver/final/', 302)]

Si estableces secure en True, el cliente emulará una solicitud HTTPS.

Se agregó el argumento query_params.

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Realiza una solicitud POST en la ruta proporcionada y devuelve un objeto de respuesta, que se documenta a continuación.

Las pares clave-valor en el diccionario data se utilizan para enviar datos POST. Por ejemplo:

>>> c = Client()
>>> c.post("/login/", {"name": "fred", "passwd": "secret"})

La evaluación de una solicitud POST a esta URL:

/login/

Con este datos POST:

name=fred&passwd=secret

Si proporcionas content_type como application/json, el data se serializa utilizando json.dumps() si es un diccionario, lista o tupla. La serialización se realiza con DjangoJSONEncoder por defecto y puede ser sobrescrita proporcionando un argumento json_encoder a Client. Esta serialización también ocurre para las solicitudes put(), patch(), y delete().

Si proporcionas cualquier otro content_type (por ejemplo, text/xml para un payload XML), los contenidos de data se envían tal cual en la solicitud POST, utilizando content_type en el encabezado HTTP Content-Type.

Si no proporcionas un valor para content_type, los valores en data se transmitirán con un tipo de contenido de multipart/form-data. En este caso, las pares clave-valor en data se codificarán como un mensaje multipart y se utilizarán para crear el payload de datos POST.

Para enviar múltiples valores para una clave dada – por ejemplo, para especificar las selecciones para un <select multiple> – proporciona los valores como lista o tupla para la clave requerida. Por ejemplo, este valor de data enviaría tres valores seleccionados para el campo llamado choices:

{"choices": ["a", "b", "d"]}

Enviar archivos es un caso especial. Para POSTear un archivo, solo necesitas proporcionar el nombre del campo de archivo como una clave y un manejador de archivo al archivo que deseas subir como valor. Por ejemplo, si tu formulario tiene campos name y attachment, el último es un FileField:

>>> c = Client()
>>> with open("wishlist.doc", "rb") as fp:
...     c.post("/customers/wishes/", {"name": "fred", "attachment": fp})
...

También puedes proporcionar cualquier objeto tipo archivo (por ejemplo, StringIO o BytesIO) como manejador de archivo. Si estás subiendo a un ImageField, el objeto necesita tener un atributo name que pase la validación validate_image_file_extension. Por ejemplo:

>>> from io import BytesIO
>>> img = BytesIO(
...     b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00"
...     b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00"
... )
>>> img.name = "myimage.gif"

Ten en cuenta que si deseas utilizar el mismo manejador de archivo para múltiples llamadas a post() entonces necesitarás restablecer manualmente el puntero del archivo entre posts. La forma más fácil de hacer esto es cerrar manualmente el archivo después de haberlo proporcionado a post(), como se demuestra arriba.

También asegúrate de que el archivo esté abierto de una manera que permita leer los datos. Si tu archivo contiene datos binarios como una imagen, esto significa que necesitarás abrir el archivo en modo rb (lectura binaria).

Los textos traducidos son:

Si la URL que solicitas con un POST contiene parámetros codificados, estos parámetros estarán disponibles en los datos de solicitud.GET. Por ejemplo, si hicieras la solicitud:

>>> c.post(
...     "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"}
... )

… la vista que maneja esta solicitud podría interrogar request.POST para recuperar el nombre de usuario y contraseña, y podría interrogar request.GET para determinar si el usuario era un visitante.

Si estableces follow en True, el cliente seguirá cualquier redirección y se establecerá un atributo redirect_chain en el objeto de respuesta que contiene tuplas con las URL intermedias y los códigos de estado.

Si estableces secure en True, el cliente emulará una solicitud HTTPS.

Se agregó el argumento query_params.

head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud HEAD en la ruta proporcionada path y devuelve un objeto Response. Este método funciona igual que Client.get(), incluyendo los parámetros follow, secure, cabeceras, parámetros de consulta, y extra, excepto que no devuelve un cuerpo de mensaje.

Se agregó el argumento query_params.

options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud OPTIONS en la ruta proporcionada path y devuelve un objeto Response. Útil para probar interfaces RESTful.

Cuando se proporciona data, se utiliza como cuerpo de solicitud, y se establece un encabezado Content-Type a content_type.

Los parámetros follow, secure, cabeceras, parámetros de consulta, y extra actúan igual que para Client.get().

Se agregó el argumento query_params.

put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud PUT en la ruta proporcionada path y devuelve un objeto Response. Útil para probar interfaces RESTful.

Cuando se proporciona data, se utiliza como cuerpo de solicitud, y se establece un encabezado Content-Type a content_type.

Los parámetros follow, secure, cabeceras, parámetros de consulta, y extra actúan igual que para Client.get().

Se agregó el argumento query_params.

patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud PATCH en la ruta proporcionada path y devuelve un objeto Response. Útil para probar interfaces RESTful.

Los parámetros follow, secure, cabeceras, parámetros de consulta, y extra actúan igual que para Client.get().

Se agregó el argumento query_params.

delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud DELETE en la ruta proporcionada path y devuelve un objeto Response. Útil para probar interfaces RESTful.

Cuando se proporciona data, se utiliza como cuerpo de solicitud, y se establece un encabezado Content-Type a content_type.

Los parámetros follow, secure, cabeceras, parámetros de consulta, y extra actúan igual que para Client.get().

Se agregó el argumento query_params.

trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra)[fuente]

Hace una solicitud TRACE sobre el path proporcionado y devuelve un objeto de respuesta Response. Útil para simular sondas diagnósticas.

A diferencia de los otros métodos de solicitud, data no se proporciona como parámetro de palabra clave con el fin de cumplir con RFC 9110 Section 9.3.8, que establece que las solicitudes TRACE deben no tener un cuerpo.

Los parámetros follow, secure, cabeceras, parámetros de consulta, y extra actúan igual que para Client.get().

Se agregó el argumento query_params.

login(**credentials)
alogin(**credentials)

Versión asíncrona: alogin()

Si tu sitio utiliza el sistema de autenticación de Django (sistema de autenticación) y manejas la autenticación de usuarios, puedes utilizar el método login() del cliente de prueba para simular el efecto de que un usuario se loguee en el sitio.

Después de llamar a este método, el cliente de prueba tendrá todos los datos de cookies y sesión necesarios para pasar cualquier prueba basada en la autenticación que pueda formar parte de una vista.

El formato del argumento credentials depende del backend de autenticación que estés utilizando (que está configurado por tu AUTHENTICATION_BACKENDS configuración). Si estás utilizando el backend de autenticación estándar proporcionado por Django (ModelBackend), credentials debería ser el nombre de usuario y la contraseña del usuario, proporcionados como argumentos de palabra clave:

>>> c = Client()
>>> c.login(username="fred", password="secret")

# Now you can access a view that's only available to logged-in users.

Si estás utilizando un backend de autenticación diferente, este método puede requerir diferentes credenciales. Requiere las credenciales que requiere el método authenticate() de tu backend.

login() devuelve True si las credenciales fueron aceptadas y la autenticación fue exitosa.

Finalmente, necesitarás recordar crear cuentas de usuario antes de poder utilizar este método. Como explicamos anteriormente, el ejecutor de pruebas se ejecuta utilizando una base de datos de prueba, que contiene por defecto ningún usuario. Como resultado, las cuentas de usuario válidas en tu sitio de producción no funcionarán bajo condiciones de prueba. Necesitarás crear usuarios como parte del conjunto de pruebas – ya sea manualmente (utilizando la API de modelos Django) o con un fijador de prueba. Recuerda que si deseas que el usuario de prueba tenga una contraseña, no puedes establecer la contraseña del usuario estableciendo directamente el atributo de contraseña – debes utilizar la función set_password() para almacenar una contraseña correctamente hasheada. Alternativamente, puedes utilizar la create_user() función auxiliar para crear un nuevo usuario con una contraseña correctamente hasheada.

force_login(user, backend=None)
aforce_login(user, backend=None)

Versión asíncrona: aforce_login()

Si tu sitio utiliza el sistema de autenticación de Django (sistema de autenticación), puedes utilizar el método force_login() para simular el efecto de que un usuario se loguee en el sitio. Utiliza este método en lugar de login() cuando una prueba requiera que un usuario esté conectado y los detalles de cómo un usuario se conectó no sean importantes.

Esta función no realiza la autenticación y verificación como login(), por lo que usuarios inactivos (is_active=False) pueden iniciar sesión sin necesidad de proporcionar credenciales del usuario.

El atributo backend del usuario se establecerá en el valor del argumento backend (que debe ser una cadena de ruta de Python con puntos), o a settings.AUTHENTICATION_BACKENDS[0] si no se proporciona un valor. La función authenticate() llamada por login() normalmente anota al usuario de esta manera.

Esta función es más rápida que login() ya que evita los costosos algoritmos de hashing de contraseñas. Además, puedes acelerar login() utilizando un hasher más débil mientras se prueban.

logout()
alogout()

Versión asíncrona: alogout()

Si tu sitio utiliza el sistema de autenticación de Django (sistema de autenticación), el método logout() puede utilizarse para simular el efecto de un usuario cerrando sesión en tu sitio.

Después de llamar a esta función, el cliente de prueba tendrá todos los datos de cookies y sesión eliminados por defecto. Las solicitudes posteriores parecerán provenir de un AnonymousUser.

Pruebas de respuestas

Los métodos get() y post() devuelven ambos un objeto Response. Este objeto Response no es el mismo que el objeto HttpResponse devuelto por las vistas de Django; el objeto de respuesta de prueba tiene algunos datos adicionales útiles para verificar en el código de prueba.

En particular, un objeto Response tiene los siguientes atributos:

class Response
client

El cliente de prueba utilizado para realizar la solicitud que resultó en la respuesta.

content

El cuerpo de la respuesta, como una cadena de bytes. Este es el contenido final de la página tal como se ha renderizado por la vista o cualquier mensaje de error.

context

La instancia del template Context que se utilizó para renderizar el template que produjo el contenido de la respuesta.

Si la página renderizada utilizaba múltiples templates, entonces context será una lista de objetos Context, en el orden en que fueron renderizados.

Independientemente del número de templates utilizados durante la renderización, puedes recuperar valores de contexto utilizando el operador []. Por ejemplo, la variable de contexto name podría recuperarse utilizando:

>>> response = client.get("/foo/")
>>> response.context["name"]
'Arthur'

¿No se están utilizando plantillas Django?

Esta propiedad solo está poblada cuando se utiliza el backend de plantillas DjangoTemplates. Si estás utilizando otro motor de plantillas, la propiedad context_data del objeto de respuesta puede ser una alternativa adecuada en respuestas con esa atributo.

exc_info

Una tupla de tres valores que proporciona información sobre la excepción no tratada, si es que ocurrió durante la vista.

Los valores son (type, value, traceback), los mismos que devuelve Python mediante la función sys.exc_info(). Sus significados son:

  • type: El tipo de la excepción.

  • value: La instancia de la excepción.

  • traceback: Un objeto de seguimiento que encapsula el stack de llamadas en el punto donde originalmente ocurrió la excepción.

Si no ocurrió ninguna excepción, entonces exc_info será None.

json(**kwargs)

El cuerpo de la respuesta, parseado como JSON. Se pasan argumentos de palabra clave adicionales a json.loads(). Por ejemplo:

>>> response = client.get("/foo/")
>>> response.json()["name"]
'Arthur'

Si el encabezado Content-Type no es "application/json", entonces se levantará una ValueError cuando se intente parsear la respuesta.

request

Los datos de solicitud que estimularon la respuesta.

wsgi_request

La instancia del objeto WSGIRequest generada por el manipulador de prueba que generó la respuesta.

status_code

El estado HTTP de la respuesta, como un entero. Para obtener una lista completa de códigos definidos, consulte el registro de códigos de estado IANA.

templates

Una lista de instancias Template utilizadas para renderizar el contenido final, en el orden en que se renderizaron. Para cada plantilla en la lista, utilice template.name para obtener el nombre del archivo de la plantilla, si la plantilla se cargó desde un archivo. (El nombre es una cadena como 'admin/index.html'.)

¿No se están utilizando plantillas Django?

Esta propiedad solo está poblada cuando se utiliza el backend DjangoTemplates. Si estás utilizando otro motor de plantillas, la propiedad template_name puede ser una alternativa adecuada si solo necesitas obtener el nombre de la plantilla utilizada para renderizar.

resolver_match

Una instancia de ResolverMatch para la respuesta. Puedes utilizar la propiedad func para verificar, por ejemplo, la vista que sirvió la respuesta:

# my_view here is a function based view.
self.assertEqual(response.resolver_match.func, my_view)

# Class-based views need to compare the view_class, as the
# functions generated by as_view() won't be equal.
self.assertIs(response.resolver_match.func.view_class, MyView)

Si la URL dada no se encuentra, acceder a esta propiedad levantará una excepción Resolver404.

Así como con una respuesta normal, también puedes acceder a los encabezados mediante HttpResponse.headers. Por ejemplo, podrías determinar el tipo de contenido de la respuesta utilizando response.headers['Content-Type'].

Excepciones

Si apuntas al cliente de pruebas hacia una vista que lanza una excepción y Client.raise_request_exception es True, esa excepción será visible en el caso de prueba. Puedes utilizar un bloque try ... except estándar o assertRaises() para probar las excepciones.

Las únicas excepciones que no son visibles al cliente de pruebas son Http404, PermissionDenied, SystemExit y SuspiciousOperation. Django atrapa estas excepciones internamente y las convierte en los códigos de respuesta HTTP adecuados. En estos casos, puedes comprobar response.status_code en tu prueba.

Si Client.raise_request_exception es False, el cliente de pruebas devolverá una respuesta 500 como la que se devuelve a un navegador. La respuesta tiene el atributo exc_info para proporcionar información sobre la excepción no manejada.

Estado persistente

El cliente de pruebas es estatal. Si una respuesta devuelve una cookie, esa cookie se almacenará en el cliente de pruebas y se enviará con todos los solicitudes get() y post() posteriores.

Las políticas de expiración para estas cookies no se siguen. Si quieres que una cookie expire, borrala manualmente o crea un nuevo objeto Client (lo que borrará efectivamente todas las cookies).

Un cliente de pruebas tiene atributos que almacenan información del estado persistente. Puedes acceder a estas propiedades como parte de la condición de prueba.

Client.cookies

Un objeto SimpleCookie de Python, que contiene los valores actuales de todas las cookies del cliente. Consulta la documentación del módulo http.cookies para obtener más información.

Client.session

Un objeto similar a un diccionario que contiene información de sesión. Consulte la documentación sobre sesiones <topics/http/sessions> para obtener detalles completos.

Para modificar la sesión y luego guardarla, debe almacenarse en una variable primero (porque se crea un nuevo SessionStore cada vez que se accede a esta propiedad):

def test_something(self):
    session = self.client.session
    session["somekey"] = "test"
    session.save()
Client.asession()

Esto es similar al atributo session pero funciona en contextos asíncronos.

Configuración de idioma

Al realizar pruebas de aplicaciones que admiten internacionalización y localización, puede desear establecer el idioma para una solicitud del cliente de prueba. El método para hacerlo depende de si está habilitado o no el middleware LocaleMiddleware.

Si está habilitado el middleware, el idioma se puede configurar creando un cookie con el nombre de LANGUAGE_COOKIE_NAME y un valor del código de idioma:

from django.conf import settings


def test_language_using_cookie(self):
    self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"})
    response = self.client.get("/")
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

o incluyendo la cabecera HTTP Accept-Language en la solicitud:

def test_language_using_header(self):
    response = self.client.get("/", headers={"accept-language": "fr"})
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

Nota

Al utilizar estos métodos, asegúrese de restablecer el idioma activo al final de cada prueba:

def tearDown(self):
    translation.activate(settings.LANGUAGE_CODE)

Más detalles se encuentran en Cómo Django descubre la preferencia del idioma.

Si no está habilitado el middleware, puede configurar el idioma activo utilizando la función translation.override().

from django.utils import translation


def test_language_using_override(self):
    with translation.override("fr"):
        response = self.client.get("/")
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

Los detalles adicionales se encuentran en Establecer explícitamente el idioma activo.

Ejemplo

El siguiente es un test unitario utilizando el cliente de pruebas:

import unittest
from django.test import Client


class SimpleTest(unittest.TestCase):
    def setUp(self):
        # Every test needs a client.
        self.client = Client()

    def test_details(self):
        # Issue a GET request.
        response = self.client.get("/customer/details/")

        # Check that the response is 200 OK.
        self.assertEqual(response.status_code, 200)

        # Check that the rendered context contains 5 customers.
        self.assertEqual(len(response.context["customers"]), 5)

Clases de casos de prueba proporcionados

Las clases de test unitarios normales extienden una clase base de unittest.TestCase. Django proporciona algunas extensiones de esta clase base:

Jerarquía de las clases de pruebas unitarias de Django (subclases de TestCase)

Jerarquía de las clases de pruebas unitarias de Django

Puedes convertir una clase de test normal unittest.TestCase a cualquier de las subclases: cambia la clase base de tu test desde unittest.TestCase a la subclase. Todos los funcionalidades estándar de las pruebas unitarias de Python estarán disponibles, y se agregarán algunas adiciones útiles como se describe en cada sección a continuación.

SimpleTestCase

class SimpleTestCase[fuente]

Una subclase de unittest.TestCase que agrega esta funcionalidad:

Si tus pruebas realizan consultas a la base de datos, utiliza subclases TransactionTestCase o TestCase.

SimpleTestCase.databases

SimpleTestCase prohíbe las consultas a la base de datos por defecto. Esto ayuda a evitar ejecutar consultas de escritura que afectarán otras pruebas ya que cada prueba SimpleTestCase no se ejecuta en una transacción. Si no te preocupa este problema, puedes deshabilitarlo estableciendo la clase de atributo databases a '__all__' en tu clase de prueba.

Advertencia

SimpleTestCase y sus subclases (por ejemplo, TestCase, …) dependen de setUpClass() y tearDownClass() para realizar algunas inicializaciones de clase amplias (por ejemplo, sobreescribir configuraciones). Si necesitas sobreescribir esos métodos, no olvides llamar a la implementación del super:

class MyTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        ...

    @classmethod
    def tearDownClass(cls):
        ...
        super().tearDownClass()

Asegúrate de tener en cuenta el comportamiento de Python si se levanta una excepción durante setUpClass(). Si eso sucede, ni las pruebas de la clase ni tearDownClass() se ejecutan. En el caso de django.test.TestCase, esto provocará un leak de transacción creada en super() lo que resulta en varios síntomas incluyendo una falla de segmentación en algunas plataformas (informada en macOS). Si quieres levantar intencionalmente una excepción como unittest.SkipTest en setUpClass(), asegúrate de hacerlo antes de llamar a super() para evitar esto.

TransactionTestCase

class TransactionTestCase[fuente]

TransactionTestCase hereda de SimpleTestCase para agregar algunas características específicas de la base de datos:

  • Reiniciando la base de datos a un estado conocido al final de cada prueba para facilitar la prueba y utilizando el ORM.

  • Database fixtures.

  • Prueba: ref:saltarse en función de características del motor de base de datos <skipping-tests>.

  • Los métodos especializados restantes assert* .

La clase TestCase de Django es una subclase más común de TransactionTestCase que utiliza las facilidades de transacciones de base de datos para acelerar el proceso de reiniciar la base de datos a un estado conocido al final de cada prueba. Sin embargo, como consecuencia de esto, no se pueden probar algunos comportamientos de la base de datos dentro de una clase TestCase de Django. Por ejemplo, no puedes probar que un bloque de código está ejecutándose dentro de una transacción, lo cual es necesario cuando se utiliza select_for_update(). En esos casos, debes utilizar TransactionTestCase.

TransactionTestCase y TestCase son idénticos excepto por la forma en que la base de datos se reinicia a un estado conocido y la capacidad para el código de prueba de probar los efectos de commit y rollback:

  • Un TransactionTestCase reinicia la base de datos después de que se ejecuta la prueba mediante la truncación de todas las tablas. Un TransactionTestCase puede llamar a commit y rollback y observar los efectos de estas llamadas en la base de datos.

  • Por otro lado, un TestCase no trunca tablas después de una prueba. En su lugar, encierra el código de prueba dentro de una transacción de base de datos que se vuelve a cargar al final de la prueba. Esto garantiza que la vuelta atrás al final de la prueba restaura la base de datos a su estado inicial.

Advertencia

Un TestCase ejecutándose en una base de datos que no admite rollback (por ejemplo, MySQL con el motor de almacenamiento MyISAM), y todas las instancias de TransactionTestCase, volverán a cargar al final de la prueba eliminando todos los datos de la base de datos de prueba.

Las apps no verán su data recargada; si necesitas esta funcionalidad (por ejemplo, las apps terceras partes deben habilitar esto) puedes establecer serialized_rollback = True dentro del cuerpo de la clase TestCase.

TestCase

class TestCase[fuente]

Este es el clase más común para utilizar al escribir pruebas en Django. Hereda de TransactionTestCase (y por extensión de SimpleTestCase). Si tu aplicación de Django no utiliza una base de datos, utilice SimpleTestCase.

La clase:

  • Envuelve las pruebas dentro de dos bloques anidados atomic(): uno para toda la clase y otro para cada prueba. Por lo tanto, si deseas probar algún comportamiento específico de transacciones de base de datos, utilice TransactionTestCase.

  • Verifica las restricciones de base de datos diferibles al final de cada prueba.

También proporciona un método adicional:

classmethod TestCase.setUpTestData()[fuente]

El bloque atomic a nivel de clase descrito anteriormente permite la creación de datos iniciales a nivel de clase, una vez para toda la TestCase. Esta técnica permite pruebas más rápidas en comparación con el uso de setUp().

Por ejemplo:

from django.test import TestCase


class MyTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        # Set up data for the whole TestCase
        cls.foo = Foo.objects.create(bar="Test")
        ...

    def test1(self):
        # Some test using self.foo
        ...

    def test2(self):
        # Some other test using self.foo
        ...

Ten en cuenta que si las pruebas se ejecutan en una base de datos sin soporte de transacciones (por ejemplo, MySQL con el motor MyISAM), setUpTestData() se llamará antes de cada prueba, lo que anula los beneficios de velocidad.

Los objetos asignados a atributos de clase en setUpTestData() deben admitir la creación de copias profundas mediante copy.deepcopy() para aislarlos de las alteraciones realizadas por cada método de prueba.

classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[fuente]

Devuelve un administrador de contexto que captura callbacks de transaction.on_commit() <django.db.transaction.on_commit> para la conexión de base de datos dada. Devuelve una lista que contiene, al salir del contexto, las funciones de callback capturadas. A partir de esta lista puedes hacer afirmaciones sobre los callbacks o llamarlos para invocar sus efectos laterales, emulando un commit.

using es el alias de la conexión de base de datos para capturar callbacks.

Si execute es True, todos los callbacks se llamarán como el administrador de contexto sale, si no ocurrió ninguna excepción. Esto emula un commit después del bloque de código envuelto.

Por ejemplo:

from django.core import mail
from django.test import TestCase


class ContactTests(TestCase):
    def test_post(self):
        with self.captureOnCommitCallbacks(execute=True) as callbacks:
            response = self.client.post(
                "/contact/",
                {"message": "I like your site"},
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(callbacks), 1)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "Contact Form")
        self.assertEqual(mail.outbox[0].body, "I like your site")

LiveServerTestCase

class LiveServerTestCase[fuente]

LiveServerTestCase hace básicamente lo mismo que TransactionTestCase con una característica adicional: lanza un servidor Django en vivo en segundo plano en la configuración y lo cierra en el desmontaje. Esto permite el uso de clientes de prueba automatizados distintos del cliente de prueba de Django como, por ejemplo, el cliente Selenium, para ejecutar una serie de pruebas funcionales dentro de un navegador y simular las acciones de un usuario real.

El servidor en vivo escucha en localhost y se une a puerto 0 que utiliza un puerto libre asignado por el sistema operativo. La URL del servidor puede ser accedida con self.live_server_url durante los tests.

Para demostrar cómo utilizar LiveServerTestCase, escribamos una prueba de Selenium. Primero, necesitamos instalar la selenium paqueta:

$ python -m pip install "selenium >= 4.8.0"

Luego, agreguemos una prueba basada en LiveServerTestCase a nuestro módulo de tests (por ejemplo: myapp/tests.py). Para este ejemplo, asumiremos que estamos utilizando el staticfiles app y queremos servir archivos estáticos durante la ejecución de los tests de manera similar a lo que obtenemos en tiempo de desarrollo con DEBUG=True, es decir, sin tener que recopilarlos usando collectstatic. Usaremos la clase StaticLiveServerTestCase que proporciona esa funcionalidad. Reemplacézala por django.test.LiveServerTestCase si no necesitas eso.

El código para esta prueba puede verse como sigue:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver


class MySeleniumTests(StaticLiveServerTestCase):
    fixtures = ["user-data.json"]

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.selenium = WebDriver()
        cls.selenium.implicitly_wait(10)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super().tearDownClass()

    def test_login(self):
        self.selenium.get(f"{self.live_server_url}/login/")
        username_input = self.selenium.find_element(By.NAME, "username")
        username_input.send_keys("myuser")
        password_input = self.selenium.find_element(By.NAME, "password")
        password_input.send_keys("secret")
        self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()

Finalmente, puedes ejecutar la prueba de la siguiente manera:

$ ./manage.py test myapp.tests.MySeleniumTests.test_login

Este ejemplo abrirá automáticamente Firefox luego irá a la página de inicio, ingresa las credenciales y presiona el botón «Iniciar sesión». Selenium ofrece otros conductores en caso de que no tengas instalado Firefox o desees utilizar otro navegador. El ejemplo anterior es solo una pequeña parte de lo que puede hacer el cliente Selenium; consulta la referencia completa para obtener más detalles.

Nota

Cuando se utiliza una base de datos SQLite en memoria para ejecutar los tests, la misma conexión a la base de datos será compartida por dos hilos en paralelo: el hilo en el que se ejecuta el servidor en vivo y el hilo en el que se ejecuta el caso de prueba. Es importante prevenir consultas simultáneas a la base de datos mediante esta conexión compartida por los dos hilos, ya que eso puede causar ocasionalmente que los tests fallen al azar. Por lo tanto, debes asegurarte de que los dos hilos no acceden a la base de datos al mismo tiempo. En particular, esto significa que en algunos casos (por ejemplo, justo después de hacer clic en un enlace o enviar un formulario), podrías necesitar verificar que se recibe una respuesta por Selenium y que la siguiente página está cargada antes de proceder con la ejecución del test. Hazlo, por ejemplo, haciendo que Selenium espere a que el etiqueta HTML <body> esté encontrada en la respuesta (requiere Selenium > 2.13):

def test_login(self):
    from selenium.webdriver.support.wait import WebDriverWait

    timeout = 2
    ...
    self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
    # Wait until the response is received
    WebDriverWait(self.selenium, timeout).until(
        lambda driver: driver.find_element(By.TAG_NAME, "body")
    )

La traducción de los textos es la siguiente:

Características de las pruebas unitarias

Cliente de prueba por defecto

SimpleTestCase.client

Cada caso de prueba en una instancia de django.test.*TestCase tiene acceso a una instancia del cliente de prueba de Django. Este cliente se puede acceder como self.client. Este cliente se recrea para cada prueba, por lo que no tienes que preocuparte por el estado (como las cookies) que se transfiere de una prueba a otra.

Esto significa que en lugar de instanciar un Client en cada prueba:

import unittest
from django.test import Client


class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get("/customer/details/")
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        client = Client()
        response = client.get("/customer/index/")
        self.assertEqual(response.status_code, 200)

…puedes referirte a self.client, como se muestra a continuación:

from django.test import TestCase


class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get("/customer/details/")
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get("/customer/index/")
        self.assertEqual(response.status_code, 200)

Personalización del cliente de prueba

SimpleTestCase.client_class

Si deseas utilizar una clase de Client diferente (por ejemplo, una subclase con comportamiento personalizado), utiliza la client_class atributo de clase:

from django.test import Client, TestCase


class MyTestClient(Client):
    # Specialized methods for your environment
    ...


class MyTest(TestCase):
    client_class = MyTestClient

    def test_my_stuff(self):
        # Here self.client is an instance of MyTestClient...
        call_some_test_code()

Carga de fijadores

TransactionTestCase.fixtures

Una clase de caso de prueba para un sitio web respaldado por base de datos no es muy útil si no hay datos en la base de datos. Las pruebas son más legibles y es más mantenible crear objetos utilizando el ORM, por ejemplo en TestCase.setUpTestData(), sin embargo, también puedes utilizar fijadores.

Los textos traducidos son:

La forma más directa de crear una fijación es utilizar el comando manage.py dumpdata . Esto asume que ya tienes algunos datos en tu base de datos. Consulta la documentación del comando dumpdata para obtener más detalles.

Una vez creada una fijación y colocada en un directorio fixtures en uno de tus INSTALLED_APPS, puedes utilizarla en tus pruebas unitarias especificando una clase fixtures como atributo en tu subclase de django.test.TestCase

from django.test import TestCase
from myapp.models import Animal


class AnimalTestCase(TestCase):
    fixtures = ["mammals.json", "birds"]

    def setUp(self):
        # Test definitions as before.
        call_setup_methods()

    def test_fluffy_animals(self):
        # A test that uses the fixtures.
        call_some_test_code()

Aquí está específicamente lo que sucederá:

  • Durante setUpClass(), todas las fijaciones nombradas se instalan. En este ejemplo, Django instalará cualquier fijación JSON nombrada mammals, seguida de cualquier fijación nombrada birds. Consulta el tema Ficheros de pruebas para obtener más detalles sobre la definición y instalación de fijaciones.

Para la mayoría de las pruebas unitarias utilizando TestCase, Django no necesita hacer nada más, porque se utilizan transacciones para limpiar la base de datos después de cada prueba por razones de rendimiento. Pero para TransactionTestCase, se producirán las siguientes acciones:

  • Al final de cada prueba, Django vaciará la base de datos, devolviendo la base de datos al estado en el que estaba directamente después de llamarse a migrate.

  • Para cada prueba subsiguiente, se recargarán las fijaciones antes de ejecutar setUp().

En cualquier caso, puedes estar seguro de que el resultado de una prueba no será afectado por otra prueba o por el orden de la ejecución de las pruebas.

Por defecto, las fijaciones solo se cargan en la base de datos default. Si estás utilizando múltiples bases de datos y estableces TransactionTestCase.databases, las fijaciones se cargarán en todas las bases de datos especificadas.

Los textos traducidos son:

Configuración de URLconf

Si tu aplicación proporciona vistas, es posible que desees incluir pruebas que utilicen el cliente de prueba para ejercitar esas vistas. Sin embargo, un usuario final está libre de desplegar las vistas en tu aplicación en cualquier URL que elija. Esto significa que tus pruebas no pueden confiar en el hecho de que tus vistas estarán disponibles en una URL particular. Adorna tu clase de prueba o método de prueba con @override_settings(ROOT_URLCONF=...) para la configuración de URLconf.

Soporte para múltiples bases de datos

TransactionTestCase.databases

Django configura una base de datos de prueba correspondiente a cada base de datos que se defina en la definición DATABASES en tus ajustes y referida por lo menos por una prueba a través de databases.

Sin embargo, una gran parte del tiempo necesario para ejecutar un caso de prueba Django TestCase es consumido por la llamada a flush que garantiza que tengas una base de datos limpia al final de cada ejecución de pruebas. Si tienes múltiples bases de datos, se requieren múltiples descargas (una por cada base de datos), lo cual puede ser un tiempo consumidor – especialmente si tus pruebas no necesitan probar la actividad de múltiples bases de datos.

Como optimización, Django solo descarga la default base de datos al final de cada ejecución de pruebas. Si tu configuración contiene múltiples bases de datos y tienes una prueba que requiere que todas las bases de datos estén limpias, puedes utilizar el atributo databases en el conjunto de pruebas para solicitar bases de datos adicionales para ser descargadas.

Por ejemplo:

class TestMyViews(TransactionTestCase):
    databases = {"default", "other"}

    def test_index_page_view(self):
        call_some_test_code()

Esta clase de caso de prueba descarga las bases de datos de prueba default y other después de ejecutar test_index_page_view. También puedes utilizar '__all__' para especificar que todas las bases de datos de prueba deben ser descargadas.

La bandera databases también controla qué bases de datos se cargan los TransactionTestCase.fixtures. Por defecto, los fijaciones solo se cargan en la base de datos default.

Las consultas contra bases de datos que no estén en databases darán errores de aserción para prevenir el escape de estado entre pruebas.

TestCase.databases

Por defecto, solo la base de datos default se envolverá en una transacción durante la ejecución de un caso de prueba y los intentos de consultar otras bases de datos darán lugar a errores de aserción para prevenir el escape de estado entre pruebas.

Utiliza la atributo de clase databases del testeo para solicitar que se envuelva en transacciones contra bases de datos no default.

Por ejemplo:

class OtherDBTests(TestCase):
    databases = {"other"}

    def test_other_db_query(self): ...

Este caso de prueba solo permitirá consultas contra la base de datos other. Al igual que para SimpleTestCase.databases y TransactionTestCase.databases, el constante '__all__' se puede utilizar para especificar que el testeo debe permitir consultas a todas las bases de datos.

Sobreescribiendo configuraciones

Advertencia

Utiliza las funciones a continuación para alterar temporalmente el valor de configuraciones en los tests. No manipules django.conf.settings directamente ya que Django no restaurará los valores originales después de tales manipulaciones.

SimpleTestCase.settings()[fuente]

A menudo es útil cambiar una configuración temporalmente y volver al estado original después de ejecutar el código de pruebas. Para este caso de uso, Django proporciona un contexto manager estándar de Python (ver PEP 343) llamado settings(), que se puede utilizar de la siguiente manera:

from django.test import TestCase


class LoginTestCase(TestCase):
    def test_login(self):
        # First check for the default behavior
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/accounts/login/?next=/sekrit/")

        # Then override the LOGIN_URL setting
        with self.settings(LOGIN_URL="/other/login/"):
            response = self.client.get("/sekrit/")
            self.assertRedirects(response, "/other/login/?next=/sekrit/")

Este ejemplo sobreescribirá la configuración LOGIN_URL para el código dentro del bloque with y restablecerá su valor al estado anterior después.

SimpleTestCase.modify_settings()[fuente]

Puede resultar inmanejable redefinir configuraciones que contienen una lista de valores. En la práctica, agregar o eliminar valores es a menudo suficiente. Django proporciona el contexto manager modify_settings() para cambios en configuraciones más fáciles:

from django.test import TestCase


class MiddlewareTestCase(TestCase):
    def test_cache_middleware(self):
        with self.modify_settings(
            MIDDLEWARE={
                "append": "django.middleware.cache.FetchFromCacheMiddleware",
                "prepend": "django.middleware.cache.UpdateCacheMiddleware",
                "remove": [
                    "django.contrib.sessions.middleware.SessionMiddleware",
                    "django.contrib.auth.middleware.AuthenticationMiddleware",
                    "django.contrib.messages.middleware.MessageMiddleware",
                ],
            }
        ):
            response = self.client.get("/")
            # ...

Para cada acción, puedes suministrar una lista de valores o una cadena. Cuando el valor ya existe en la lista, append y prepend no tienen efecto; tampoco lo tiene remove cuando el valor no existe.

override_settings(**kwargs)[fuente]

En caso de que quieras sobreescribir una configuración para un método de testeo, Django proporciona el decorador override_settings() (ver PEP 318). Se utiliza de la siguiente manera:

from django.test import TestCase, override_settings


class LoginTestCase(TestCase):
    @override_settings(LOGIN_URL="/other/login/")
    def test_login(self):
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/other/login/?next=/sekrit/")

El decorador también se puede aplicar a las clases de TestCase:

from django.test import TestCase, override_settings


@override_settings(LOGIN_URL="/other/login/")
class LoginTestCase(TestCase):
    def test_login(self):
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/other/login/?next=/sekrit/")
modify_settings(*args, **kwargs)[fuente]

De manera similar, Django proporciona el decorador modify_settings():

from django.test import TestCase, modify_settings


class MiddlewareTestCase(TestCase):
    @modify_settings(
        MIDDLEWARE={
            "append": "django.middleware.cache.FetchFromCacheMiddleware",
            "prepend": "django.middleware.cache.UpdateCacheMiddleware",
        }
    )
    def test_cache_middleware(self):
        response = self.client.get("/")
        # ...

El decorador también se puede aplicar a las clases de casos de prueba:

from django.test import TestCase, modify_settings


@modify_settings(
    MIDDLEWARE={
        "append": "django.middleware.cache.FetchFromCacheMiddleware",
        "prepend": "django.middleware.cache.UpdateCacheMiddleware",
    }
)
class MiddlewareTestCase(TestCase):
    def test_cache_middleware(self):
        response = self.client.get("/")
        # ...

Nota

Cuando se le da una clase, estos decoradores modifican la clase directamente y la devuelven; no crean ni devuelven una copia modificada de ella. Por lo tanto, si intenta modificar los ejemplos anteriores para asignar el valor de retorno a un nombre diferente que LoginTestCase o MiddlewareTestCase, puede sorprenderse al encontrar que las clases originales de casos de prueba están igualmente afectadas por el decorador. Para una clase dada, modify_settings() siempre se aplica después de override_settings().

Advertencia

El archivo de configuración contiene algunas configuraciones que solo se consultan durante la inicialización de los internos de Django. Si cambia con override_settings, el ajuste se cambia si lo accede a través del módulo django.conf.settings, sin embargo, los internos de Django lo acceden de manera diferente. En efecto, utilizar override_settings() o modify_settings() con estas configuraciones no hará probablemente lo que esperas que haga.

No recomendamos alterar la configuración DATABASES. Alterar la configuración CACHES es posible, pero un poco complicado si estás utilizando internos que hacen uso de caché, como django.contrib.sessions. Por ejemplo, deberías reinicializar el backend de sesión en un caso de prueba que utiliza sesiones cacheadas y sobreescribe CACHES.

Finalmente, evita asignar alias a tus configuraciones como constantes de módulo nivel con override_settings() no funcionará en tales valores ya que solo se evalúan la primera vez que el módulo es importado.

También puedes simular la ausencia de una configuración borrando después de haber sobrescrito las configuraciones, como se muestra a continuación:

@override_settings()
def test_something(self):
    del settings.LOGIN_URL
    ...

Cuando se sobreescriben las configuraciones, asegúrate de manejar los casos en que el código de tu aplicación utiliza un caché o una característica similar que retiene estado incluso si la configuración se cambia. Django proporciona el django.test.signals.setting_changed signal que te permite registrar callbacks para limpiar y reiniciar el estado cuando las configuraciones cambian.

Django mismo utiliza este señal para resetear varios datos:

Overridden settings

Reset de datos

USE_TZ, ZONA HORARIA

Databases timezone

TEMPLATES

Motores de plantillas

FORM_RENDERER

Renderizador predeterminado

MÓDULOS DE SERIALIZACIÓN

Cache de serializadores

LOCALE_PATHS, LANGUAGE_CODE

Traducción predeterminada y traducciones cargadas

STATIC_ROOT, STATIC_URL, ALMACENES

Configuración de almacenamiento

Se agregó la capacidad de restablecer el renderizador predeterminado cuando se cambia la configuración FORM_RENDERER.

Aislamiento de aplicaciones

utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)

Registra las clases de modelo definidas dentro de un contexto envuelto en su propio registro aislado apps. Esta funcionalidad es útil cuando se crean clases de modelos para pruebas, ya que las clases se eliminarán limpiamente después y no habrá riesgo de colisiones de nombres.

Los etiquetas de la aplicación que el registro aislado debe contener deben pasar como argumentos individuales. Puedes usar isolate_apps() como decorador o administrador de contexto. Por ejemplo:

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class MyModelTests(SimpleTestCase):
    @isolate_apps("app_label")
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        ...

… o

with isolate_apps("app_label"):

    class TestModel(models.Model):
        pass

    ...

La forma de decorador también se puede aplicar a clases.

Dos argumentos de palabra clave opcionales pueden ser especificados:

  • attr_name: atributo asignado al registro aislado si se utiliza como decorador de clase.

  • kwarg_name: paso de argumento por palabra clave que pasa el registro aislado si se utiliza como decorador de función.

La instancia temporal de Apps utilizada para aislar la registro de modelos se puede recuperar como un atributo cuando se utiliza como decorador de clase mediante el parámetro attr_name:

@isolate_apps("app_label", attr_name="apps")
class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)

… o alternativamente como un argumento en el método de prueba cuando se utiliza como decorador de método mediante el parámetro kwarg_name

class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label", kwarg_name="apps")
    def test_model_definition(self, apps):
        class TestModel(models.Model):
            pass

        self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)

Vaciar la bandeja de salida de pruebas

Si utilizas alguna de las clases personalizadas TestCase de Django, el ejecutor de pruebas eliminará los contenidos del buzón de correo electrónico de prueba al comienzo de cada caso de prueba.

Para más detalles sobre servicios de correo electrónico durante las pruebas, consulta **Servicios de correo electrónico**_ a continuación.

Declaraciones de aserción

La traducción es:

Los textos traducidos son:

SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[fuente]
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)

Las aserciones que la ejecución de callable levanta expected_exception y que expected_message se encuentra en el mensaje de la excepción. Cualquier otro resultado se informa como una falla. Es una versión más simple de unittest.TestCase.assertRaisesRegex() con la diferencia de que expected_message no se trata como un patrón regular.

Si solo se dan los parámetros expected_exception y expected_message, devuelve un administrador de contexto para que el código que se está probando pueda escribirse en línea en lugar de como una función:

with self.assertRaisesMessage(ValueError, "invalid literal for int()"):
    int("a")
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)[fuente]
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)

Análogo a SimpleTestCase.assertRaisesMessage() pero para assertWarnsRegex() en lugar de assertRaisesRegex().

SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[fuente]

Las aserciones que un campo de formulario se comporte correctamente con varios inputs.

Parámetros:
  • fieldclass – la clase del campo que se va a probar.

  • valid – un diccionario que mapea los inputs válidos a sus valores limpiados esperados.

  • invalid – un diccionario que mapea los inputs inválidos a uno o más mensajes de error levantados.

  • field_args – los argumentos pasados para instanciar el campo.

  • field_kwargs – los kwargs pasados para instanciar el campo.

  • empty_value – El resultado de la traducción es:

Por ejemplo, el siguiente código prueba que un campo EmailField acepta a@a.com como una dirección de correo electrónico válida, pero rechaza aaa con un mensaje de error razonable:

self.assertFieldOutput(
    EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]}
)
SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')[fuente]

Se asume que un campo en un formulario lanza la lista proporcionada de errores.

form es una instancia de Form. El formulario debe estar vinculado pero no necesariamente validado (assertFormError() llamará automáticamente a full_clean() en el formulario).

field es el nombre del campo en el formulario para comprobar. Para comprobar los errores de formulario no relacionados con campos, utilice field=None.

errors es una lista de todos los mensajes de error que se espera que tenga el campo. También puedes pasar un solo mensaje de error si solo esperas uno, lo que significa que errors='mensaje de error' es lo mismo que errors=['mensaje de error'].

SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='')[fuente]

Se asume que el formset lanza la lista proporcionada de errores cuando se renderiza.

formset es una instancia de FormSet. El formset debe estar vinculado pero no necesariamente validado (assertFormSetError() llamará automáticamente a full_clean() en el formset).

form_index es el número del formulario dentro del FormSet (comenzando por 0). Utilice form_index=None para comprobar los errores no de formulario del formset, es decir, los errores que se obtienen al llamar a formset.non_form_errors(). En ese caso también debes utilizar field=None.

field y errors tienen el mismo significado que los parámetros de assertFormError().

SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[fuente]

Los asserts que un respuesta produjo el código de estado dado y que text aparece en su contenido. Si se proporciona count, text debe ocurrir exactamente count veces en la respuesta.

Establece html a True para manejar text como HTML. La comparación con el contenido de la respuesta se basará en la semántica del HTML en lugar de la igualdad caracter por caracter. Se ignora el espacio en blanco en la mayoría de los casos, no es significativo el orden de los atributos. Consulte assertHTMLEqual() para obtener más detalles.

En versiones anteriores, los mensajes de error no contenían el contenido de la respuesta.

SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[fuente]

Los asserts que un respuesta produjo el código de estado dado y que text NO aparece en su contenido.

Establece html a True para manejar text como HTML. La comparación con el contenido de la respuesta se basará en la semántica del HTML en lugar de la igualdad caracter por caracter. Se ignora el espacio en blanco en la mayoría de los casos, no es significativo el orden de los atributos. Consulte assertHTMLEqual() para obtener más detalles.

En versiones anteriores, los mensajes de error no contenían el contenido de la respuesta.

SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[fuente]

Los asserts que el template con el nombre dado se utilizó al renderizar la respuesta.

response debe ser una instancia de respuesta devuelta por el cliente de pruebas.

template_name debería ser un string como 'admin/index.html'.

El argumento count es un entero que indica el número de veces que se debe renderizar el template. El valor por defecto es None, lo que significa que el template se debe renderizar una o más veces.

Puedes utilizar esto como administrador de contexto, de la siguiente manera:

with self.assertTemplateUsed("index.html"):
    render_to_string("index.html")
with self.assertTemplateUsed(template_name="index.html"):
    render_to_string("index.html")
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[fuente]

Los asserts que el template con el nombre dado NO se utilizó al renderizar la respuesta.

Puedes utilizar este como un administrador de contexto de la misma manera que assertTemplateUsed().

SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')[fuente]

Se asegura de que dos URLs son iguales, ignorando el orden de los parámetros de consulta excepto para los parámetros con el mismo nombre. Por ejemplo, /path/?x=1&y=2 es igual a /path/?y=2&x=1, pero /path/?a=1&a=2 no es igual a /path/?a=2&a=1.

SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[fuente]

Se asegura de que la respuesta response devolvió un código de estado de redirección status_code, se redirigió a expected_url (incluyendo cualquier dato GET), y que la página final se recibió con target_status_code.

Si tu solicitud utilizó el argumento follow, los valores de expected_url y target_status_code serán la url y el código de estado para el punto final de la cadena de redirecciones.

Si fetch_redirect_response es False, la página final no se cargará. Dado que el cliente de prueba no puede cargar URLs externas, esto es particularmente útil si expected_url no forma parte de tu aplicación Django.

El esquema se maneja correctamente cuando se comparan dos URLs. Si no está presente ningún esquema en la ubicación a donde nos redirigimos, el esquema original de la solicitud se utiliza. Si está presente, el esquema en expected_url es el utilizado para hacer las comparaciones.

SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[fuente]

Se asegura de que las cadenas html1 y html2 son iguales. La comparación se basa en la semántica HTML. La comparación tiene en cuenta lo siguiente:

  • El espacio en blanco antes y después de los etiquetas HTML se ignora.

  • Todos los tipos de espacio en blanco se consideran equivalentes.

  • Todas las etiquetas abiertas se cierran implícitamente, por ejemplo cuando una etiqueta circundante se cierra o el documento HTML finaliza.

  • Los tags vacíos son equivalentes a su versión auto-cerrada.

  • La ordenación de los atributos de un elemento HTML no es significativa.

  • Las atributos booleanos (como checked) sin argumento son iguales a los atributos que igualan en nombre y valor (consulte los ejemplos).

  • El texto, las referencias de caracteres y las referencias de entidades que se refieren al mismo carácter son equivalentes.

Los siguientes ejemplos son pruebas válidas y no levantan ningún AssertionError:

self.assertHTMLEqual(
    "<p>Hello <b>&#x27;world&#x27;!</p>",
    """<p>
        Hello   <b>&#39;world&#39;! </b>
    </p>""",
)
self.assertHTMLEqual(
    '<input type="checkbox" checked="checked" id="id_accept_terms" />',
    '<input id="id_accept_terms" type="checkbox" checked>',
)

html1 y html2 deben contener HTML. Se levantará un AssertionError si uno de ellos no puede ser parseado.

La salida en caso de error se puede personalizar con el argumento msg.

SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[fuente]

Se asume que las cadenas html1 y html2 no son iguales. La comparación se basa en la semántica HTML. Consulte assertHTMLEqual() para detalles.

html1 y html2 deben contener HTML. Se levantará un AssertionError si uno de ellos no puede ser parseado.

La salida en caso de error se puede personalizar con el argumento msg.

SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[fuente]

Se asume que las cadenas xml1 y xml2 son iguales. La comparación se basa en la semántica XML. De manera similar a assertHTMLEqual(), la comparación se realiza sobre el contenido parseado, por lo tanto solo se consideran diferencias semánticas, no sintácticas. Si se pasa XML inválido en cualquier parámetro, siempre se levanta un AssertionError, incluso si ambas cadenas son idénticas.

La declaración de XML, el tipo de documento, las instrucciones de procesamiento y los comentarios se ignoran. Solo se comparan el elemento raíz y sus hijos.

La salida en caso de error se puede personalizar con el argumento msg.

SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[fuente]

Los textos traducidos son:

La salida en caso de error se puede personalizar con el argumento msg.

SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[fuente]

Asserta que el fragmento de HTML needle está contenido en el haystack una vez.

Si se especifica el argumento entero count, entonces además se verificará con rigor el número de ocurrencias de needle.

En la mayoría de los casos, se ignora el espacio en blanco y no es significativo el orden de los atributos. Consulte assertHTMLEqual() para más detalles.

En versiones antiguas, los mensajes de error no contenían el haystack.

SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='')[fuente]

Asserta que el fragmento de HTML needle no está contenido en el haystack.

En la mayoría de los casos, se ignora el espacio en blanco y no es significativo el orden de los atributos. Consulte assertHTMLEqual() para más detalles.

SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[fuente]

Asserta que las cadenas JSON raw y expected_data son iguales. Se aplican las reglas usuales sobre espacio en blanco no significativo del JSON, ya que la tarea pesada se delega a la biblioteca json.

La salida en caso de error se puede personalizar con el argumento msg.

SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[fuente]

Asserta que las cadenas JSON raw y expected_data no son iguales. Consulte assertJSONEqual() para detalles adicionales.

La salida en caso de error se puede personalizar con el argumento msg.

TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[fuente]

Asserta que una consulta de conjuntos qs coincide con un iterable particular de valores values.

Si se proporciona transform, values se compara con una lista producida aplicando transform a cada miembro de qs.

Por defecto, la comparación también depende del ordenamiento. Si qs no proporciona un ordenamiento implícito, puedes establecer el parámetro ordered en False, lo que convierte la comparación en una comparación de collections.Counter. Si el orden es indefinido (si el dado qs no está ordenado y la comparación se realiza contra más de un valor ordenado), se levanta un ValueError.

La salida en caso de error se puede personalizar con el argumento msg.

TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[fuente]

Asegura que cuando func se llama con *args y **kwargs se ejecutan num consultas a la base de datos.

Si está presente una clave "using" en kwargs, se utiliza como alias de base de datos para el cual verificar el número de consultas:

self.assertNumQueries(7, my_function, using="non_default_db")

Si deseas llamar a una función con un parámetro using puedes hacerlo envolviendo la llamada con una lambda para agregar un parámetro adicional:

self.assertNumQueries(7, lambda: my_function(using=7))

También se puede utilizar como administrador de contexto:

with self.assertNumQueries(2):
    Person.objects.create(name="Aaron")
    Person.objects.create(name="Daniel")

Etiquetado de pruebas

Puedes etiquetar tus pruebas para poder ejecutar fácilmente un subconjunto particular. Por ejemplo, podrías etiquetar las pruebas rápidas o lentas:

from django.test import tag


class SampleTestCase(TestCase):
    @tag("fast")
    def test_fast(self): ...

    @tag("slow")
    def test_slow(self): ...

    @tag("slow", "core")
    def test_slow_but_core(self): ...

También puedes etiquetar una clase de casos de prueba:

@tag("slow", "core")
class SampleTestCase(TestCase): ...

Las clases heredadas heredan etiquetas de sus superclases y los métodos heredan etiquetas de su clase. Dado:

@tag("foo")
class SampleTestCaseChild(SampleTestCase):
    @tag("bar")
    def test(self): ...

SampleTestCaseChild.test se etiquetará con 'slow', 'core', 'bar' y 'foo'.

Luego puedes elegir qué pruebas correr. Por ejemplo, para correr solo las pruebas rápidas:

$ ./manage.py test --tag=fast

O para correr las pruebas rápidas y la de núcleo (aunque es lenta):

$ ./manage.py test --tag=fast --tag=core

También puedes excluir pruebas por etiqueta. Para correr las pruebas de núcleo si no son lentas:

$ ./manage.py test --tag=core --exclude-tag=slow

test --exclude-tag tiene precedencia sobre test --tag, por lo que si una prueba tiene dos etiquetas y seleccionas una de ellas y excluyes la otra, la prueba no se ejecutará.

Pruebas de código asíncrono

Si tan solo quieres probar el resultado de tus vistas asíncronas, el cliente de pruebas estándar las ejecutará dentro de su propio bucle asíncrono sin necesidad de hacer nada más.

Sin embargo, si deseas escribir pruebas completamente asíncronas para un proyecto Django, deberás tener en cuenta varias cosas.

En primer lugar, tus pruebas deben ser métodos async def en la clase de prueba (para darles un contexto asíncrono). Django detectará automáticamente cualquier prueba async def y las envolverá para que se ejecuten dentro de su propio bucle de eventos.

Si estás probando desde una función asíncrona, también debes utilizar el cliente de pruebas asíncrono. Este está disponible como django.test.AsyncClient, o como self.async_client en cualquier prueba.

class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[fuente]

AsyncClient tiene los mismos métodos y firmas que el cliente de pruebas sincrónico (normal), con las siguientes excepciones:

  • Los textos traducidos manteniendo todas sus etiquetas intactas son:

  • Las cabeceras pasadas como argumentos de palabra clave extra no deben tener el prefijo HTTP_ requerido por el cliente sincrónico (consulte Client.get()). Por ejemplo, aquí está cómo establecer una cabecera HTTP Accept:

    >>> c = AsyncClient()
    >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
    

Se agregó el argumento query_params.

Al utilizar AsyncClient cualquier método que realice una solicitud debe ser esperado:

async def test_my_thing(self):
    response = await self.async_client.get("/some-url/")
    self.assertEqual(response.status_code, 200)

El cliente asíncrono también puede llamar a vistas sincrónicas; corre por el camino de petición asíncrona de Django (consulte asynchronous request path), que admite ambos. Cualquier vista llamada a través del AsyncClient obtendrá un objeto ASGIRequest para su request en lugar de la WSGIRequest que el cliente normal crea.

Advertencia

Si estás utilizando decoradores de prueba, deben ser compatibles con async para asegurarte de que funcionen correctamente. Los decoradores integrados de Django funcionarán correctamente, pero los terceros pueden parecer no ejecutarse (se «envolverán» la parte equivocada del flujo de ejecución y no tu prueba).

Si necesitas utilizar estos decoradores, entonces debes decorar tus métodos de prueba con async_to_sync() dentro de ellos en lugar de eso:

from asgiref.sync import async_to_sync
from django.test import TestCase


class MyTests(TestCase):
    @mock.patch(...)
    @async_to_sync
    async def test_my_thing(self): ...

Servicios de correo electrónico

Si alguna de tus vistas Django envían correo electrónico utilizando la funcionalidad de correo electrónico de Django (consulte Django’s email functionality), probablemente no querrás enviar correo electrónico cada vez que ejecutes una prueba con esa vista. Por esta razón, el ejecutor de pruebas de Django redirige automáticamente todos los correos electrónicos enviados por Django a un buzón vacío. Esto te permite probar cada aspecto del envío de correos electrónicos – desde el número de mensajes enviados hasta el contenido de cada mensaje – sin enviar realmente los mensajes.

El ejecutor de pruebas logra esto reemplazando transparentemente la backend normal de correo electrónico con un backend de prueba. (No te preocupes – esto no afecta a ningún otro remitente de correos electrónicos fuera de Django, como el servidor de correo de tu máquina si lo estás ejecutando).

django.core.mail.outbox

Durante la ejecución de pruebas, cada correo electrónico enviado se almacena en django.core.mail.outbox. Esto es una lista de todas las instancias EmailMessage que han sido enviadas. La atributo outbox es un atributo especial creado solo cuando se utiliza el backend de correo electrónico locmem. No existe normalmente como parte del módulo django.core.mail y no puedes importarlo directamente. El código a continuación muestra cómo acceder correctamente a este atributo.

Los ejemplos de pruebas examinan la longitud y el contenido de django.core.mail.outbox:

from django.core import mail
from django.test import TestCase


class EmailTest(TestCase):
    def test_send_email(self):
        # Send message.
        mail.send_mail(
            "Subject here",
            "Here is the message.",
            "from@example.com",
            ["to@example.com"],
            fail_silently=False,
        )

        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the subject of the first message is correct.
        self.assertEqual(mail.outbox[0].subject, "Subject here")

Como se ha indicado previamente, la caja de salida de las pruebas se vacía al comienzo de cada prueba en un Django *TestCase. Para vaciar manualmente la caja de salida, asigna la lista vacía a mail.outbox:

from django.core import mail

# Empty the test outbox
mail.outbox = []

Comandos de Gestión

Los comandos de gestión se pueden probar con la función call_command(). El resultado se puede redirigir en una instancia StringIO:

from io import StringIO
from django.core.management import call_command
from django.test import TestCase


class ClosepollTest(TestCase):
    def test_command_output(self):
        out = StringIO()
        call_command("closepoll", poll_ids=[1], stdout=out)
        self.assertIn('Successfully closed poll "1"', out.getvalue())

Saltar pruebas

La biblioteca unittest proporciona los decoradores @skipIf y @skipUnless para permitir saltarte las pruebas si se sabe con anticipación que esas pruebas van a fallar bajo ciertas condiciones.

Por ejemplo, si la prueba requiere una biblioteca opcional en particular para tener éxito, podría decorarse el caso de prueba con @skipIf. Luego, el ejecutor de pruebas informará de que la prueba no se ejecutó y por qué, en lugar de fallar la prueba o omitirla.

Para complementar estos comportamientos de saltar las pruebas, Django proporciona dos decoradores adicionales de salto. En lugar de probar un booleano genérico, estos decoradores comprueban las capacidades del motor de base de datos y saltan la prueba si el motor no admite una característica específica.

Los decoradores utilizan una cadena identificadora para describir características del motor de base de datos. Esta cadena corresponde a atributos de la clase de características de conexión al motor de base de datos. Consulte la clase django.db.backends.base.features.BaseDatabaseFeatures para obtener una lista completa de las características del motor de base de datos que se pueden utilizar como base para saltar las pruebas.

skipIfDBFeature(*feature_name_strings)[fuente]

Saltar la prueba decorada o TestCase si todas las características del motor de base de datos nombradas son compatibles.

Por ejemplo, el siguiente test no se ejecutará si la base de datos admite transacciones (por ejemplo, no lo haría bajo PostgreSQL, pero sí bajo MySQL con tablas MyISAM):

class MyTests(TestCase):
    @skipIfDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
skipUnlessDBFeature(*feature_name_strings)[fuente]

Saltate el test decorado o TestCase si ninguna de las características de la base de datos nombradas no están soportadas.

Por ejemplo, el siguiente test solo se ejecutará si la base de datos admite transacciones (por ejemplo, lo haría bajo PostgreSQL, pero no bajo MySQL con tablas MyISAM):

class MyTests(TestCase):
    @skipUnlessDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass