37 votes

Quelles sont les meilleures pratiques pour tester "différentes couches" dans Django?

Je suis PAS nouveau pour les tests, mais suis vraiment confus avec le désordre de recommandations pour tester les différentes couches dans Django.

Certains recommandent (et ils ont raison) pour éviter les Doctests dans le modèle comme ils ne sont pas facile à gérer...

D'autres disent ne pas utiliser les fixtures, comme ils sont moins flexibles que les fonctions d'assistance, par exemple..

Il y a aussi deux groupes de personnes qui se battent pour l'utilisation de la Maquette des objets. Le premier groupe est d'avis que dans l'utilisation de Fantaisie et de les isoler du reste du système, tandis qu'un autre groupe préfère Arrêter les Moqueries et commencer à faire des tests..

Tout ce que j'ai mentionné ci-dessus, étaient pour la plupart en ce qui concerne le test des modèles. Le test fonctionnel est une autre histoire (à l'aide de test.Client() VS webTest VS etc. )

Est-il TOUT maintenable, extandible et de manière appropriée pour le test de différentes couches??

Mise à JOUR

Je suis conscient de Carl Meyer parler à PyCon 2012..

45voto

Hassek Points 3136

Mise à JOUR 08-07-2012

Je peux vous dire que mes pratiques pour les tests unitaires qui travaillent très bien pour mes propres fins, et je vais vous donner mes raisons:

1.- L'utilisation de Luminaires seulement l'information qui est nécessaire pour les tests, mais ne va pas changer, par exemple, vous avez besoin d'un utilisateur pour chaque test que vous n'utilisez donc une base de luminaire pour créer des utilisateurs.

2.- Utiliser une factory pour créer vos objets, personnellement, j'aime FactoryBoy (cela vient de FactoryGirl qui est un rubis de la bibliothèque). J'ai créer un fichier séparé appelé factories.py pour chaque application, où je sauvegarder tous ces objets. De cette façon, je garde hors les fichiers de test tous les objets que j'ai besoin, ce qui le rend beaucoup plus lisible et facile à entretenir. La chose cool à propos de cette approche est que vous créez un objet de base qui peuvent être modifiées si vous voulez tester quelque chose d'autre basée sur un certain objet de l'usine. Aussi il ne dépend pas de django, donc quand j'ai migré de ces objets lorsque j'ai commencé à utiliser mongodb et nécessaire de les tester, tout était lisse. Maintenant, après la lecture sur les usines, il est commun de dire "Pourquoi voudrais-je utiliser luminaires ensuite". Puisque ces appareils ne doivent jamais changer tous les goodies supplémentaires provenant des usines sont en quelque sorte inutile et django prend en charge les luminaires très bien hors de la boîte.

3.- J'ai Simulé des appels à des services externes, parce que ces appels faire mes tests très lent, et ils dépendent de choses qui n'ont rien à voir avec mon code de bonne ou de mauvaise. par exemple, si je tweet dans mon test, je fais tester à tweeter, à juste titre, copie de la réponse et de se moquer de cet objet afin qu'il renvoie exactement la même réponse à chaque fois, sans faire l'appel. Aussi est parfois bon de tester quand les choses vont mal et se moquant est idéal pour cela.

4.- Je utiliser un serveur d'intégration (jenkins est ma recommandation ici), qui exécute les tests à chaque fois que je pousse mon serveur de test et si elles échouent, il m'envoie un e-mail. C'est tout simplement génial car il m'arrive beaucoup de choses que je casse quelque chose d'autre dans mon dernier changement et j'ai oublié d'exécuter les tests. Il vous donne également d'autres goodies comme un rapport de couverture, pylint/jslint/pep8 vérifications et il existe beaucoup de plugins, où vous pouvez définir les différentes statistiques.

Sujet de votre question pour le test avant la fin, django est livré avec des fonctions d'assistance pour gérer cela de manière basique.

C'est ce que j'utilise personnellement, vous pouvez incendie se, de poteaux, de connexion de l'utilisateur, etc. c'est assez pour moi. Je n'ai pas tendance à utiliser une interface frontale complète des essais moteur, comme le sélénium, puisque je pense qu'il est inutile de tester autre chose que la couche de gestion. Je suis sûr que certains seront différents, et cela dépend toujours de ce que vous travaillez sur.

En outre mon avis, django 1.4 vient avec un pratique de l'intégration dans le navigateur de cadres.

Je vais mettre un exemple d'application où je peux appliquer ce pratiques de sorte qu'il est plus compréhensible. Nous allons créer une base très blog app:

structure

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())

fixtures/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": "test@email.com",
        "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()

Remarque que je suis à l'aide de certains aspects spécifiques de la technologie pour le bien de l'exemple (qui n'ont pas été testés btw).

J'insiste, cela peut ne pas être la meilleure pratique standard (ce dont je doute, il en existe un), mais il fonctionne assez bien pour moi.

20voto

Filip Dupanović Points 10071

J'aime vraiment les suggestions de @Hassek et tiens à souligner ce que un excellent moment il fait sur le manque évident de pratiques standard, ce qui est vrai pour beaucoup de Django aspects, et pas seulement les tests, parce que tous nous approche du cadre avec différentes préoccupations à l'esprit, en ajoutant également pour que le plus grand degré de souplesse que nous avons avec la conception de nos applications, nous avons souvent radicalement différentes solutions qui sont applicables pour le même problème.

Ayant dit que, bien que la plupart d'entre nous encore lutter pour un grand nombre des mêmes objectifs lors de l'essai de nos applications, principalement:

  • Le maintien de nos modules de test bien organisé
  • La création de réutilisables affirmation et méthodes d'assistance, des fonctions d'assistance qui permettent de réduire la LDC pour les méthodes d'essai, afin de les rendre plus compact et plus lisible
  • Montrant qu'il est de toute évidence, l'approche systématique à la façon dont les composants de l'application sont testés

Comme @Hassek, ce sont mes préférences qui peuvent, directement en conflit avec les pratiques que vous pouvez appliquer, mais j'ai l'impression que c'est agréable de partager les choses que nous avons prouvé que le travail, si ce n'est dans notre cas.

Pas de cas de test luminaires

Application des luminaires de l'excellent travail, dans le cas où vous avez certaines constantes du modèle de données que vous souhaitez garantir à être présent dans la base de données, dire une collection de villes avec leurs noms et bureau de poste numéros.

Cependant, je vois cela comme une inflexible solution pour fournir des données de scénario de test. Appareils de Test sont très prolixe, modèle mutations de la force vous soit passer par un long processus de reproduire le gabarit de données ou d'effectuer manuel fastidieux des changements et le maintien de l'intégrité référentielle est difficile à effectuer manuellement.

En outre, vous aurez plus de chances d'utiliser de nombreux types d'appareils dans vos tests, et pas seulement pour les modèles: vous souhaitez stocker le corps de la réponse de requêtes à l'API, pour créer des appareils qui cible NoSQL pilotes de base de données, écrire ont des appareils qui sont utilisés pour remplir les données de formulaire, etc.

En fin de compte, en utilisant des Api pour créer des données est concis, lisible et il les rend beaucoup plus facile de repérer les relations, de sorte que la plupart d'entre nous recourir à l'aide de usines pour de la création dynamique d'appareils d'éclairage.

L'utilisation intensive des usines

Usine de fonctions et de méthodes sont préférables à l'éradication de vos données de test. Vous pouvez créer helper usine au niveau du module de fonctions ou de cas de test de méthodes que vous pouvez réutilisez à travers l'application des tests ou à l'échelle de l'ensemble du projet. En particulier, factory_boy, que @Hassek mentionne, vous offre la possibilité d'hériter/étendre les données de fixture et ne séquençage automatique, qui peut paraître un peu maladroit, si vous voulez le faire à la main autrement.

Le but ultime de l'utilisation des usines est de couper vers le bas sur le code de la duplication et de rationaliser la façon dont vous créez des données de test. Je ne peux pas vous donner exacte métriques, mais je suis sûr que si vous allez par le biais de vos méthodes de test avec un œil averti, vous remarquerez une grande partie de votre code de test est principalement la préparation des données que vous aurez besoin pour conduire vos tests.

Lorsque cela est fait de manière incorrecte, la lecture et le maintien de tests devient une activité épuisante. Ce qui tend à dégénérer lorsque les données mutations conduisent à ne pas évidentes de l'échec de test à travers le conseil, au point où vous ne serez pas en mesure d'appliquer systématique refactoring efforts.

Mon approche personnelle de ce problème est de commencer avec un myproject.factory module qui crée d'accès facile, les références à QuerySet.create méthodes pour mes modèles et aussi pour tout les objets que je pourrais utiliser régulièrement dans la plupart de mes examens:

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

Cela permet à son tour de me faire quelque chose comme ceci:

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 plupart des gens sont rigoureux sur la réduction de la duplication de code, mais j'ai fait intentionnellement introduire certains chaque fois que je sens qu'il contribue à l'essai à l'exhaustivité. Encore une fois, le but, avec quelle que soit l'approche que vous prenez pour des usines est de minimiser la quantité de brainfuck vous introduire dans l'en-tête de chaque méthode de test.

Mocker, mais de les utiliser à bon escient

Je suis un fan de mock, comme je l'ai développé une appréciation pour l'auteur de la solution à ce que je crois était le problème qu'il voulait aborder. Les outils fournis par le paquet vous permettent de test assertions par l'injection de résultats attendus.

# 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)

Comme vous pouvez le voir, se moque sont vraiment utiles, mais ils ont une fâcheuse effet secondaire: votre se moque de montrer clairement votre faire des hypothèses sur la façon dont il est que votre application se comporte, ce qui introduit de couplage. Si Converter est refait à utiliser autre chose que l' CurrencyApi, quelqu'un peut ne pas comprendre pourquoi la méthode de test est soudainement défaut.

Donc, avec un grand pouvoir vient une grande responsabilité--si vous allez être un smartass et mocker pour éviter profondément enracinée test obstacles, vous pouvez complètement dissimuler la véritable nature de votre échec de test.

Par-dessus tout, être cohérent. Très très cohérent

C'est le point le plus important à être fait. Être compatible avec absolument tout:

  • comment vous organisez vous dans chacun de vos modules de test
  • comment vous présenter des cas de test pour les composants de votre application
  • comment vous présenter des méthodes d'essai pour affirmer le comportement de ces composants
  • quelle est la structure de méthodes d'essai
  • comment l'approche de test de composants communs (la classe de base des points de vue, des modèles, des formes, etc.)
  • la manière dont vous appliquez la réutilisation

Pour la plupart des projets, le comment votre collaboration aller à l'approche de test est souvent négligé. Alors que le code de l'application elle-même est parfait--en adhérant à des guides de style, l'utilisation de Python idiomes, la réapplication de Django approche propre à résoudre les problèmes connexes, des manuels d'utilisation des composants de l'infrastructure, etc.--personne n'a vraiment fait un effort pour comprendre comment tester un code valide, utile outil de communication et c'est une honte si, peut-être, avoir des lignes directrices claires pour le code de test est tout ce qu'il faut.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X