2010-02-13 21 views
22

Qualcuno ha probabilmente già sviluppato una tecnica per alleviare la noia per la seguente prova di unità idiomatica:Django unit testing per modulo di modifica

  1. ottenere un URL con i dati dei moduli già popolate
  2. POST una forma riveduta con una sola o più campi modificati
  3. Controllo risposta (profitto!)

Fase 2 è il più noioso, in bicicletta attraverso i campi del modulo. Esistono hack che fanno risparmiare tempo per testare i moduli di Django?

[Aggiornamento: Non sto testando la gestione dei moduli di Django. Sto verificando che la mia applicazione produce risposte corrette quando un utente apporta modifiche a un modulo. Questa è un'applicazione che elabora le informazioni cliniche, quindi molte possibili risposte al test.]

+0

stai scrivendo i test che hanno quei tre passaggi per ogni campo della forma? E le tue forme hanno molti campi? Hai un sacco di campi da testare? È questo che stai dicendo? –

+0

Lotto di moduli, molti campi. Ma ogni test è un test per un risultato particolare (ad esempio, l'app informa correttamente l'utente che non riesce a qualificare completamente una diagnosi), non un test di ciascun campo. –

risposta

28

Dipende da cosa stai provando a testare. Preferirei i tuoi test un po 'più finemente di quanto sembra che tu stia facendo.

Se il codice che è necessario testare è la logica di convalida del modulo, quindi semplicemente istanziare la classe del modulo direttamente nei test, passarlo a vari dizionari di dati e chiamare .is_valid(), controllare gli errori corretti o la loro mancanza . Non è necessario coinvolgere richieste HTML o HTTP.

Se la logica di visualizzazione (che IMO deve essere ridotta a icona) che si sta testando, si vorrà probabilmente utilizzare il client di test, ma non è necessario eseguire test multistadio o molti test a questo livello. Nella logica delle verifiche di prova non vorrei raschiare l'HTML (cioè i template di test), userei response.context per estrarre l'oggetto form dal contesto.

Se ciò che si desidera verificare è che i modelli contengono il corretto HTML per rendere la forma effettivamente lavoro (al fine di rilevare gli errori come dimenticare di includere il modulo di gestione per un formset nel modello), io uso WebTest e django-webtest , che analizza il tuo HTML e semplifica il riempimento dei valori dei campi e invia il modulo come farebbe un browser.

+0

"Vorrei usare response.context per estrarre l'oggetto del modulo" <- Facendo questo ora. Non sto testando veramente l'HTML, solo i risultati di un processo. Grazie per il tuo post. –

2

Pensa attentamente al motivo per cui è necessario testare l'unità. Le forme fanno parte della funzionalità principale di Django e, come tale, sono ben coperte dai test delle unità di Django. Se tutto quello che stai facendo è la creazione/aggiornamento di base, che dalla tua domanda sembra essere il caso, non vedo alcun motivo per scrivere test unitari per questo.

+0

Forse ha una convalida personalizzata. –

+2

Sì, molte convalide personalizzate. Credimi, non sto cercando di creare più lavoro per me stesso. ;-) –

0

Stai forse cercando strumenti che fanno il test front end come twill o selenium

Entrambi questi generano codice Python, che possono essere inclusi nei test di Django, in modo che quando si esegue i test, si apre gli URL, pubblica i dati e controlla ciò che vuoi!

Dovrebbe aiutare a vedere these tests scritto per il selenio, per un'app django riutilizzabile open source.

0

Non vedo come o perché sono necessari test di unità per questo. Mi sembra che tu stia testando (e correggendo) il possibile input dell'utente, che è trattato molto semplicemente con la convalida del modulo di Django (e la convalida del modello in 1.2)

4

Non è chiaro, ma una ipotesi è che si hanno test come questo.

class TestSomething(TestCase): 
    fixtures = [ "..." ] 
    def test_field1_should_work(self): 
     response= self.client.get("url with form data already populated") 
     form_data = func_to_get_field(response) 
     form_data['field1']= new value 
     response= self.client.post("url", form_data) 
     self.assert() 
    def test_field2_should_work(self): 
     response= self.client.get("url with form data already populated") 
     form_data = func_to_get_field(response) 
     form_data['fields']= new value 
     response= self.client.post("url", form_data) 
     self.assert() 

In primo luogo, stai facendo troppo. Semplificare.

class TestFormDefaults(TestCase): 
    fixtures = [ "some", "known", "database" ] 
    def test_get_should_provide_defaults(self): 
     response= self.client.get("url with form data already populated") 
     self.assert(...) 

Quanto sopra dimostra che i valori predefiniti compongono i moduli.

class TestPost(TestCase): 
    fixtures = [ "some", "known", "database" ] 
    def test_field1_should_work(self): 
     # No need to GET URL, TestFormDefaults proved that it workd. 
     form_data= { expected form content based on fixture and previous test } 
     form_data['field1']= new value 
     response= self.client.post("url", form_data) 
     self.assert() 

Non perdere tempo a fare un "get" per ogni "post". Puoi provare - separatamente - che le operazioni GET funzionano. Una volta che hai questa prova, fai semplicemente i POST.

Se i POSTS sono altamente specifici della sessione e stateful, è comunque possibile eseguire un GET, ma non preoccuparsi di analizzare la risposta. Puoi provare (separatamente) che ha esattamente i campi giusti.

Per ottimizzare il tuo riposo, considera questo.

class TestPost(TestCase): 
    fixtures = [ "some", "known", "database" ] 
    def test_many_changes_should_work(self): 
     changes = [ 
      ('field1', 'someValue', 'some expected response'), 
      ('field2', 'someValue'), 
      ... 
     ] 
     for field, value, expected in changes: 
      self.client.get("url") # doesn't matter what it responds, we've already proven that it works. 
      form_data= { expected form content based on fixture and previous test } 
      form_data[field]= value 
      response self.client.post("url", form_data) 
      self.assertEquas(expected, who knows what) 

Quanto sopra funzionerà ovviamente, ma fa sembrare piccolo il numero di test.

+0

In suvated perché è la risposta più utile finora. Sto fondamentalmente facendo la tua ottimizzazione ora.GET è già in un test separato, ma lo sto usando qui per rimpiazzare la linea: "form_data = {contenuto del modulo previsto basato su fixture e test precedente}", mentre sto iterando attraverso i dispositivi generati automaticamente e si risparmia il lavoro di digitare manualmente i dettagli. –

+0

@Jeff Bauer: "salva il lavoro di digitare manualmente i dettagli"? Che cosa? Così rifattora i dati di default in 'setUp' o una superclasse o una funzione separata. Esistono un milione di tecniche di programmazione per ridurre la ridondanza, che possono essere utilizzate nei test di unità. Perché non stai semplicemente rielaborando le parti noiose? Non capisco cosa ci sia di tanto difficile progettare una classe di test che sia in qualche modo ottimale. –

+0

-1 per 'ovviamente' – thumbtackthief

20

django-webtest è perfetto per questi test:

from django_webtest import WebTest 

class MyTestCase(WebTest): 
    def test_my_view(self) 
     form = self.app.get('/my-url/').form 
     self.assertEqual(form['my_field_10'].value, 'initial value') 
     form['field_25'] = 'foo' 
     response = form.submit() # all form fields are submitted 

A mio parere è meglio di twill per il test Django perché fornisce l'accesso a Django interni in modo nativo di Django response.context, response.templates, self.assertTemplateUsed e self.assertFormError API è supportata.

D'altra parte è migliore del client di prova django nativo perché ha un'API molto più potente e facile.

Sono un po 'di parte;) ma credo che django-webtest sia ora il modo migliore per scrivere i test di django.

0

Ti consiglierei di dare un'occhiata agli strumenti del livello di test di accettazione come framework di test del robot o letucce che sono pensati solo per te che vuoi fare "Sto verificando che la mia applicazione produce risposte corrette quando un utente apporta modifiche una forma ", sembra più un test di accettazione (black-box) che un test unitario.

Per instace, Robot consente di definire i test in forma di tabella, si definisce il flusso di lavoro una volta e quindi è possibile definire facilmente un gruppo di test che esercitano il flusso di lavoro con dati diversi.

24

È possibile utilizzare response.context e form.initial per ottenere i valori necessari per inviare:

update_url = reverse('myobject_update',args=(myobject.pk,)) 

# GET the form 
r = self.client.get(update_url) 

# retrieve form data as dict 
form = r.context['form'] 
data = form.initial # form is unbound but contains data 

# manipulate some data 
data['field_to_be_changed'] = 'updated_value' 

# POST to the form 
r = self.client.post(update_url, data) 

# retrieve again 
r = self.client.get(update_url) 
self.assertContains(r, 'updated_value') # or 
self.assertEqual(r.context['form'].initial['field_to_be_changed'], 'updated_value') 
+0

Non sono sicuro se questo è ancora rilevante, ma sto lavorando su un progetto bloccato su Django 1.02 ed è r.context [0] ["form"] per ottenere il modulo. – Tom

+0

Grazie, questo funziona perfettamente per testare in modo funzionale le viste che utilizzano Forms/ModelForms! – mrooney

+0

Nota: il nome "modulo" non è garantito e dipende dal nome del contesto utilizzato nella visualizzazione. per esempio. Se lo fai per testare l'admin di Django, il nome è "adminform". – Cerin