Szukaj…


Testowanie - kompletny przykład

Zakłada się, że przeczytałeś dokumentację dotyczącą rozpoczęcia nowego projektu Django. Załóżmy, że główna aplikacja w twoim projekcie ma nazwę td (skrót od wersji testowej). Aby utworzyć pierwszy test, utwórz plik o nazwie test_view.py i skopiuj do niego następującą treść.

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)

Możesz uruchomić ten test przez

 ./manage.py test

i najbardziej naturalnie zawiedzie! Zobaczysz błąd podobny do następującego.

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

Dlaczego tak się dzieje? Ponieważ nie zdefiniowaliśmy dla tego widoku! Więc zróbmy to. Utwórz plik o nazwie views.py i umieść w nim następujący kod

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

Następnie zamapuj go na / hello /, edytując urls py w następujący sposób:

from td import views

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

Teraz uruchom ponownie ./manage.py test ponownie i viola !!

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

OK

Skuteczne testowanie modeli Django

Zakładając klasę

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

Przykłady testowania

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):
        ...

Kilka punktów

  • created_properly testy służą do weryfikacji właściwości stanu modeli django. Pomagają uchwycić ustawienia witryny, w których zmieniliśmy wartości domyślne, ścieżki_pliku_pliku itp.
  • absolute_url może wydawać się trywialna, ale przekonałem się, że pomogło mi to uniknąć błędów przy zmianie ścieżek URL
  • Podobnie piszę przypadki testowe dla wszystkich metod zaimplementowanych w modelu (używając mock obiektów itp.)
  • Poprzez zdefiniowanie wspólnego BaseModelTestCase możemy skonfigurować niezbędne relacje między modelami, aby zapewnić prawidłowe testowanie.

W razie wątpliwości napisz test. Trywialne zmiany zachowań są wychwytywane przez zwracanie uwagi na szczegóły, a dawno zapomniane fragmenty kodu nie powodują niepotrzebnych kłopotów.

Testowanie kontroli dostępu w widokach Django

tl; dr : Utwórz klasę podstawową, która definiuje dwa obiekty użytkownika (powiedz user i another_user user ). Utwórz pozostałe modele i zdefiniuj trzy wystąpienia Client .

  • self.client : reprezentujący user zalogowanego w przeglądarce
  • self.another_client reprezentowanie another_user „s klient
  • self.unlogged_client : Reprezentowanie self.unlogged_client osoby

Teraz uzyskaj dostęp do wszystkich swoich publicznych i prywatnych adresów URL z tych trzech obiektów klienckich i dyktuj oczekiwane odpowiedzi. Poniżej przedstawiłem strategię dotyczącą obiektu Book który może być private (będący własnością kilku uprzywilejowanych użytkowników) lub public (widoczny dla wszystkich).

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)

Baza danych i testy

Podczas testowania Django używa specjalnych ustawień bazy danych, aby testy mogły normalnie korzystać z bazy danych, ale domyślnie są uruchamiane na pustej bazie danych. Zmiany bazy danych w jednym teście nie będą widoczne przez inny. Na przykład oba następujące testy zostaną zaliczone:

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)

Lampy

Jeśli chcesz, aby obiekty bazy danych były używane przez wiele testów, utwórz je w metodzie setUp przypadku testowego. Dodatkowo, jeśli zdefiniowałeś urządzenia w swoim projekcie django, można je uwzględnić w następujący sposób:

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

Domyślnie django szuka urządzeń w katalogu fixtures w każdej aplikacji. Dalsze katalogi można ustawić za pomocą ustawienia FIXTURE_DIRS :

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

Załóżmy, że utworzyłeś model w następujący sposób:

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

Wtedy twoje urządzenia .json mogłyby wyglądać tak:

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

Ponownie użyj testowej bazy danych

Aby przyspieszyć uruchamianie testowe, możesz nakazać zarządzającemu polecenie ponownego użycia testowej bazy danych (i zapobiec tworzeniu go przed usunięciem i usuwaniu po każdym uruchomieniu testowym). Można to zrobić za pomocą flagi keepdb (lub skrót -k ) w następujący sposób:

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

Ogranicz liczbę wykonanych testów

Można ograniczyć testy wykonywane przez manage.py test , określając, które moduły powinny zostać wykryte przez testera:

# 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

Jeśli chcesz uruchomić kilka testów, możesz przekazać wzór nazw plików. Na przykład możesz chcieć uruchomić tylko testy obejmujące twoje modele:

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

OK

Wreszcie można zatrzymać zestaw testów przy pierwszym niepowodzeniu, używając opcji --failfast . Ten argument pozwala szybko uzyskać potencjalny błąd napotkany w pakiecie:

$ 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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow