versiones guia español actualizar django testing tdd automated-tests django-testing

guia - ¿Cuáles son las mejores prácticas para probar "diferentes capas" en Django?



qgis español (2)

No soy nuevo en las pruebas, pero me confundí con el desorden de las recomendaciones para probar diferentes capas en Django.

Algunos recomiendan (y tienen razón) evitar los Doctest en el modelo, ya que no son mantenibles ...

Otros dicen que no utilices artefactos , ya que son menos flexibles que las funciones auxiliares , por ejemplo ...

También hay dos grupos de personas que luchan por usar objetos simulados . El primer grupo cree en usar Mock y aislar el resto del sistema, mientras que otro grupo prefiere dejar de burlarse y comenzar a probar .

Todo lo que he mencionado anteriormente, se refería principalmente a modelos de prueba. Las pruebas funcionales son otra historia (usando test.Client () VS webTest VS etc.)

¿Hay CUALQUIER manera mantenible, ampliable y adecuada para probar diferentes capas?

ACTUALIZAR

Estoy al tanto de la charla de Carl Meyer en PyCon 2012 ..


Me encantan las sugerencias de @Hassek y quiero subrayar el excelente punto que hace sobre la evidente falta de prácticas estándar, lo cual es válido para muchos de los aspectos de Django, no solo para las pruebas, ya que todos abordamos el marco con diferentes preocupaciones. En mente, además de aumentar el gran grado de flexibilidad que tenemos en el diseño de nuestras aplicaciones, a menudo terminamos con soluciones drásticamente diferentes que son aplicables al mismo problema.

Sin embargo, una vez dicho esto, la mayoría de nosotros todavía luchamos por alcanzar los mismos objetivos al probar nuestras aplicaciones, principalmente:

  • Manteniendo nuestros módulos de prueba prolijamente organizados
  • Creación de métodos de aserción y ayuda reutilizables, funciones auxiliares que reducen el LOC para los métodos de prueba, para hacerlos más compactos y legibles
  • Mostrando que existe un enfoque obvio y sistemático sobre cómo se prueban los componentes de la aplicación

Al igual que @Hassek, estas son mis preferencias que pueden entrar en conflicto directo con las prácticas que puede estar aplicando, pero creo que es bueno compartir las cosas que hemos demostrado que funcionan, aunque solo sea en nuestro caso.

Sin accesorios de casos de prueba

Los accesorios de aplicación funcionan muy bien, en caso de que tenga ciertos datos de modelo constantes que le gustaría garantizar que estén presentes en la base de datos, por ejemplo, una colección de ciudades con sus nombres y números de la oficina postal.

Sin embargo, veo esto como una solución inflexible para proporcionar datos de casos de prueba. Los accesorios de prueba son muy detallados, las mutaciones del modelo te obligan a pasar por un largo proceso de reproducción de los datos del dispositivo o realizar tediosos cambios manuales y mantener la integridad referencial es difícil de realizar manualmente.

Además, probablemente usará muchos tipos de accesorios en sus pruebas, no solo para los modelos: le gustaría almacenar el cuerpo de respuesta de las solicitudes de API, para crear dispositivos que se dirijan a los servidores de base de datos NoSQL, para escribir tengan dispositivos que se utilicen. rellenar datos de formulario, etc.

Al final, utilizar las API para crear datos es conciso, legible y hace que sea mucho más fácil detectar las relaciones, por lo que la mayoría de nosotros recurrimos al uso de fábricas para la creación dinámica de accesorios.

Haga un uso extensivo de las fábricas

Las funciones y métodos de fábrica son preferibles a pisotear sus datos de prueba. Puede crear funciones de nivel de módulo auxiliar de fábrica o métodos de caso de prueba que desee reutilizar en las pruebas de aplicación o en todo el proyecto. Particularmente, FactoryBoy , que @Hassek menciona, le proporciona la capacidad de heredar / ampliar los datos del dispositivo y realizar una secuencia automática, lo que podría parecer un poco torpe si lo hiciera a mano de otra manera.

El objetivo final de utilizar fábricas es reducir la duplicación de código y simplificar la forma de crear datos de prueba. No puedo darte las métricas exactas, pero estoy seguro de que si revisas tus métodos de prueba con un ojo experto, notarás que una gran parte de tu código de prueba está principalmente preparando los datos que necesitarás para conducir tus pruebas.

Cuando esto se hace incorrectamente, leer y mantener pruebas se convierte en una actividad agotadora. Esto tiende a intensificarse cuando las mutaciones de datos conducen a fallas de prueba no tan obvias en general, en cuyo momento no podrá aplicar esfuerzos sistemáticos de refactorización.

Mi enfoque personal para este problema es comenzar con un módulo myproject.factory que crea referencias de fácil acceso a los métodos QuerySet.create para mis modelos y también para cualquier objeto que use regularmente en la mayoría de mis pruebas de aplicación:

from django.contrib.auth.models import User, AnonymousUser from django.test import RequestFactory from myproject.cars.models import Manufacturer, Car from myproject.stores.models import Store create_user = User.objects.create_user create_manufacturer = Manufacturer.objects.create create_car = Car.objects.create create_store = Store.objects.create _factory = RequestFactory() def get(path=''/'', data={}, user=AnonymousUser(), **extra): request = _factory.get(path, data, **extra) request.user = user return request def post(path=''/'', data={}, user=AnonymousUser(), **extra): request = _factory.post(path, data, **extra) request.user = user return request

Esto a su vez me permite hacer algo como esto:

from myproject import factory as f # Terse alias # A verbose, albeit readable approach to creating instances manufacturer = f.create_manufacturer(name=''Foomobiles'') car1 = f.create_car(manufacturer=manufacturer, name=''Foo'') car2 = f.create_car(manufacturer=manufacturer, name=''Bar'') # Reduce the crud for creating some common objects manufacturer = f.create_manufacturer(name=''Foomobiles'') data = {name: ''Foo'', manufacturer: manufacturer.id) request = f.post(data=data) view = CarCreateView() response = view.post(request)

La mayoría de las personas son rigurosas a la hora de reducir la duplicación de código, pero de hecho introduzco intencionalmente algunas cuando considero que contribuyen a evaluar la exhaustividad. Una vez más, el objetivo con el enfoque que elija para las fábricas es minimizar la cantidad de brainfuck que introduce en el encabezado de cada método de prueba.

Usa simulacros, pero úsalos sabiamente

Soy fanático de la mock , ya que he desarrollado una apreciación por la solución del autor a lo que creo que era el problema que quería abordar. Las herramientas proporcionadas por el paquete le permiten formar afirmaciones de prueba al inyectar los resultados esperados.

# Creating mocks to simplify tests factory = RequestFactory() request = factory.get() request.user = Mock(is_authenticated=lamda: True) # A mock of an authenticated user view = DispatchForAuthenticatedOnlyView().as_view() response = view(request) # Patching objects to return expected data @patch.object(CurrencyApi, ''get_currency_list'', return_value="{''foo'': 1.00, ''bar'': 15.00}") def test_converts_between_two_currencies(self, currency_list_mock): converter = Converter() # Uses CurrencyApi under the hood result = converter.convert(from=''bar'', to=''foo'', ammount=45) self.assertEqual(4, result)

Como puede ver, los simulacros son realmente útiles, pero tienen un efecto secundario desagradable: sus burlas muestran claramente su suposición sobre cómo se comporta su aplicación, lo que introduce el acoplamiento. Si el Converter se refactoriza para usar algo que no sea CurrencyApi , es posible que alguien no entienda por qué el método de prueba falla de repente.

Entonces, con gran poder viene una gran responsabilidad: si va a ser un listillo y usar burlas para evitar obstáculos de prueba profundamente arraigados, puede ofuscar por completo la verdadera naturaleza de sus fallas de prueba.

Sobre todo, sé consecuente. Muy muy consistente

Este es el punto más importante que se debe hacer. Sea consistente con absolutamente todo:

  • cómo organizas código en cada uno de tus módulos de prueba
  • cómo introduce casos de prueba para los componentes de su aplicación
  • cómo se introducen los métodos de prueba para afirmar el comportamiento de esos componentes
  • cómo estructuras los métodos de prueba
  • cómo se aproxima a probar componentes comunes (vistas basadas en clases, modelos, formularios, etc.)
  • cómo aplicar la reutilización

Para la mayoría de los proyectos, a menudo se pasa por alto el poco acerca de cómo su enfoque de colaboración para abordar las pruebas. Si bien el código de la aplicación se ve perfecto, siguiendo las guías de estilo, el uso de modismos de Python, volviendo a aplicar el propio enfoque de Django para resolver problemas relacionados, el uso de los componentes del marco de trabajo de libros de texto, etc., nadie realmente se esfuerza por descubrir cómo convierta el código de prueba en una herramienta de comunicación válida y útil, y es una pena si, tal vez, tener todas las instrucciones claras para el código de prueba es suficiente.


ACTUALIZACIÓN 08-07-2012

Puedo decirte mis prácticas para pruebas unitarias que están funcionando bastante bien para mis propios fines y te daré mis razones:

1.- Use los Fixtures solo para la información que es necesaria para probar pero no va a cambiar, por ejemplo, necesita un usuario para cada prueba que haga, así que use un dispositivo base para crear usuarios.

2.- Usar una fábrica para crear tus objetos, personalmente amo FactoryBoy (viene de FactoryGirl, que es una biblioteca de rubíes). Creo un archivo separado llamado fábricas.py para cada aplicación donde guardo todos estos objetos. De esta manera, mantengo fuera de los archivos de prueba todos los objetos que necesito, lo que lo hace mucho más legible y fácil de mantener. Lo bueno de este enfoque es que creas un objeto base que se puede modificar si quieres probar otra cosa en función de algún objeto de la fábrica. Además, no depende de django, así que cuando migré estos objetos cuando comencé a usar mongodb y necesitaba probarlos, todo fue sencillo. Ahora, después de leer acerca de las fábricas, es común decir "¿Por qué querría usar aparatos entonces?". Dado que estos accesorios nunca deberían cambiar, todas las golosinas extra de las fábricas son inútiles y django soporta los accesorios muy bien de la caja.

3.- Mock llamadas a servicios externos, porque estas llamadas hacen que mis pruebas sean muy lentas y dependen de cosas que no tienen nada que ver con que mi código sea correcto o incorrecto. por ejemplo, si twitteo dentro de mi prueba, lo pruebo para que twittee correctamente, copie la respuesta y simule ese objeto para que devuelva esa respuesta exacta cada vez sin hacer la llamada real. También a veces es bueno probar cuando las cosas van mal y la burla es excelente para eso.

4.- Utilizo un servidor de integración (mi recomendación es jenkins ) que ejecuta las pruebas cada vez que presiono mi servidor de transferencia y si fallan me envía un correo electrónico. Esto es genial, ya que me sucede mucho que rompo algo más en mi último cambio y olvidé ejecutar las pruebas. También le ofrece otras ventajas como un informe de cobertura, las pylint / jslint / pep8 y existe una gran cantidad de complementos donde puede establecer diferentes estadísticas.

Acerca de su pregunta para probar la interfaz, django viene con algunas funciones de ayuda para manejar esto de una manera básica.

Esto es lo que yo personalmente uso, puedes disparar, publicar, iniciar sesión en el usuario, etc. eso es suficiente para mí. No tiendo a utilizar un motor de prueba frontal completo como el selenio, ya que creo que es excesivo probar cualquier otra cosa además de la capa de negocios. Estoy seguro de que algunos serán diferentes y siempre depende de en qué estés trabajando.

Además de mi opinión, django 1.4 viene con una integration muy útil para los marcos en el navegador.

Estableceré una aplicación de ejemplo en la que pueda aplicar estas prácticas para que sea más comprensible. Vamos a crear una aplicación de blog muy básica:

estructura

blogger/ __init__.py models.py fixtures/base.json factories.py tests.py

models.py

from django.db import models class Blog(models.Model): user = models.ForeignKey(User) text = models.TextField() created_on = models.DateTimeField(default=datetime.now())

accesorios / base.json

[ { "pk": 1, "model": "auth.user", "fields": { "username": "fragilistic_test", "first_name": "demo", "last_name": "user", "is_active": true, "is_superuser": true, "is_staff": true, "last_login": "2011-08-16 15:59:56", "groups": [], "user_permissions": [], "password": "IAmCrypted!", "email": "[email protected]", "date_joined": "1923-08-16 13:26:03" } } ]

factories.py

import factory from blog.models import User, Blog class BlogFactory(factory.Factory): FACTORY_FOR = Blog user__id = 1 text = "My test text blog of fun"

tests.py

class BlogTest(TestCase): fixtures = [''base''] # loads fixture def setUp(self): self.blog = BlogFactory() self.blog2 = BlogFactory(text="Another test based on the last one") def test_blog_text(self): self.assertEqual(Blog.objects.filter(user__id=1).count(), 2) def test_post_blog(self): # Lets suppose we did some views self.client.login(username=''user'', password=''IAmCrypted!'') response = self.client.post(''/blogs'', {''text'': "test text", user=''1''}) self.assertEqual(response.status, 200) self.assertEqual(Blog.objects.filter(text=''test text'').count(), 1) def test_mocker(self): # We will mock the datetime so the blog post was created on the date # we want it to mocker = Mock() co = mocker.replace(''datetime.datetime'') co.now() mocker.result(datetime.datetime(2012, 6, 12)) with mocker: res = Blog.objects.create(user__id=1, text=''test'') self.assertEqual(res.created_on, datetime.datetime(2012, 6, 12)) def tearDown(self): # Django takes care of this but to be strict I''ll add it Blog.objects.all().delete()

Tenga en cuenta que estoy usando alguna tecnología específica por el bien del ejemplo (que no han sido probados por cierto).

Tengo que insistir, esta puede no ser la mejor práctica estándar (que dudo que exista) pero me está funcionando bastante bien.