# Documentazione Django 6.0.7

# Scrivere la tua prima applicazione in Django, parte 5

Questo tutorial inizia dove è terminato Tutorial 4 . Abbiamo costruito una applicazione web per i questionari ed adesso per essa creeremo alcuni testi automatici.

Dove trovare aiuto:

Se hai difficoltà a completare questo tutorial, per favore vai alla sezione Getting Help delle FAQ.

## Introduzione ai test automatici

### Cosa sono i test automatici?

I test sono routine che controllano il funzionamento del codice.

Il testing opera a livelli differenti. Alcuni test si posso applicare a piccoli dettagli (un modello particolare restituisce i valori come ci si aspetta?) mentre altri esaminano le operazioni del software a linee più generali (la sequenza di input dell’utente sul sito produce l’output desiderato?). Non è differente dal tipo di test che hai fatto nel Tutorial 2, usando la `shell` per esaminare il comportamento di un metodo o lanciando l’applicazione ed inserendo i dati per controllare come si comporta.

La differenza nell” automatizzare i test è che il sistema svolge i test al posto tuo. Tu crei un insieme di test una volta, e ogni volta che effettui una modifica alla tua applicazione, puoi controllare che il codice funzioni come ti aspettavi originariamente, senza dover impiegare tempo a svolgere test manualmente.

### Perché devi creare dei test

Quindi perche creare test, e perche ora?

Potresti sentire di avere abbastanza da fare solo per imparare Python / Django, e avere ancora un’altra cosa da imparare e da fare può sembrare gravoso e forse non necessario. Dopotutto, la nostra applicazione per i sondaggi ora funziona abbastanza bene; affrontare il problema della creazione di test automatizzati non lo farà funzionare meglio. Se la creazione dell’applicazione sondaggi è l’ultima parte della programmazione Django che farai mai, allora è vero, non hai bisogno di sapere come creare test automatizzati. Ma se non è così, ora è un ottimo momento per imparare.

#### I test ti faranno risparmiare tempo

Fino a un certo punto accertarsi che l’app “sembra funzionare” potrebbe essere un test soddisfacente. In applicazioni sofisticate, potresti avere dozzine di complesse interazioni tra i componenti.

Una modifica in uno di questi componenti potrebbe avere conseguenze inaspettate sul comportamento dell’applicazione. Controllare che «tutto funzioni» potrebbe voler dire provare le funzionalità del tuo codice con decine di differenti varianti dei tuoi dati di test per essere sicuro di non aver danneggiato nulla con l’ultima modifica - non un bell’impiego del tuo tempo.

Specialmente quando i test automatici possono fare tutto questo per te in pochi secondi. Se qualcosa va storto, i test ti assisteranno nel trovare il codice che ha causato il comportamento inaspettato.

A volte, potrebbe sembrare un lavoro che toglie tempo alla tua produttività, dover programmare per affrontare il compito poco affascinante e poco emozionante di scrivere test, soprattutto quando sai che il tuo codice sta funzionando a dovere.

Tuttavia, scrivere test è molto più soddisfacente che passare ore a testare manualmente l’applicazione o cercare di identificare la causa di un problema appena introdotto.

#### I test non si limitano a identificare i problemi, ma li prevengono

È uno sbaglio pensare che i test siano un aspetto negativo dello sviluppo.

Senza test, il fine o il comportamento atteso di una applicazione potrebbe essere piuttosto opaco. Anche se è il tuo codice, talvolta ti ritroverai a frugarci dentro cercando di capire che cosa fa esattamente.

I test operano un cambiamento; illuminano il tuo codice dall’interno e quando qualcosa va storto, gettano luce sulla parte che ha fallito – anche quando non ti sei nemmeno accorto che ha fallito.

#### I test rendono il tuo codice più attraente

Potresti anche aver creato un software brillante ma potresti scoprire che molti altri sviluppatori si rifiutano di dargli uno sguardo perchè manca dei test; senza test, non ci faranno affidamento. Jacob Kaplan-Moss, uno degli sviluppatori originali di Django, dice «Il codice senza test è marcio dalle fondamenta».

Il fatto che altri sviluppatori vogliano vedere i test nel tuo software prima di prenderlo sul serio è ancora un altro motivo per iniziare a scrivere test.

#### I test aiutano i gruppi a lavorare insieme

I punti precedenti sono scritti dal punto di vista di un solo sviluppatore che mantiene una applicazione. Applicazioni complesse saranno manutenute da team. I test garantiscono che i colleghi non rompano inavvertitamente il codice (e che tu non danneggi il loro senza volerlo). Se vuoi vivere della programmazione Django, devi essere bravo a scrivere test!

## Strategie basilari di test

Ci sono tanti modi per scrivere i test.

Some programmers follow a discipline called «test-driven development»; they actually write their tests before they write their code. This might seem counterintuitive, but in fact it’s similar to what most people will often do anyway: they describe a problem, then create some code to solve it. Test-driven development formalizes the problem in a Python test case.

Molto più spesso, un principiante del testing creerà un po” di codice e poi deciderà che debba avere dei test. Magari sarebbe stato meglio scrivere alcuni test prima ma non è mai troppo tardi per iniziare.

A volte è difficile capire da dove cominciare per scrivere dei test. Se hai scritto molte centinaia di righe di Python, scegliere qualcosa da testare può non essere semplice. In questi casi, puoi scrivere il tuo primo test la prossima volta che apporti un cambiamento, che sia aggiungere una nuova feature o risolvere un bug.

Quindi facciamo le cose in maniera giusta.

## Scrivere il nostro primo test

### Identifichiamo un bug

Fortunatamente, c’è un piccolo bug nell’applicazione `polls` che possiamo risolvere: il metodo `Question.was_published_recently()` restituisce `True` se `Question` è stata pubblicata entro un giorno (che è corretto) ma anche se il campo `pub_date` di `Question` è futuro (che certamente non va bene).

Conferma il bug usando la `shell` per controllare il metodo su una domanda la cui data è futura:

/ 

    $ python manage.py shell

    ...\> py manage.py shell

    >>> import datetime
    >>> from django.utils import timezone
    >>> # create a Question instance with pub_date 30 days in the future
    >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
    >>> # was it published recently?
    >>> future_question.was_published_recently()
    True

Siccome le cose nel futuro non sono “recenti”, questo è chiaramente sbagliato.

### Creare un test per esporre un bug

Quel che abbiamo appena fatto nella `shell` per testare il problema è esattamente quel che facciamo nel nostro test automatizzato, quindi convertiamolo in un test automatico.

Il posto che per convenzione ospita i test di una applicazione è il file `tests.py`; il sistema di testing troverà automaticamente i test in ogni file il cui nome inizia per `test`.

Metti quanto segue nel file `tests.py` nell’applicazione `polls`:

`polls/tests.py`

    import datetime

    from django.test import TestCase
    from django.utils import timezone

    from .models import Question

    class QuestionModelTests(TestCase):
        def test_was_published_recently_with_future_question(self):
            """
            was_published_recently() returns False for questions whose pub_date
            is in the future.
            """
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertIs(future_question.was_published_recently(), False)

Qui abbiamo creato una sottoclasse `django.test.TestCase` con un metodo che crea una istanza di `Question` con una `pub_date` nel futuro. Quindi controlliamo l’output di `was_published_recently()` - che dovrebbe essere False.

### Eseguire i test

Nel terminale, possiamo eseguire il nostro test:

/ 

    $ python manage.py test polls

    ...\> py manage.py test polls

and you’ll see something like:

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/path/to/djangotutorial/polls/tests.py", line 16, in test_was_published_recently_with_future_question
        self.assertIs(future_question.was_published_recently(), False)
    AssertionError: True is not False

    ----------------------------------------------------------------------
    Ran 1 test in 0.001s

    FAILED (failures=1)
    Destroying test database for alias 'default'...

Errore diverso?

Se al contrario ottieni un `NameError`, potresti esserti perso uno step nella Parte 2 dove abbiamo aggiunto gli import di `datetime` e `timezone` a `polls/models.py`. Copia gli import da quella sezione e prova a lanciare di nuovo i test.

Quello che è successo è questo

- `manage.py test polls` cerca i test nell’applicazione `polls`

- ha trovato una sottoclasse di tipo `django.test.TestCase`

- ha creato un database speciale con lo scopo di testing

- cerca metodi di test - quelli i cui nomi iniziano con `test`

- in `test_was_published_recently_with_future_question` ha creato una istanza di `Question` il cui campo `pub_date` è avanti di 30 giorni nel futuro

- …ed usando il metodo `assertIs()`, ha scoperto che il suo `was_published_recently()` restituisce `True`, anche se volevamo che restituisse `False`

Il test ci informa su quale test è fallito e ci dice anche la linea su cui si è verificato il fallimento.

### Correggere il bug

Sappiamo già che il problema è: `Question.was_published_recently()` dovrebbe restituire `False` se la sua `pub_date` è futura. Correggi il metodo in `models.py`, così che restiuisca `True` solo se la data è nel passato:

`polls/models.py`

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

and run the test again:

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s

    OK
    Destroying test database for alias 'default'...

Dopo aver identificato un bug, abbiamo scritto un test che lo evidenzia e corretto il bug nel codice in modo che il nostro test venga superato.

Molte altre cose potrebbero andare storte con la nostra applicazione in futuro, ma possiamo essere sicuri che non reintrodurremo inavvertitamente questo specifico bug perché l’esecuzione del test ci avviserà immediatamente. Possiamo considerare questa piccola parte dell’applicazione bloccata in modo sicuro per sempre.

### Test più completi

Già che ci siamo, possiamo aggiustare meglio il metodo `was_published_recently()`; in effetti, potrebbe essere estremamente imbarazzante se nel tentativo di correggere un bug ne introducessimo un altro.

Aggiungi altri due metodi di test alla stessa classe, per testare il comportamento del metodo in modo più esteso:

`polls/tests.py`

    def test_was_published_recently_with_old_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is older than 1 day.
        """
        time = timezone.now() - datetime.timedelta(days=1, seconds=1)
        old_question = Question(pub_date=time)
        self.assertIs(old_question.was_published_recently(), False)

    def test_was_published_recently_with_recent_question(self):
        """
        was_published_recently() returns True for questions whose pub_date
        is within the last day.
        """
        time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
        recent_question = Question(pub_date=time)
        self.assertIs(recent_question.was_published_recently(), True)

Ed adesso abbiamo tre test che confermano che `Question.was_published_recently()` restuisce valori ragionevoli per le domande passate, recenti e future.

Di nuovo, `polls` è una applicazione minimale ma a prescindere da quanto cresca in complessità o con quale codice interagisca, adesso abbiamo qualche garanzia che i metodi per cui abbiamo scritto test si comporteranno come ci aspettiamo.

## Testare una vista

L’applicazione dei sondaggi agisce in modo abbastanza indiscriminato: pubblicherà ogni domanda, incluse quelle il cui campo `pub_date` è futuro. Dovremmo migliorare questa cosa. Impostare una `pub_date` nel futuro significa che la domanda deve essere pubblicata in quel momento ma fino ad allora deve essere invisibile.

### Un test per una vista

Quando abbiamo corretto il bug sopra, abbiamo scritto prima il test e poi il codice per risolverlo. In effetti è stato un esempio di sviluppo basato sui test (TDD), ma non importa in quale ordine svolgiamo il lavoro.

Nel nostro primo test, ci siamo concentrati sul comportamento interno del codice. Per questo test, vogliamo verificarne il comportamento come se fosse fatto da un utente tramite un browser web.

Prima di provare a riparare qualcosa, diamo un’occhiata agli strumenti a nostra disposizione.

### Il client test di Django

Django provides a test `Client` to simulate a user interacting with the code at the view level. We can use it in `tests.py` or even in the `shell`.

Cominciamo di nuovo con la `shell`, dove dobbiamo fare un paio di cose che non saranno necessarie in `tests.py`. La prima è approntare l’ambiente di test nella `shell`:

/ 

    $ python manage.py shell

    ...\> py manage.py shell

    >>> from django.test.utils import setup_test_environment
    >>> setup_test_environment()

`setup_test_environment()` installa un renderer di template che ci permette di esaminare alcuni attributi in più sulle responses, come `response.context` che altrimenti non sarebbe disponibile. Nota che questo metodo non inizializza un database di test, ciò che segue quindi sarà lanciato sul database esistente e l’output può differire leggermente, dipendentemente dalle domande che hai già creato. Puoi ottenere risultati inaspettati se la tua `TIME_ZONE` in `settings.py` non è corretta. Se non ti sei ricordato di impostarla prima, controllala prima di continuare.

Next we need to import the test client class (later in `tests.py` we will use the `django.test.TestCase` class, which comes with its own client, so this won’t be required):

    >>> from django.test import Client
    >>> # create an instance of the client for our use
    >>> client = Client()

With that ready, we can ask the client to do some work for us:

    >>> # get a response from '/'
    >>> response = client.get("/")
    Not Found: /
    >>> # we should expect a 404 from that address; if you instead see an
    >>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
    >>> # omitted the setup_test_environment() call described earlier.
    >>> response.status_code
    404
    >>> # on the other hand we should expect to find something at '/polls/'
    >>> # we'll use 'reverse()' rather than a hardcoded URL
    >>> from django.urls import reverse
    >>> response = client.get(reverse("polls:index"))
    >>> response.status_code
    200
    >>> response.content
    b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#x27;s up?</a></li>\n    \n    </ul>\n\n'
    >>> response.context["latest_question_list"]
    <QuerySet [<Question: What's up?>]>

### Migliorare la nostra vista

La lista di questionari mostra i questionari che non sono ancora stati pubblicati (cioè quelli la cui `pub_date` è futura). Mettiamo a posto questa cosa.

Nel Tutorial 4 abbiamo introdotto una view basate su classe, basata su `ListView`:

`polls/views.py`

    class IndexView(generic.ListView):
        template_name = "polls/index.html"
        context_object_name = "latest_question_list"

        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by("-pub_date")[:5]

Dobbiamo modificare il metodo `get_queryset()` e cambiarlo in modo che controlli anche la data confrontandola con `timezone.now()`. Per prima cosa dobbiamo aggiungere un import:

`polls/views.py`

    from django.utils import timezone

e poi dobbiamo correggere il metodo `get_queryset` in questo modo:

`polls/views.py`

    def get_queryset(self):
        """
        Return the last five published questions (not including those set to be
        published in the future).
        """
        return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[
            :5
        ]

`Question.objects.filter(pub_date__lte=timezone.now())` returns a queryset containing `Question`s whose `pub_date` is less than or equal to - that is, earlier than or equal to - `timezone.now()`.

### Testare la nostra nuova vista

Now you can satisfy yourself that this behaves as expected by firing up `runserver`, loading the site in your browser, creating a few `Question` entries with dates in the past and future, and checking that only those that have been published are listed. You don’t want to have to do that every single time you make any change that might affect this - so let’s also create a test, based on our `shell` session above.

Aggiungere quanto segue a `polls/tests.py`:

`polls/tests.py`

    from django.urls import reverse

e creeremo una funzione scorciatoia per creare domande così come una nuiova classe di test:

`polls/tests.py`

    def create_question(question_text, days):
        """
        Create a question with the given `question_text` and published the
        given number of `days` offset to now (negative for questions published
        in the past, positive for questions that have yet to be published).
        """
        time = timezone.now() + datetime.timedelta(days=days)
        return Question.objects.create(question_text=question_text, pub_date=time)

    class QuestionIndexViewTests(TestCase):
        def test_no_questions(self):
            """
            If no questions exist, an appropriate message is displayed.
            """
            response = self.client.get(reverse("polls:index"))
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, "No polls are available.")
            self.assertQuerySetEqual(response.context["latest_question_list"], [])

        def test_past_question(self):
            """
            Questions with a pub_date in the past are displayed on the
            index page.
            """
            question = create_question(question_text="Past question.", days=-30)
            response = self.client.get(reverse("polls:index"))
            self.assertQuerySetEqual(
                response.context["latest_question_list"],
                [question],
            )

        def test_future_question(self):
            """
            Questions with a pub_date in the future aren't displayed on
            the index page.
            """
            create_question(question_text="Future question.", days=30)
            response = self.client.get(reverse("polls:index"))
            self.assertContains(response, "No polls are available.")
            self.assertQuerySetEqual(response.context["latest_question_list"], [])

        def test_future_question_and_past_question(self):
            """
            Even if both past and future questions exist, only past questions
            are displayed.
            """
            question = create_question(question_text="Past question.", days=-30)
            create_question(question_text="Future question.", days=30)
            response = self.client.get(reverse("polls:index"))
            self.assertQuerySetEqual(
                response.context["latest_question_list"],
                [question],
            )

        def test_two_past_questions(self):
            """
            The questions index page may display multiple questions.
            """
            question1 = create_question(question_text="Past question 1.", days=-30)
            question2 = create_question(question_text="Past question 2.", days=-5)
            response = self.client.get(reverse("polls:index"))
            self.assertQuerySetEqual(
                response.context["latest_question_list"],
                [question2, question1],
            )

Diamo un occhiata da vicino.

La prima è una funzione scorciatoia, `create_question`, che astrae alcune ripetizioni nel processo di creazione delle domande.

`test_no_questions` doesn’t create any questions, but checks the message: «No polls are available.» and verifies the `latest_question_list` is empty. Note that the `django.test.TestCase` class provides some additional assertion methods. In these examples, we use `assertContains()` and `assertQuerySetEqual()`.

In `test_past_question`, creiamo una domanda e verifichiamo che appaia nell’elenco.

In `test_future_question`, creiamo una domanda con una `pub_date` nel futuro. Il database viene reimpostato per ogni metodo di test, quindi la prima domanda non c’è più e così, di nuovo, l’indice non dovrebbe mostrare nessuna domanda.

E così via. In effetti, stiamo usando i test per raccontare la storia dell’input dell’admin e la user experience sul sito, controllando ad ogni stato e per ogni cambiamento del sito, che i cambiamenti vengano pubblicati.

### Testare la `DetailView`

Quel che abbiamo funziona bene; eppure, anche se le domande future non appaiono nell”indice, gli utenti possono ancora raggiungere se conoscono o indovinano la URL giusta. Quindi dobbiamo aggiungere una restrizione simile a `DetailView`:

`polls/views.py`

    class DetailView(generic.DetailView):
        ...

        def get_queryset(self):
            """
            Excludes any questions that aren't published yet.
            """
            return Question.objects.filter(pub_date__lte=timezone.now())

Dovremmo quindi aggiungere alcuni test, per verificare che una `Question` la cui `pub_date` sia nel passato, possa essere visualizzata e quella con `pub_date` nel futuro non lo sia:

`polls/tests.py`

    class QuestionDetailViewTests(TestCase):
        def test_future_question(self):
            """
            The detail view of a question with a pub_date in the future
            returns a 404 not found.
            """
            future_question = create_question(question_text="Future question.", days=5)
            url = reverse("polls:detail", args=(future_question.id,))
            response = self.client.get(url)
            self.assertEqual(response.status_code, 404)

        def test_past_question(self):
            """
            The detail view of a question with a pub_date in the past
            displays the question's text.
            """
            past_question = create_question(question_text="Past Question.", days=-5)
            url = reverse("polls:detail", args=(past_question.id,))
            response = self.client.get(url)
            self.assertContains(response, past_question.question_text)

### Idee per altri test

Dovremmo aggiungere un metodo simile a `get_queryset` a `ResultsView` e creare una nuova classe di test per quella view. Sarà molto simile a quello che abbiamo appena creato; in effetti ci saranno un sacco di ripetizioni.

We could also improve our application in other ways, adding tests along the way. For example, it’s pointless that a `Question` with no related `Choice` can be published on the site. So, our views could check for this, and exclude such `Question` objects. Our tests would create a `Question` without a `Choice`, and then test that it’s not published, as well as create a similar `Question` with at least one `Choice`, and test that it is published.

Perhaps logged-in admin users should be allowed to see unpublished `Question` entries, but not ordinary visitors. Again: whatever needs to be added to the software to accomplish this should be accompanied by a test, whether you write the test first and then make the code pass the test, or work out the logic in your code first and then write a test to prove it.

Ad un certo punto sarai obbligato a guardare i tuoi test e chiederti se il tuo codice soffra di eccesso di test , il che ci porta a:

## Nei test, di più è meglio

Potrebbe sembrare che i nostri test aumentino senza controllo. A questa velocità ci sarà in breve tempo più codice nei nostri test che nella nostra applicazione, e la ripetizione è antiestetica, in comparazione con l’eleganza della brevità del resto del nostro codice.

** Non importa **. Lascia che crescano. Per la maggior parte puoi scrivere un test una volta e poi dimenticartene. Continuerà a svolgere la sua funzione mentre continui a sviluppare il programma.

Sometimes tests will need to be updated. Suppose that we amend our views so that only `Question` entries with associated `Choice` instances are published. In that case, many of our existing tests will fail - telling us exactly which tests need to be amended to bring them up to date, so to that extent tests help look after themselves.

Al peggio, dal momento che continui a sviluppare, potrai trovarti ad avere alcuni test che ora sono ridondanti. Nonostante questo, non è un problema; testare la ridondanza è una buona prassi.

Finché i tuoi test sono organizzati in modo ragionevole, non diventeranno ingestibili. Le buone regole pratiche includono avere:

- Una `TestClass` separata per ogni model o view

- un metodo test separato per ogni set di condizioni che tu vuoi testare

- nomi di metodi di test che descrivono la loro funzione

## Altri test

Questo tutorial introduce solo alcune delle basi del testing. C’è molto di più che puoi fare ed una serie di strumenti molto utili sono a tua disposizione per realizzare cose molto ingegnose.

Per esempio, mentre alcuni nostri test qui hanno coperto un po” di logica interna di un modello ed il modo in cui le view pubblicano le informazioni, puoi usare un framework «in-browser» come Selenium per controllare il modo in cui il tuo HTML viene mostrato nel browser. Questi tool inoltre ti permettono di controllare non solo il comportamento del tuo codice Django ma anche, per esempio, del tuo JavaScript. E” veramente fantastico vedere come i test lanciano il browser e ci interagiscono, come se si trattasse di un essere umano! Django include `LiveServerTestCase` per facilitare l’integrazione con tool come Selenium.

Se hai un’applicazione complessa, potresti voler eseguire i test automaticamente ad ogni commit ai fini dell’integrazione continua, in modo che il controllo di qualità sia esso stesso, almeno parzialmente, automatizzato.

Un buon modo per individuare parti della tua applicazione non testate è controllare la code coverage. Questo aiuta anche a identificare codice morto o fragile. Se tu non riesci a testare un pezzo di codice, di solito significa che quel codice dovrebbe essere rimosso o rielaborato. La Coverage aiuta a identificare codice non più in uso. Vedi Integration with coverage.py per avere dettagli.

Testare in Django contiene informazioni complete sui test.

## Cos’altro?

Per i dettagli completi sui test, vedi Testare in Django.

Quando ti senti pronto e preparato nel testare le view Django, leggi parte 6 di questo tutorial per imparare a gestire i file statici.