Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Vođen testovima



Vodič za testiranje Django # 2


Kao drugi deo ovog serijala o testiranju Django (Deo I ako ste ga propustili), mi ćemo nastaviti sa proširenjem testiranja na cenjenoj "glasačkoj" app.

Kao i u prethodnom postu, kod se može naći na Github sa oznakama za obeležavanje našeg napredka na tom putu.

Gde smo stali na prethodnim postovima, dodali smo neke testove za osnovne HTTP GET zahteve. Idemo da proširimo gledanje ka:

● Testiranju zahteva postu
● Testiranju oblika
● Testiranju modela

Takođe ćemo poboljšati originalni kod, tako da možete videti kako da počnete testiranje druge uobičajene aspekte


Testiranje poruke


Prirodni deo svake web aplikacije, POST zahtevi su uobičajeni (i dobri) načini da korisnik unosi podatke u aplikaciju. U trenutnoj "glasačkoj" codebase, postoji jedan stav da se rukuje POSTom. Obezbeđenje da radi pravu stvar i sa nevažećim i važećim podatacima je važno za testiranje..

Tag: 05-poruke-okolo
Krenućemo sa testiranjem dobrih podatake, budući da je to često najlakši deo poruke za testiranje Dodajte sledeći metod ma


PollsViewsTestCase:

def test_good_vote(self):
      poll_1 = Poll.objects.get(pk=1)
      self.assertEqual(poll_1.choice_set.get(pk=1).votes, 1)

      resp = self.client.post('/polls/1/vote/', {'choice': 1})
      self.assertEqual(resp.status_code, 302)
      self.assertEqual(resp['Location'], 'http://testserver/polls/1/results/')

      self.assertEqual(poll_1.choice_set.get(pk=1).votes, 2)


Prvo smo dopunili anketu sa kojom ćemo raditi iz baze i obezbediti (kao razumnu stvar) da je prvi izbor ima pravi broj glasova. Ovo omogućava da, ukoliko se pribor ili ponašanje menja u budućnosti, mi ćemo znati odmah da nešto nije u redu.

Mi koristimo nove metode (self.client.post) da pošaljemo zahtevani POST na željenu URL. Ovaj metod može da potraje nekoliko kwargs, ali za sada, pružamo argumente za podatke, što jekoji je jednostavan rečnik koji simulira forma-kodirane podatke koji dolaze iz . Pošto je naš šablon samo popunjava izbor sa PK, mi smo samo prolazimo u tome.

Onda smo uradili proveru da vidimo šta je izmenjen statusa, ovaj put smo u potrazi za preusmeravanjima (302) umesto OK (200). Takođe proveravamo da li je zaglavlje I lokacija poslata, da bi bili sigurni da se preusmeravaju na pravo mesto. Django je testiranje infrastrukture na domenu http://testserver/, jer ne postoji pravi domen za rad sa jednim.

Konačno, možemo učitati još jedan primerak prvog izbora i obezbediti da je broj glasova bio uvećan.

Kada pokrenete testovi, trebalo bi da:


....
----------------------------------------------------------------------
Ran 4 tests in 0.374s

OK


Međutim, kao što svako ko je ikada bio korisnik orijentisanih programa zna, stvari ne idu uvek u pravom smeru. Tako ćemo dodati još jedan metod test da proverimo sve stvari koje mogu da krene naopako. Dodajte sledeći toPollsViewsTestCase


def test_bad_votes(self):
      # Ensure a non-existant PK throws a Not Found.
      resp = self.client.post('/polls/1000000/vote/')
      self.assertEqual(resp.status_code, 404)

      # Sanity check.
      poll_1 = Poll.objects.get(pk=1)
      self.assertEqual(poll_1.choice_set.get(pk=1).votes, 1)

      # Send no POST data.
      resp = self.client.post('/polls/1/vote/')
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['error_message'], "You didn't select a choice.")

      # Send junk POST data.
      resp = self.client.post('/polls/1/vote/', {'foo': 'bar'})
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['error_message'], "You didn't select a choice.")

      # Send a non-existant Choice PK.
      resp = self.client.post('/polls/1/vote/', {'choice': 300})
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['error_message']

metod: Prvo i najlakše je da se uverite da sigurno pokušavaju da glasaju na nepostojeću Anketu koja ne radi. Mi onda uradimo istu proveru kao i pre, samo da bi se uverili da li se stvari menjaju kasnije, mi ne idemo nalov na divlje guske bezveze.

Oonda probamo da kažemo da nema POST podataka, koji bi trebalo da vrate 200 i poruku o grešci. Pratimo POST podatke, ali ne uključujemo podatke sa kojima smo zabrinuti. Konačno, mi smo testirali Choice primarni ključ koji nije bio u formi. Zato što se radi o HTTP POST, ljudi mogu podesiti zlonamerne skripte koje mogu da šalju razne podatke. Obezbediti se da ovo ne prekine stvari je važno.

U ovom slučaju, greška je ista bez obzira kakve greške podataka postoje. Međutim, takvo ponašanje se može promeniti u budućnosti. (FORESHADOVING!)

Pokretanje testova sada bi trebalo da vam dam:


.....
----------------------------------------------------------------------
Ran 5 tests in 0.475s

OK

par drugih beležaka o testiranju Poruke:

● Imajte na umu da niste morali da brinete o CSRF. U ovom trenutku, Django onemogućava CSRF proveravajući kada se radi test paket po defaultu, iako je moguće da ih uključite.
● Testiranje HTTP zaglavlja je samo stvar dodavanja Django verziju zaglavlju (tj. HTTP_REFERER umesto referer) kao kwarg theclient.get ili client.post ispitivanje
● izvodljivo je I testiranje fajlova takođe. Trik je da se uključi fajl nalik objektu (preko open (), StringIO, itd) podataka rečniku.

Sada ćemo početi proširenje Django tutorial koda da koristite više napredne funkcije. Šta savremena Django aplikacija može da uradi


sporedno: Imenovana SEO


Primeticete da se Django uputstvo ne koristi imenom URL adrese, što može biti veoma koristan deo priključive aplikacije. Imenovani URL vam omogućava da zakačite neku aplikaciju na drugu URL bez promene koda / templatesPrvo

Integrisanje njima je lako, I u glavnom kodu i kod našeg testa

Tag:.. 06-imenu-URL
adrese, možemo promeniti URLconf u Ankete / urls.pi da ugradimo nove nazive:


urlpatterns = patterns('polls.views',
    url(r'^$', 'index', name='polls_index'),
    url(r'^(?P<poll_id>\d+)/$', 'detail', name='polls_detail'),
    url(r'^(?P<poll_id>\d+)/results/$', 'results', name='polls_results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'vote', name='polls_vote'),
)

Mi smo tada promenili pregled glasova tako da se promenilo preusmeravanje URL:


return HttpResponseRedirect(reverse('polls_results', kwargs={'poll_id': p.id}))

Konačno, možemo da menjamo naše šablone za korišćenje {% url = polls_vote poll_id poll.id%} za akciju i
{% url polls_vote poll_id=poll.id %} for the <form> action & {% url polls_detail poll_id=poll.id %} for the detail links.

Evo mesta gde naš novi test paket dolazi lako. Bez rada drugih stvari, treba da pokrene svoje testove. Trebalo bi da se vratimo:

.....
----------------------------------------------------------------------
Ran 5 tests in 0.253s

OK

Ovo super je vest! To znači da, uprkos nam idemo u izradu i promene, nismo prekinuli našu aplikaciju! Da smo napravili grešku (recimo neispravno unesena jedna od URL adresa), mi bi bismo već imali neku grešku kada pokrećemo paket,

Poslednji korak je da naše testove pokrenemo do ugovorenog URL i:


import datetime
from django.core.urlresolvers import reverse
from django.test import TestCase
from polls.models import Poll, Choice

class PollsViewsTestCase(TestCase):
    fixtures = ['polls_views_testdata.json']

    def test_index(self):
        resp = self.client.get(reverse('polls_index'))
        self.assertEqual(resp.status_code, 200)
        self.assertTrue('latest_poll_list' in resp.context)
        self.assertEqual([poll.pk for poll in resp.context['latest_poll_list']], [1])
        poll_1 = resp.context['latest_poll_list'][0]
        self.assertEqual(poll_1.question, 'Are you learning about testing in Django?')
        self.assertEqual(poll_1.choice_set.count(), 2)
        choices = poll_1.choice_set.all()
        self.assertEqual(choices[0].choice, 'Yes')
        self.assertEqual(choices[0].votes, 1)
        self.assertEqual(choices[1].choice, 'No')
        self.assertEqual(choices[1].votes, 0)

    def test_detail(self):
        resp = self.client.get(reverse('polls_detail', kwargs={'poll_id': 1}))
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.context['poll'].pk, 1)
        self.assertEqual(resp.context['poll'].question, 'Are you learning about testing in Django?')

        # Ensure that non-existent polls throw a 404.
        resp = self.client.get(reverse('polls_detail', kwargs={'poll_id': 2}))
        self.assertEqual(resp.status_code, 404)

    def test_results(self):
        resp = self.client.get(reverse('polls_results', kwargs={'poll_id': 1}))
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.context['poll'].pk, 1)
        self.assertEqual(resp.context['poll'].question, 'Are you learning about testing in Django?')

        # Ensure that non-existent polls throw a 404.
        resp = self.client.get(reverse('polls_results', kwargs={'poll_id': 2}))
        self.assertEqual(resp.status_code, 404)

    def test_good_vote(self):
        poll_1 = Poll.objects.get(pk=1)
        self.assertEqual(poll_1.choice_set.get(pk=1).votes, 1)

        resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}), {'choice': 1})
        self.assertEqual(resp.status_code, 302)
        self.assertEqual(resp['Location'], 'http://testserver/polls/1/results/')

        self.assertEqual(poll_1.choice_set.get(pk=1).votes, 2)

    def test_bad_votes(self):
        # Ensure a non-existant PK throws a Not Found.
        resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1000000}))
        self.assertEqual(resp.status_code, 404)

        # Sanity check.
        poll_1 = Poll.objects.get(pk=1)
        self.assertEqual(poll_1.choice_set.get(pk=1).votes, 1)

        # Send no POST data.
        resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}))
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.context['error_message'], "You didn't select a choice.")

        # Send junk POST data.
        resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}), {'foo': 'bar'})
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.context['error_message'], "You didn't select a choice.")

        # Send a non-existant Choice PK.
        resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}), {'choice': 300})
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.context['error_message'], "You didn't select a choice.")

Pokretanje našeg testa ponovo i dalje treba da daju prelazni rezultat, ali više nije važno gde se naš URLconf ukačio


testiranja. Obrasci


Jedna ekscentričnost od Django tutorijala jeste propust upotrebe ofForms. To je delimično zbog istorijskih razloga i delimično da bi se smanjila zabuna u ranoj fazi. Posebno imajući u vidu da se izbor stalno menja u zahtevima sa različitim objektima Ankete, morate da koristite malu količinu ne-tipičnog koda da bi to uradili.

Ali Obrasci pružaju neke prilično moćne validacije i mogućnosti ponovnog učitavanja pa ćemo ih integrisati

Tag:. 07-početna forma
Počinjemo tako što stvaramo novu datoteku (anketa / forms.pi) kao u sledećem kodu:


from django import forms
from polls.models import Choice

class PollForm(forms.Form):
    def __init__(self, *args, **kwargs):
        # We require an ``instance`` parameter.
        self.instance = kwargs.pop('instance')

        # We call ``super`` (without the ``instance`` param) to finish
        # off the setup.
        super(PollForm, self).__init__(*args, **kwargs)

        # We add on a ``choice`` field based on the instance we've got.
        # This has to be done here (instead of declaratively) because the
        # ``Poll`` instance will change from request to request.
        self.fields['choice'] = forms.ModelChoiceField(queryset=Choice.objects.filter(poll=self.instance.pk), empty_label=None, widget=forms.RadioSelect)

Da bismo tada uveli PollForm u naše stavove i izmenili detalje i voteviews, uradite sledeće:


def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    form = PollForm(instance=p)
    return render_to_response('polls/detail.html', {'poll': p, 'form': form},
                               context_instance=RequestContext(request))

# ...Then later...

def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)

    if request.method == 'POST':
        form = PollForm(request.POST, instance=p)

        if form.is_valid():
            choice = form.cleaned_data['choice']
            choice.votes += 1
            choice.save()

            return HttpResponseRedirect(reverse('polls_results', kwargs={'poll_id': p.id}))
    else:
        form = PollForm(instance=p)

    return render_to_response('polls/detail.html', {
        'poll': p,
        'form': form,
    }, context_instance=RequestContext(request))

Proveravamo da li je metod HTTP postovan. Ako nije (else slučaj), mi instanciramo osnovne obrazce (sa biračkog primera na telu van choicefield) i vraćamo redovnu stranicu.

Ako je postovan onda smo instancirali formu sa objavljenim podacima i naše Pollinstance. Pustili smo oblik regulatornog čišćenja podataka i proveravamo da li je izbor bio važeći. Ako je obrazac važeći, mi jednostavno ažuriramo thechoice.votes kao i pre i preusmeravanje. Inače, dozvoljavamo greške na obrascu.

Konačno smo ažurirali Ankete / detail.html šablon, uklonili uputstvo u korist:.


{{ form.as_p }}

Ako pokrenete testove, trebalo bi da:


E....
======================================================================
ERROR: test_bad_votes (polls.tests.PollsViewsTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/daniel/Desktop/guide_to_testing/polls/tests.py", line 66, in test_bad_votes
    self.assertEqual(resp.context['error_message'], "You didn't select a choice.")
  File "/usr/local/Cellar/python/2.7/lib/python2.7/site-packages/django/template/context.py", line 60, in __getitem__
    raise KeyError(key)
KeyError: 'error_message'

----------------------------------------------------------------------
Ran 5 tests in 0.256s

FAILED (errors=1)

Uh-oh. Raskinuli smo naše testove, jer se funkcionalnost promenila. Zanimljivo, možemo videti da test_good_vote metod još uvek prolazi, što znači da nisu raskinuli ispravan protok kroz aplikacije. Pošto oblik sada ima grešku, moramo da ažuriramo naše testove da


def test_bad_votes(self):
      # Ensure a non-existant PK throws a Not Found.
      resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1000000}))
      self.assertEqual(resp.status_code, 404)

      # Sanity check.
      poll_1 = Poll.objects.get(pk=1)
      self.assertEqual(poll_1.choice_set.get(pk=1).votes, 1)

      # Send no POST data.
      resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}))
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['form']['choice'].errors, [u'This field is required.'])

      # Send junk POST data.
      resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}), {'foo': 'bar'})
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['form']['choice'].errors, [u'This field is required.'])

      # Send a non-existant Choice PK.
      resp = self.client.post(reverse('polls_vote', kwargs={'poll_id': 1}), {'choice': 300})
      self.assertEqual(resp.status_code, 200)
      self.assertEqual(resp.context['form']['choice'].errors, [u'Select a valid choice. That choice is not one of the available choices.'])

odgovara: Sada pregledavamo greške na obrascu elemenata da bi se predstavile ispravne poruke o grešci. Takođe možemo da vidimo da se menja obrazac koji nam je dao bolje poruke o greškama za besplatne korisnike.

Running testovi sada daje prelaznu rezultat:
.....
----------------------------------------------------------------------
Ran 5 tests in 0.240s

OK

Svi testovi koji prolaze kroz tag:


Pravljenja


Tag:08-poruke-refactor

Imamo šansu da prepravimo kod malo. Primetićete da su protoci kroz detaljni prikaz i protoci glasanja, koji je poslao GET isti. Hajde da unapredite svoje stavove. Prvo, uklonite sledeću stavku URLconf:

url(r'^(?P<poll_id>\d+)/vote/$', 'vote', name='polls_vote'),

Dalje, mi ćemo uzeti šifru sa gledišta glasova i staviti ga u detaljni prikaz, prepisujući što je bilo i ukloniti ostatke voteview:

def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)

    if request.method == 'POST':
        form = PollForm(request.POST, instance=p)

        if form.is_valid():
            choice = form.cleaned_data['choice']
            choice.votes += 1
            choice.save()

            return HttpResponseRedirect(reverse('polls_results', kwargs={'poll_id': p.id}))
    else:
        form = PollForm(instance=p)

    return render_to_response('polls/detail.html', {
        'poll': p,
        'form': form,
    }, context_instance=RequestContext(request))

Na kraju, mi smo promenili kopiju Ankete / detail.html šablona koristeći:

<form action="." method="post">


Ako kliknete na detalje stranice, videćete da to radi dobro, ali bilo koji testovi koje su uperene na glasanje su sada zastareli. Mi ćemo ažurirati sve reference da se preokrenu ('polls_vote', ...) toreverse ('polls_detail', ...). Kada smo pokrenuli naše testove, trebalo bi da:

.....
----------------------------------------------------------------------
Ran 5 tests in 0.239s

OK

iai! Vratili smo se u zelenu (svi testovi su prošli) i imamo uspešno prepravljen naš kod. Ali, postoji još jedna stvar koju možemo napraviti. To povećanje glasanja bi stvarno trebalo da se pogura malo, jer to će biti zajednički rad i možda ne samo ograničeno na tom nivou.

Gde I kako ćemo ga pogurati, dodajući sledeće metode toChoice:.

class Choice(models.Model):
    # What's already there, then...

    def record_vote(self):
          self.votes += 1
          self.save()

Takođe ažuriramo našu PollForm da se iskoriste nove metode dodajući novu metodu (PollForm.save):

class PollForm(forms.Form):
    # What's already there, then...

    def save(self):
          choice = self.cleaned_data['choice']
          choice.record_vote()
          return choice

Konačno, mi ćemo ažurirati naše detalje koje vidite kroz pozivanje form.save () umesto stare logike:

def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)

    if request.method == 'POST':
        form = PollForm(request.POST, instance=p)

        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('polls_results', kwargs={'poll_id': p.id}))
    else:
        form = PollForm(instance=p)

    return render_to_response('polls/detail.html', {
        'poll': p,
        'form': form,
    }, context_instance=RequestContext(request))

Pokretanje testova pokazuje da još uvek prolazi protok!

.....
----------------------------------------------------------------------
Ran 5 tests in 0.243s

OK

To je dobro, jer se sada naš pregled čini kao nešto što je moguće I dalje od povezivanja potrebnih elemenata i kontrole. Naša forma predstavlja jednostavan način da sačuvate glasove. A logika koliko glasova se beleži se preselio u drugi izbor modela, gde drugi delovi našeg codebase sada mogu da iskoriste u totali.

U ovom slučaju, to je skoro pa trivijalno, te nije vredno toga. Da morate da promenite način kako se glasovi evidentiraju u budućnosti (recimo logovanju negde drugde ili proširenje modela podataka tako što ćemo vezati pojedinačne glasove sa feedback-om nazad korisniku), samo bi trebalo da ažurirate model kako bi se prilagodili tome.

Tag:09-odvojene-forme-testovi.
Potencijalni problem se ipak pojavio. Naša forma i naš model je pronašla nove metode koje nisu pokrivene testom Django. I dok naš pregled vrsta testova uverava da je radi pravilno, oni su više kao integracija testova (uverivši oblik / model / pogled / template svi igramo zajedno).

Ali prvo, možda ćemo želeti da reorganizujemo svoje testove. Mi smo samo mogli da ih dodamo na naš tests.pi datoteku, ali ja više volim da razdvojimo testove u strukturom fajlu koji oslikava podešavanje aplikacija.

Da biste to uradili stvorite nove testove kroz direktorijum, a onda pomerite ekistingtests.pi u testovima / vievs pi.. Na kraju, morate da odustanete u antests / __init__.pi datoteci (tako da se testovi / prepoznata kao Pithon modula). Unutar tog fajla, morate da stavite u sledeći kod:

from polls.tests.views import *


Ovaj uvoz je test kod koji smo prepisivali, tako da dobijate pokrenuti test kao deo paketa. Hajde da pokrenemo naše testove da proverite:

.....
----------------------------------------------------------------------
Ran 5 tests in 1.066s

OK

Savršeno. Sada ćemo kreirati nove testove / forms.pi File & start dodavanjem jedinice testa za našu forme. Mi ne treba da testiramo svaki aspekt forme (jer Django ima dobru pokrivenost test oblika). Ono što treba da testiramo jesu mesta odobrene forme, posebno __ init__ & savemethods. Naš kodeks treba da izgleda otprilike ovako:

from django.test import TestCase
from polls.forms import PollForm
from polls.models import Poll, Choice

class PollFormTestCase(TestCase):
    fixtures = ['polls_forms_testdata.json']

    def setUp(self):
        super(PollFormTestCase, self).setUp()
        self.poll_1 = Poll.objects.get(pk=1)
        self.poll_2 = Poll.objects.get(pk=2)

    def test_init(self):
        # Test successful init without data.
        form = PollForm(instance=self.poll_1)
        self.assertTrue(isinstance(form.instance, Poll))
        self.assertEqual(form.instance.pk, self.poll_1.pk)
        self.assertEqual([c for c in form.fields['choice'].choices], [(1, u'Yes'), (2, u'No')])

        # Test successful init with data.
        form = PollForm({'choice': 3}, instance=self.poll_2)
        self.assertTrue(isinstance(form.instance, Poll))
        self.assertEqual(form.instance.pk, self.poll_2.pk)
        self.assertEqual([c for c in form.fields['choice'].choices], [(3, u'Alright.'), (4, u'Meh.'), (5, u'Not so good.')])

        # Test a failed init without data.
        self.assertRaises(KeyError, PollForm)

        # Test a failed init with data.
        self.assertRaises(KeyError, PollForm, {})

Vrlo slično onome što smo videli, sa nekim novim elementima. Dodali smo podešavanja koji nam omogućava da radimo stvari koje će uticati na sve metode ispitivanja u okviru klase. Pošto ćemo imati ankete na nekoliko mesta, mi smo ih učitali tamoinit__..

Onda samo ručno testiramo metod __ Trudimo se i sa i bez podataka, kako bi se osiguralo da nema greške I da radi pravilno. Takođe smo dodali novu Anketu + Izbori našim podacima, tako da se mi zapravo možemo osigurati da nešto nije prekinuto I da vuče u pogrešnom smeru / svi podaci, što je važno da bi mogli da se afirmišu na terenu.

Takođe smo uveli novu metodu (assertRaises) jer prilikom testiranja nedostaje prvostepeni kwarg. Ovaj metod omogućava da se napravi određeni izuzetak u toku izvršenja. Koristi se čudno, jer ne koristite zagrade (PollForm ({})), umesto širenja argumenata u okviru assertRaises pozivapolls_forms_testdata.json)..

Imajte na umu da koristimo nova kola. Mi ovo radimo, jer ako smo promenili našu polls_vievs_testdata.json, mi bismo prekinuli naše postojeće preglede testova.

Naredno može biti malo krhko jer ako su svi vaši testovi dele jednu bazu podataka.To je lako da se napravi mnogo testova sa malim promenama. Međutim, dodajte da poklapanja mogu pomoći da pronađete greške na drugim mestima, ili otkriti krhke testove. Potrebno je da odmerite prednosti / troškove i odlučite šta treba da radite za vašu situaciju.

Naša nova utakmica treba da izgleda nešto ovako i obuhvata novu anketu sa više izbora opcija, tako da možete proveriti da se izbori menjaju kakve god da se instance koriste.

[
    {
        "pk": 1, 
        "model": "polls.poll", 
        "fields": {
            "pub_date": "2011-04-09 23:44:02", 
            "question": "Are you learning about testing in Django?"
        }
    }, 
    {
        "pk": 2, 
        "model": "polls.poll", 
        "fields": {
            "pub_date": "2011-04-16 14:42:52", 
            "question": "How do you feel today?"
        }
    },
    {
        "pk": 1, 
        "model": "polls.choice", 
        "fields": {
            "votes": 1, 
            "poll": 1, 
            "choice": "Yes"
        }
    }, 
    {
        "pk": 2, 
        "model": "polls.choice", 
        "fields": {
            "votes": 0, 
            "poll": 1, 
            "choice": "No"
        }
    },
    {
        "pk": 3, 
        "model": "polls.choice", 
        "fields": {
            "votes": 1, 
            "poll": 2, 
            "choice": "Alright."
        }
    }, 
    {
        "pk": 4, 
        "model": "polls.choice", 
        "fields": {
            "votes": 0, 
            "poll": 2, 
            "choice": "Meh."
        }
    }, 
    {
        "pk": 5, 
        "model": "polls.choice", 
        "fields": {
            "votes": 0, 
            "poll": 2, 
            "choice": "Not so good."
        }
    }
]

Jedan od poslednjih detalja. Moramo da dodamo testove / __init__.pi kako bi se datoteka mogla povući u novim oblicima testova. Sve što je potrebno je dodato:

from polls.tests.forms import *

Sada, pokretanje naših testove treba dati:

......
----------------------------------------------------------------------
Ran 6 tests in 0.926s

OK

Sada, da se testiraju - save metod. Mi ćemo početi novu metodu (test_save) u sada poznate kodove:

def test_save(self):
    self.assertEqual(self.poll_1.choice_set.get(pk=1).votes, 1)
    self.assertEqual(self.poll_1.choice_set.get(pk=2).votes, 0)

    # Test the first choice.
    form_1 = PollForm({'choice': 1}, instance=self.poll_1)
    form_1.save()
    self.assertEqual(self.poll_1.choice_set.get(pk=1).votes, 2)
    self.assertEqual(self.poll_1.choice_set.get(pk=2).votes, 0)

Neki se prikazivati naše testove i videti šta se dešava:

.E.....
======================================================================
ERROR: test_save (polls.tests.forms.PollFormTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/daniel/Desktop/guide_to_testing/polls/tests/forms.py", line 39, in test_save
    form_1.save()
  File "/Users/daniel/Desktop/guide_to_testing/polls/forms.py", line 20, in save
    choice = self.cleaned_data['choice']
AttributeError: 'PollForm' object has no attribute 'cleaned_data'

----------------------------------------------------------------------
Ran 7 tests in 0.328s

FAILED (errors=1)

Uh oh. Šta se desilo? The traceback ukazuje da cleaned_data nije prisutan na obrascu. Jedini način kako to može da se desi je kada je obrazac nepovezan (nema podataka uslovom), kada postoji provera greške, ili kada provera nije izvršena. Poslednji razlog je uzrok, jer ništa se ne zove form.full_clean ili form.is_valid. . Pošto je ovo moglo da se dogodi u stvarnom kodu, imamo problem u našim rukama

Da bi popravili ovo, mi ćemo dodati proveru metodai vidite da li je potvrđen:

class PollForm(forms.Form):
    # Existing code then...
    def save(self):
        if not self.is_valid():
            raise forms.ValidationError("PollForm was not validated first before trying to call 'save'.")

        choice = self.cleaned_data['choice']
        choice.record_vote()
        return choice

Najvažnije je da mi pokušavamo da sprečimo neispravan pristup podacima ili, još gore, pisanje loših podataka u bazu podataka. Sada pokrenite testove I treba da dobijete:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.323s

OK

Mnogo bolje. Hajde da završimo testiranje save metod. Revidirani kod izgleda:

def test_save(self):
    self.assertEqual(self.poll_1.choice_set.get(pk=1).votes, 1)
    self.assertEqual(self.poll_1.choice_set.get(pk=2).votes, 0)

    # Test the first choice.
    form_1 = PollForm({'choice': 1}, instance=self.poll_1)
    form_1.save()
    self.assertEqual(self.poll_1.choice_set.get(pk=1).votes, 2)
    self.assertEqual(self.poll_1.choice_set.get(pk=2).votes, 0)

    # Test the second choice.
    form_2 = PollForm({'choice': 2}, instance=self.poll_1)
    form_2.save()
    self.assertEqual(self.poll_1.choice_set.get(pk=1).votes, 2)
    self.assertEqual(self.poll_1.choice_set.get(pk=2).votes, 1)

    # Test the other poll.
    self.assertEqual(self.poll_2.choice_set.get(pk=3).votes, 1)
    self.assertEqual(self.poll_2.choice_set.get(pk=4).votes, 0)
    self.assertEqual(self.poll_2.choice_set.get(pk=5).votes, 0)

    form_3 = PollForm({'choice': 5}, instance=self.poll_2)
    form_3.save()
    self.assertEqual(self.poll_2.choice_set.get(pk=3).votes, 1)
    self.assertEqual(self.poll_2.choice_set.get(pk=4).votes, 0)
    self.assertEqual(self.poll_2.choice_set.get(pk=5).votes, 1)

Mi gomilu različitih tvrdnji ovde imamo na različitim podacima, samo treba da se uverite da se odgovarajuće tačke čuvaju na pravim mestima. Još jednom pokrenuti testove:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.323s

OK

I naša forma je testirana! Ako su vaši obrasci uključili i druge stvari, kao što su customclean_FOO metode, to su takođe dobre stvari za testiranje, u istom stilu kao i za save metodu, uveravajući vas da su podaci očišćeni.



Testiranje modela


Tag:10model-testovi

Na kraju, testiranje modela. Dobra vest je da će naši testovi izgledati veoma poznato, snažno podsećajući na tok i sadržaj obrazaca testova

Opet, nećemo brinuti o testiranju stvari koje bi trebalo da imaju Django testove. Mi ćemo testirati samo naše ekstenzije na modelima kao i testiranje našeg korišćenja tih testova:..

Prvo stavite sledeće u testove / __init__.pi datoteka, tako da ne zaboravite da kasnije uvedete naše ankete testova

from polls.tests.models import *

Zatim ćemo kreirati novi fajl pod nazivom testa / models.pi. Nakon razmatranja našeg modela, postoji nekoliko neposrednih stvari koji treba da se ispituju. Oni

● su: vas_published_to da i metod učini:..
● Anketarecord_vote metod izbora uobičajeno

Pored toga, s obzirom na skeletnu prirodu modela, postoje izvesna poboljšanja koja možemo uraditi
● datetime u Poll.pub_date oblasti`.
● podrazumevani broj glasova na `Choice.
● Anketa objekata sa budućim pub_date se možda slučajno objavljuju u našem prikazu liste. Imati spisak objavljenih glasova je korisna metoda..

Počnimo od početnih testova koje pokrivamo. Mi radimo isti test kao što smo uradili u PollFormTestCase, a onda uradite neka osnovna testiranja na vas_published_toda metodom.

import datetime
from django.test import TestCase
from polls.models import Poll, Choice

class PollTestCase(TestCase):
    fixtures = ['polls_forms_testdata.json']

    def setUp(self):
        super(PollTestCase, self).setUp()
        self.poll_1 = Poll.objects.get(pk=1)
        self.poll_2 = Poll.objects.get(pk=2)

    def test_was_published_today(self):
        # Because unless you're timetraveling, they weren't.
        self.assertFalse(self.poll_1.was_published_today())
        self.assertFalse(self.poll_2.was_published_today())

        # Modify & check again.
        now = datetime.datetime.now()
        self.poll_1.pub_date = now
        self.poll_1.save()
        self.assertTrue(self.poll_1.was_published_today())

Ukoliko rezultati testiranja nisu stvoreni danas, onda modifikujte tekući datum / vreme. Ponovnom tom metodom sada treba da nam daju Istinausedatetime.datetime.nov..

Budite pažljivi sa datumom na bazi testova, posebno u stvarima kada prolazite kroz testove, jednog dana možda uspeti. Uobičajeni način rukovanja je korišćenje ruga jedne ili druge vrste, koje ćemo pokriti u kasnijem toku teksta.

Testove sada treba vratiti:

........
----------------------------------------------------------------------
Ran 8 tests in 0.353s

OK

Sada ćemo testirati Choice.record_vote metod. Mi ćemo dodati novu klasu našim testovima / models.pi datoteka na ovaj način:

class ChoiceTestCase(TestCase):
    fixtures = ['polls_forms_testdata.json']

    def test_record_vote(self):
        choice_1 = Choice.objects.get(pk=1)
        choice_2 = Choice.objects.get(pk=2)

        self.assertEqual(Choice.objects.get(pk=1).votes, 1)
        self.assertEqual(Choice.objects.get(pk=2).votes, 0)

        choice_1.record_vote()
        self.assertEqual(Choice.objects.get(pk=1).votes, 2)
        self.assertEqual(Choice.objects.get(pk=2).votes, 0)

        choice_2.record_vote()
        self.assertEqual(Choice.objects.get(pk=1).votes, 2)
        self.assertEqual(Choice.objects.get(pk=2).votes, 1)

        choice_1.record_vote()
        self.assertEqual(Choice.objects.get(pk=1).votes, 3)
        self.assertEqual(Choice.objects.get(pk=2).votes, 1)

Ništa nije bezveze ovde, samo proveravam nekoliko poziva da theChoice.record_vote metod da bih se se uverio da su ispravni koraci. Primetićete da smo stalno na usingChoice.objects.get (pk = 1) gde smo umesto choice_1 objekta kreirali prvo. To je zato što želimo da budemo sigurni da je baza podataka koju uzimamo zaista ažurirana (ne neuspešna i samo ažurirana - računati na model stepena u memoriji).

Pokrenite testove, dobija se srećni rezultat:

----------------------------------------------------------------------
Ran 9 tests in 0.373s

OK

Tag: 11-model-poboljšanja, na već pokrenuta poboljšanja. Za promenu, mi ćemo koristiti test-driven stil, I pokrenuti testove, a zatim raditi na izgradnji iz funkcionalnosti u modele neuspeha.

Počinjemo dodavanjem sledeće metode na PollTestCase:

def test_better_defaults(self):
    now = datetime.datetime.now()
    poll = Poll.objects.create(
        question="A test question."
    )
    self.assertEqual(poll.pub_date.date(), now.date())

Pokretanje testova daje nam "crveni" (Test ):

...E......
======================================================================
ERROR: test_better_defaults (polls.tests.models.PollTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  # Snipped for brevity...
IntegrityError: polls_poll.pub_date may not be NULL

----------------------------------------------------------------------
Ran 10 tests in 0.661s

FAILED (errors=1)

Uh, Integrity Error. Hajde da popravimo promenu definisanjem modela (`` anketa):

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published', default=datetime.datetime.now)

Re-running the tests turns them "green" (test success):

..........
----------------------------------------------------------------------
Ran 10 tests in 0.378s

OK

Pokretanjem, testove pretvara u "zelenu" (test uspeh):

..........
----------------------------------------------------------------------
Ran 10 tests in 0.378s

OK

UPOZORENjE - Kada smo izradu modela promenili, testovi će pokupiti ovo (zbog sveže obnovljene DB strukture na svakom pokretanju), ALI vaše podešavanje će i dalje koristiti staru šemu. Molimo Vas da koristite alatku kao Jug ili nashvegas da rukujete uživo migracijama glasova.

Poboljšavanjem izbora sledećeg testa dajemo normalan podrazumevani broj glasova. Dodajte sledeći metod za pokretanje I ispitivanja da li je crveno:

def test_better_defaults(self):
    poll = Poll.objects.create(
        question="Are you still there?"
    )
    choice = Choice.objects.create(
        poll=poll,
        choice="I don't blame you."
    )

    self.assertEqual(poll.choice_set.all()[0].choice, "I don't blame you.")
    self.assertEqual(poll.choice_set.all()[0].votes, 0)

testovi ostaju crveni ali su drugačijiu

E..........
======================================================================
ERROR: test_better_defaults (polls.tests.models.ChoiceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  # Snipped for brevity...
IntegrityError: polls_choice.votes may not be NULL

----------------------------------------------------------------------
Ran 11 tests in 0.563s

FAILED (errors=1)

Ponovo izaberite:

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Ponovo testirajte grešku:

E..........
======================================================================
ERROR: test_better_defaults (polls.tests.models.ChoiceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/daniel/Desktop/guide_to_testing/polls/tests/models.py", line 64, in test_better_defaults
    self.assertEqual(poll.choices.all()[0].choice, "I don't blame you.")
AttributeError: 'Poll' object has no attribute 'choices'

---------------------------------------------------------------------
Ran 11 tests in 0.500s

FAILED (errors=1)

testovi nam daju:

...........
----------------------------------------------------------------------
Ran 11 tests in 0.435s

OK

I vratili smo se u zelenu. Hajde da se pozabavimo našim konačnim poboljšanjem, grantingPoll je bolji način da objedinite objavljene ankete. Uzećemo pristup dodavanja novog menadžera u Anketa klasi. Testovi prvo (...).

def test_no_future_dated_polls(self):
    # Create the future-dated ``Poll``.
    poll = Poll.objects.create(
        question="Do we have flying cars yet?",
        pub_date=datetime.datetime.now() + datetime.timedelta(days=1)
    )
    self.assertEqual(list(Poll.objects.all().values_list('id', flat=True)), [1, 2, 3])
    self.assertEqual(list(Poll.published.all().values_list('id', flat=True)), [1, 2])

Imajte na umu da smo pomoću values_list. izaberali listu primarnih ključeva, ALI smo objedinili te pozive na listi .. Ovo je zbog toga što, uprkos the__repr__ izgleda kao da je lista, values_list zapravo vraća aValuesListKueriSet, koji neće da testira listu kao "ravnopravnu" u integrisanoj listi.

Radni testovi daju neuspeh kao što se očekivalo:

.....E......
======================================================================
ERROR: test_no_future_dated_polls (polls.tests.models.PollTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/daniel/Desktop/guide_to_testing/polls/tests/models.py", line 39, in test_no_future_dated_polls
    self.assertEqual(list(Poll.published.all().values_list('id', flat=True)), [1, 2])
AttributeError: type object 'Poll' has no attribute 'published'

----------------------------------------------------------------------
Ran 12 tests in 0.418s

FAILED (errors=1)

Dakle, mi smo izmenili model Glasanja podešavanja ovako, dodajući novi, nepodrazumevani model menadžeru:

class PollManager(models.Manager):
    def get_query_set(self):
        now = datetime.datetime.now()
        return super(PollManager, self).get_query_set().filter(pub_date__lte=now)

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published', default=datetime.datetime.now)

    objects = models.Manager()
    published = PollManager()

And we run our tests again, getting green once again:

............
----------------------------------------------------------------------
Ran 12 tests in 0.574s

OK

I mi smo ponovo pokrenuli naše testove, sve je zeleno opet:

............
----------------------------------------------------------------------
Ran 12 tests in 0.574s

OK

Nažalost, ovi testovi koji prolaze zapravo maskiraju problem. Dodali smo taj novi Poll.published menadžera, ali ga ne koristite nigde van testa, što znači da će se pregled i dalje služiti budućim datiranim stavkama. Ako se promeni raspored / polls_vievs_testdata.json gde se uključuje dodatni datum iz budućnosti, zastarelu Anketu ćemo izložiti greški:

# ...Somewhere within the ``[]``s...
{
    "pk": 2, 
    "model": "polls.poll", 
    "fields": {
        "pub_date": "2031-04-09 23:44:02", 
        "question": "IT'S THE FUTURE..."
    }
},

Sada radni testove donose nekoliko propusta

........F.FF
======================================================================
FAIL: test_detail (polls.tests.views.PollsViewsTestCase)
----------------------------------------------------------------------
# Snipped.

======================================================================
FAIL: test_index (polls.tests.views.PollsViewsTestCase)
----------------------------------------------------------------------
# Snipped.

======================================================================
FAIL: test_results (polls.tests.views.PollsViewsTestCase)
----------------------------------------------------------------------
# Snipped.

----------------------------------------------------------------------
Ran 12 tests in 0.484s

FAILED (failures=3)

Popraviti ove ažuriranjem naših pregleda sledećim kodom:

def index(request):
    latest_poll_list = Poll.published.all().order_by('-pub_date')[:5]
    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

def detail(request, poll_id):
    p = get_object_or_404(Poll.published.all(), pk=poll_id)

    if request.method == 'POST':
        form = PollForm(request.POST, instance=p)

        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('polls_results', kwargs={'poll_id': p.id}))
    else:
        form = PollForm(instance=p)

    return render_to_response('polls/detail.html', {
        'poll': p,
        'form': form,
    }, context_instance=RequestContext(request))

def results(request, poll_id):
    p = get_object_or_404(Poll.published.all(), pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

Imajte na umu da može get_object preuzeti bilo koji modela (koristeći podrazumevani menadžer) ili KueriSet, u ovom slučaju Poll.published.all ().

Pokretanje testova nas vraća na zeleno:

............
----------------------------------------------------------------------
Ran 12 tests in 0.436s

OK




Published (Last edited): 18-02-2013 , source: http://toastdriven.com/blog/2011/apr/17/guide-to-testing-in-django-2/