Recherche…


Testing - un exemple complet

Cela suppose que vous ayez lu la documentation sur le démarrage d'un nouveau projet Django. Supposons que l'application principale de votre projet s'appelle td (abréviation de test). Pour créer votre premier test, créez un fichier nommé test_view.py et copiez-y le contenu suivant.

from django.test import Client, TestCase

class ViewTest(TestCase):

    def test_hello(self):
        c = Client()
        resp = c.get('/hello/')
        self.assertEqual(resp.status_code, 200)

Vous pouvez exécuter ce test en

 ./manage.py test

et il échouera tout naturellement! Vous verrez une erreur similaire à la suivante.

Traceback (most recent call last):
  File "/home/me/workspace/td/tests_view.py", line 9, in test_hello
    self.assertEqual(resp.status_code, 200)
AssertionError: 200 != 404

Pourquoi ça arrive? Parce que nous n'avons pas défini de vue pour cela! Alors faisons-le. Créez un fichier appelé views.py et placez-y le code suivant

from django.http import HttpResponse
def hello(request):
    return HttpResponse('hello')

Ensuite, associez-le à / hello / en éditant les URL py comme suit:

from td import views

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^hello/', views.hello),
    ....
]

Maintenant, relancez le test ./manage.py test nouveau et alto !!

Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK

Tester les modèles Django efficacement

En supposant une classe

from django.db import models

class Author(models.Model):
   name = models.CharField(max_length=50)
   
    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return reverse('view_author', args=[str(self.id)])


class Book(models.Model):
    author = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    private = models.BooleanField(default=false)
    publish_date = models.DateField()

    def get_absolute_url(self):
        return reverse('view_book', args=[str(self.id)])
    
    def __str__(self):
        return self.name

Exemples de tests

from django.test import TestCase
from .models import Book, Author

class BaseModelTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        super(BaseModelTestCase, cls).setUpClass()
        cls.author = Author(name='hawking')
        cls.author.save()
        cls.first_book = Book(author=cls.author, name="short_history_of_time")
        cls.first_book.save()
        cls.second_book = Book(author=cls.author, name="long_history_of_time")
        cls.second_book.save()


class AuthorModelTestCase(BaseModelTestCase):
    def test_created_properly(self):
         self.assertEqual(self.author.name, 'hawking')
         self.assertEqual(True, self.first_book in self.author.book_set.all())
    
    def test_absolute_url(self):
        self.assertEqual(self.author.get_absolute_url(), reverse('view_author', args=[str(self.author.id)]))

class BookModelTestCase(BaseModelTestCase):
    
    def test_created_properly(self:
        ...
        self.assertEqual(1, len(Book.objects.filter(name__startswith='long'))
    
    def test_absolute_url(self):
        ...

Des points

  • created_properly tests created_properly sont utilisés pour vérifier les propriétés d'état des modèles django. Ils aident à prendre des précautions lorsque nous avons modifié les valeurs par défaut, file_upload_paths, etc.
  • absolute_url peut sembler trivial mais j'ai trouvé que cela m'a aidé à éviter certains bogues lors de la modification des chemins d'url
  • De même, j'écris des cas de test pour toutes les méthodes implémentées dans un modèle (en utilisant mock objets mock , etc.)
  • En définissant un BaseModelTestCase commun, nous pouvons définir les relations nécessaires entre les modèles pour garantir des tests corrects.

Enfin, en cas de doute, écrivez un test. Les changements de comportement triviaux se font en prêtant attention aux détails et les bouts de code oubliés ne finissent pas par causer des problèmes inutiles.

Tester le contrôle d'accès dans les vues Django

tl; dr : crée une classe de base qui définit deux objets utilisateur (disons user et another_user ). Créez vos autres modèles et définissez trois instances Client .

  • self.client : Représentant l' user connecté au navigateur
  • self.another_client : Représentant another_user client de »
  • self.unlogged_client : Représenter une personne non associée

Accédez maintenant à toutes vos URL publiques et privées à partir de ces trois objets client et dictez la réponse attendue. Ci-dessous, j'ai présenté la stratégie d'un objet Book qui peut être private (appartenant à quelques utilisateurs privilégiés) ou public (visible par tous).

from django.test import TestCase, RequestFactory, Client
from django.core.urlresolvers import reverse

class BaseViewTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        super(BaseViewTestCase, cls).setUpClass()
        cls.client = Client()
        cls.another_client = Client()
        cls.unlogged_client = Client()
        cls.user = User.objects.create_user(
                'dummy',password='dummy'
                )
        cls.user.save()
        cls.another_user = User.objects.create_user(
                'dummy2', password='dummy2'
                )
        cls.another_user.save()
        cls.first_book = Book.objects.create(
                name='first',
                private = true
        )
        cls.first_book.readers.add(cls.user)
        cls.first_book.save()
        cls.public_book = Template.objects.create(
                name='public',
                private=False
        )
        cls.public_book.save()


    def setUp(self):
        self.client.login(username=self.user.username, password=self.user.username)
        self.another_client.login(username=self.another_user.username, password=self.another_user.username)


"""
   Only cls.user owns the first_book and thus only he should be able to see it.
   Others get 403(Forbidden) error
"""
class PrivateBookAccessTestCase(BaseViewTestCase):
    
    def setUp(self):
        super(PrivateBookAccessTestCase, self).setUp()
        self.url = reverse('view_book',kwargs={'book_id':str(self.first_book.id)})

    def test_user_sees_own_book(self):
        response = self.client.get(self.url)
        self.assertEqual(200, response.status_code)
        self.assertEqual(self.first_book.name,response.context['book'].name)
        self.assertTemplateUsed('myapp/book/view_template.html')

    def test_user_cant_see_others_books(self):
        response = self.another_client.get(self.url)
        self.assertEqual(403, response.status_code)
        
    def test_unlogged_user_cant_see_private_books(self):
        response = self.unlogged_client.get(self.url)
        self.assertEqual(403, response.status_code)

"""
    Since book is public all three clients should be able to see the book
"""
 class PublicBookAccessTestCase(BaseViewTestCase):
    
    def setUp(self):
        super(PublicBookAccessTestCase, self).setUp()
        self.url = reverse('view_book',kwargs={'book_id':str(self.public_book.id)})

    def test_user_sees_book(self):
        response = self.client.get(self.url)
        self.assertEqual(200, response.status_code)
        self.assertEqual(self.public_book.name,response.context['book'].name)
        self.assertTemplateUsed('myapp/book/view_template.html')

    def test_another_user_sees_public_books(self):
        response = self.another_client.get(self.url)
        self.assertEqual(200, response.status_code)
        
    def test_unlogged_user_sees_public_books(self):
        response = self.unlogged_client.get(self.url)
        self.assertEqual(200, response.status_code)

La base de données et les tests

Django utilise des paramètres de base de données spéciaux lors des tests afin que les tests puissent utiliser la base de données normalement mais par défaut, ils s'exécutent sur une base de données vide. Les modifications de base de données dans un test ne seront pas vues par un autre. Par exemple, les deux tests suivants réussiront:

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

class MyTest(TestCase):

    def test_1(self):
        self.assertEqual(Thing.objects.count(), 0)
        Thing.objects.create()
        self.assertEqual(Thing.objects.count(), 1)

    def test_2(self):
        self.assertEqual(Thing.objects.count(), 0)
        Thing.objects.create(attr1="value")
        self.assertEqual(Thing.objects.count(), 1)

Agencements

Si vous souhaitez que des objets de base de données soient utilisés par plusieurs tests, créez-les dans la méthode setUp du setUp de test. De plus, si vous avez défini des appareils dans votre projet Django, ils peuvent être inclus comme suit:

class MyTest(TestCase):
    fixtures = ["fixture1.json", "fixture2.json"]

Par défaut, django recherche des appareils dans le répertoire des fixtures de chaque application. D'autres répertoires peuvent être définis à l'aide du paramètre FIXTURE_DIRS :

# myapp/settings.py
FIXTURE_DIRS = [
    os.path.join(BASE_DIR, 'path', 'to', 'directory'),
]

Supposons que vous ayez créé un modèle comme suit:

# models.py
from django.db import models


class Person(models.Model):
    """A person defined by his/her first- and lastname."""
    firstname = models.CharField(max_length=255)
    lastname = models.CharField(max_length=255)

Alors vos appareils .json pourraient ressembler à ça:

# fixture1.json
[
    { "model": "myapp.person",
        "pk": 1,
        "fields": {
            "firstname": "Peter",
            "lastname": "Griffin"
        }
    },
    { "model": "myapp.person",
        "pk": 2,
        "fields": {
            "firstname": "Louis",
            "lastname": "Griffin"
        }
    },
]

Réutiliser la base de données de test

Pour accélérer vos tests, vous pouvez demander à la commande-management de réutiliser la base de données de test (et d'empêcher sa création avant et sa suppression après chaque test). Cela peut être fait en utilisant le flag keepdb (ou sténographie -k ) comme ceci:

# Reuse the test-database (since django version 1.8)
$ python manage.py test --keepdb

Limiter le nombre de tests exécutés

Il est possible de limiter les tests exécutés par le manage.py test en spécifiant les modules à découvrir par le runner de test:

# Run only tests for the app names "app1"
$ python manage.py test app1

# If you split the tests file into a module with several tests files for an app
$ python manage.py test app1.tests.test_models

# it's possible to dig down to individual test methods.
$ python manage.py test app1.tests.test_models.MyTestCase.test_something

Si vous voulez exécuter un tas de tests, vous pouvez passer un modèle de noms de fichiers. Par exemple, vous pouvez exécuter uniquement des tests impliquant vos modèles:

$ python manage.py test -p test_models*
Creating test database for alias 'default'...
.................................................
----------------------------------------------------------------------
Ran 115 tests in 3.869s

OK

Enfin, il est possible d'arrêter la suite de tests dès le premier échec, en utilisant --failfast . Cet argument permet d'obtenir rapidement l'erreur potentielle rencontrée dans la suite:

$ python manage.py test app1
...F..
----------------------------------------------------------------------
Ran 6 tests in 0.977s

FAILED (failures=1)


$ python manage.py test app1 --failfast
...F
======================================================================
[Traceback of the failing test]
----------------------------------------------------------------------
Ran 4 tests in 0.372s

FAILED (failures=1)


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow