Een aangepast gebruikersmodel testen met Coverage en de testtools van Django

Django biedt veel testtools; in deze tutorial gebruiken we een aantal ervan om een app te testen met een aangepast gebruikersmodel en authenticatie.

13 juli 2020 13:11
Thema's: Aangepast gebruikersmodel Authenticatie Testen

We zullen de app userauth testen die we in een eerdere tutorial hebben gemaakt. Deze bevat een aangepast gebruikersmodel en authenticatie met allauth. De opdracht om tests uit te voeren voor een specifieke app of map is:

python3 manage.py test userauth

Het argument test is vergelijkbaar met andere argumenten die Django gebruikt, zoals runserver, createsuperuser, migrate enz. Mogelijk verschijnt de volgende foutmelding:

Got an error creating the test database: permission denied to create database

Dat komt omdat Django een testdatabase zal maken, die zal worden vernietigd wanneer alle tests zijn uitgevoerd. Je moet de databasegebruiker dus toestemming geven om deze testdatabase te maken. Ga naar de psql-opdrachtprompt:

psql postgres

en typ de volgende opdracht:

ALTER USER usr_pet CREATEDB;

waarbij usr_pet de databasegebruiker is. Verlaat psql met \q.

Een andere fout die kan optreden is:

ValueError: Missing staticfiles manifest entry for 'images/favicon-32x32.png'

of iets dergelijks. Dit komt door het gebruik van ManifestStaticFilesStorage in de parameter STATICFILES_STORAGE in onze instellingen. Django raadt aan om deze parameter tijdens het testen op de standaardwaarde in te stellen, dus zet een comment-teken voor de regel in de instellingen waar deze parameter is gedefinieerd.

Als we nu python3 manage.py test userauth uitvoeren, zal Django vertellen hoeveel tests zijn uitgevoerd en eventuele fouten weergeven. Zelfs zonder tests kunnen we het testcommando uitvoeren; Django zal de installatie doorlopen en wat andere dingen doen, dus zal OK teruggeven of eventuele fouten detecteren.

Er zijn veel manieren om te testen en veel artikelen over de beperkingen ervan. Twee populaire testmethoden zijn dekkingstests (bijv. met Coverage) en browserautomatisering (bijv. met Selenium). Hier beperken we ons tot dekkingstesten, die gemakkelijk kunnen worden geïntegreerd met Django. Installeer het pakket:

pip3 install coverage

Voeg het toe aan requirements.txt en voeg coverage toe aan INSTALLED_APPS. Voer het uit met:

coverage run --source=userauth manage.py test userauth

De vlag source vertelt Coverage om alleen de code in de directory userauth te meten, en de userauth aan het einde van de opdracht vertelt Django alleen om tests uit te voeren in de app userauth. Het volgende commando

coverage html

maakt een directory htmlcov aan in de projectdirectory met daarin een bestand index.html. Klik er met de rechtermuisknop op en kies open in browser Voor onze app userauth zien we dat een substantieel deel van de code al bezocht is, ook zonder tests. Dat komt omdat we in onze app maximaal gebruik hebben gemaakt van de ingebouwde klassen en methoden van Django en allauth. Laten we enkele tests maken voor de stukjes code die niet werden behandeld, te beginnen met een eenvoudige: de string-representatie van het aangepaste gebruikersmodel. Teruggrijpend op onze eerdere tutorial over ons gebruikersmodel:

def __str__(self):
    return f"{self.username}: {self.first_name} {self.last_name}"

We zullen Django's klasse Testcase gebruiken. Eerst maken we een gebruiker in onze set-up met setUpTestData, daarna testen we met assertEqual of de string-representatie is wat het zou moeten zijn:

from .models import CustomUser
from django.test import TestCase

class TestCustomUser(TestCase):

    @classmethod
    def setUpTestData(cls):
       cls.user = CustomUser.objects.create(username="userJohnDoe", password="secretpassword", first_name="John", last_name="Doe")

    def test_string_representation_of_customuser(self):
        expected_representation_customuser = "userJohnDoe: John Doe"
        self.assertEqual(expected_representation_customuser, str(self.user))

Als we weer Coverage uitvoeren, blijkt dat we dat stuk code nu hebben bezocht. Dit is natuurlijk een heel eenvoudig voorbeeld. Laten we de get_absolute_url-methode van ons model testen. Nogmaals uit onze eerdere tutorial:

def get_absolute_url(self):
    return reverse('account_profile')

met het volgende in onze urls.py:

path('profile/', profile_view, name='account_profile'),

en in onze views.py:

def profile_view(request):
    return render(request, 'account/profile.html')

Een call naar profile_view met een ingelogde gebruiker moet een geldig antwoord geven. De Django-documentatie beschrijft hoe een gebruikerslogin kan worden gesimuleerd met RequestFactory, dat een request-object genereert. We importeren RequestFactory en profile_view:

from .views import profile_view
from django.test import RequestFactory

In onze setUpTestData voegen we de regel toe:

cls.factory = RequestFactory()

en dan is onze test (met behulp van het HttpResponse-attribuut status_code):

def test_profile_view_with_user_gets_valid_response(self):
    request = self.factory.get(self.user.get_absolute_url())
    # log user in
    request.user = self.user
    self.assertEqual(profile_view(request).status_code, 200)

Onze laatste test voor userauth is voor LoginForm in forms.py. Definieer een tweede gebruiker in setUpTestData:

cls.user2 = CustomUser.objects.create(username="undefined", password="undefined", first_name="undefined", last_name="undefined")

We kunnen de eerste gebruiker niet hergebruiken, omdat de tests niet noodzakelijkerwijs worden uitgevoerd in de volgorde waarin ze zijn gedefinieerd, wat betekent dat wijzigingen in de ene test van invloed kunnen zijn op hergebruikte variabelen in een andere. Definieer wat formuliergegevens, voer deze in in SignupForm en gebruik de signup methode van het formulier voor de nieuw gemaakte gebruiker om te controleren of de methode doet wat hij moet doen:

def test_signup_form(self):
    form_data = {'first_name': "Jane", 'last_name': "Doe", 'display_name': "Jane Doe"}
    form = SignupForm(data=form_data)
    self.assertTrue(form.is_valid())
    form.signup(self, user=self.user2)
    self.assertEqual(self.user2.display_name, "Jane Doe")

coverage opnieuw uitvoeren laat zien dat we meer dan 95% van de code van userauth hebben behandeld en dat slechts enkele eenvoudige coderegels ontbreken. We beëindigen het testen van deze app en gaan verder met de volgende.

Reageer op dit artikel (log eerst in of bevestig hieronder met naam en email)