Pisanie pierwszej aplikacji Django, część 5.

Ten tutorial zaczyna się, gdzie skończył się Tutorial 4. Zbudowaliśmy aplikację web-ankietową, teraz stworzymy dla niej kilka zautomatyzowanych testów.

Gdzie szukać pomocy:

Jeśli masz trudności w przejściu tego tutorialu, przejdź do sekcji Uzyskiwanie pomocy często zadawanych pytań.

Wprowadzenie do zautomatyzowanych testów

Czym są zautomatyzowane testy?

Testy są rutynami, które sprawdzają działanie twojego kodu.

Testowanie działa na różnych poziomach. Niektóre testy mogą mieć zastosowanie do małych detali (czy szczególna metoda modelu zwraca spodziewane wartości?), podczas gdy inne sprawdzają ogólne działanie software’u (czy sekwecja wejścia od użytkownika na stronie daje pożądany rezultat?). Nie różni się to niczym od rodzaju testów, które robiłeś wcześniej w Tutorialu 2, używając shell, by sprawdzić zachowanie metody, lub uruchamiając aplikację i wprowadzające dane, by sprawdzić, jak się zachowuje.

To, co jest innego w testach zautomatyzowanych, to to, że testowanie wykonywanie jest dla ciebie przez system. Raz tworzysz zestaw testów i później, kiedy robisz zmiany w swojej aplikacji, możesz sprawdzić, czy twój kod nadal działa jak to pierwotnie zamierzałeś, bez konieczności wykonywania pochłaniających czas testów ręcznych.

Dlaczego potrzebujesz tworzyć testy

Więc dlatego tworzyć testy i dlaczego teraz?

Możesz mieć wrażenie, że masz wystarczająco dużo na talerzu ucząc się tylko Pythona/Django i mając znów kolejną rzecz do nauki może wydawać się przytłaczające i może niepotrzebne. Ostatecznie nasza aplikacja ankietowa pracuje teraz całkiem szczęśliwie; przechodzenie przez problem tworzenia zautomatyzowanych testów nie spowoduje, że będzie działać choć trochę lepiej. Jeśli tworzenie aplikacji ankietowej jest ostatnim programowaniem w Django, jaki kiedykolwiek zrobisz, wtedy prawda, nie potrzebujesz wiedzieć, jak robić zautomatyzowane testy. Ale jeśli to nie jest twój przypadek, teraz jest wspaniały czas do nauki.

Testy oszczędzą ci czas

Do pewnego momentu, „sprawdzenie czy zdaje się działać” będzie satysfakcjonującym testem. W bardziej złożonej aplikacji możesz mieć tuziny złożonych interakcji pomiędzy komponentami.

Zmiana w jakimkolwiek z tych komponentów może mieć nieprzewidziane konsekwencje w zachowaniu aplikacji. Sprawdzenie, czy nadal „zdaje się działać” może znaczyć przechodzenie przez całą funkcjonalność twojego kodu z dwudziestoma różnymi wariacjami danych testowych, by upewnić się, czy czegoś nie zepsułeś – nie jest to dobrze wykorzystany czas.

Jest to szczególnie prawdą kiedy zautomatyzowane testy mogą zrobić to za ciebie w sekundy. Jeśli coś poszło źle, testy pomogą też w zidentyfikowaniu kodu, który powoduje nieprzewidziane zachowanie.

Czasem może wydawać się żmudne odrywać się od produktywnej, kreatywnej pracy programistycznej, aby zmierzyć się z szarym i nie ekscytującym biznesem pisania testów, szczególnie kiedy wiesz, że twój kod działa dobrze.

Jednakże zadanie pisania testów jest o wiele bardziej satysfakcjonujące niż spędzanie godzin na testowaniu ręcznie swojej aplikacji lub na próbach identyfikacji przyczyny nowo-wprowadzonego problemu.

Testy nie tylko identyfikują problemy, one pomagają ich unikać

Błędem jest myśleć o testach głównie jako negatywnym aspekcie dewelopmentu.

Bez testów cel lub zamierzone działanie aplikacji może być raczej nieprzejrzyste. Nawet jeśli to twój własny kod, czasem okaże się, że czasem w nim węszysz próbując odkryć co dokładnie robi.

Testy to zmieniają; oświetlają twój kod z wnętrza i kiedy coś pójdzie nie tak, skupiają światłona części, która poszła źle – nawet kiedy się nie zorientowałeś, że poszło źle.

Testy powodują, że twój kod jest bardziej atrakcyjny

Mógłbyś stworzyć wspaniały kawałek kodu, ale okaże się, że wielu deweloperów odmówi spojrzenia na niego, bo brakuje mu testów; bez testów, nie będą mu ufać. Jacob Kaplan-Moss, jeden z pierwotnych twórców Django mówi: „Kod bez testów jest popsuty z założenia”.

To, że inni deweloperzy chcą mieć testy w twoim oprogramowaniu zanim potraktują go na poważnie jest kolejnym powodem, byś zaczął pisać testy.

Testy pomagają zespołom pracować razem

Poprzednie punkty są napisane z perspektywy pojedynczego dewelopera opiekującego się aplikacją. Złożone aplikacje będą utrzymywane przez zespoły. Testy gwarantują, że koledzy nie zepsują przypadkowo twojego kodu (i że ty nie zepsujesz ich bez wiedzy o tym). Jeśli chcesz utrzymywać się jako programista Django, musisz być dobry w pisaniu testów!

Podstawowe strategie testowania

Jest wiele sposobów, w jaki można podejść do pisania testów.

Niektórzy programiści stosują się do zasady nazwanej „test-driven development”; piszą oni testy zanim jeszcze napiszą swój kod. To może wydawać się nieintuicyjne, ale w praktyce jest podobne do tego, co większość ludzi często i tak robi: opisują problem, następnie tworzą pewien kod, aby go rozwiązać. Test-driven development formalizuje problem w pythonowym test-case’ie.

Częściej ktoś nowy w testowanie stworzy pewien kod i później zdecyduje, że powinien on mieć jakieś testy. Prawdopodobnie lepiej byłoby napisać część testów wcześniej, ale nigdy nie jest za późno, żeby zacząć to robić.

Czasem trudno jest wymyślić, od czego zacząć w pisaniu testów. Jeśli napisałeś kilka tysięcy linii Pythona, wybranie czegoś do przetestowania może nie być łatwe. W takim przypadku owocnym jest napisać pierwszy test następnym razem, gdy wprowadzisz zmianę, czy to dodajesz nową funkcjonalność, czy naprawiasz błąd.

A więc zróbmy to teraz.

Pisanie naszego pierwszego testu

Odnajdujemy błąd

Na szczęście w aplikacji polls jest mały błąd do naprawienia przez nas od razu: metoda Question.was_published_recently() zwraca True jeśli Question zostało opublikowane w ostatnim dniu (co jest poprawne) ale również jeśli pole pub_date instancji Question jest w przyszłości (co poprawne z pewnością nie jest).

Potwierdź buga używając shella i sprawdź metodę na pytaniu, którego data jest w przyszłości:

$ python manage.py shell
...\> py manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # 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

Ponieważ rzeczy w przyszłości nie są „recent”, to jest oczywiście źle.

Stwórz test, aby wskazać błąd

To, co zrobiliśmy w :djadmin:`shell`u, aby sprawdzić obecność problemu jest dokładnie tym, co chcemy zrobić w zautomatyzowanym teście, więc obróćmy to w zautomatyzowany test.

Miejscem według konwencji na testy aplikacji jest plik tests.py aplikacji; system testowania automatycznie znajdzie testy w każdym pliku, którego nazwa zaczyna się od test.

Wprowadź poniższe w plik tests.py aplikacji 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)

Stworzyliśmy tutaj podklasę django.test.TestCase z metodą, która tworzy instancję Question z pub_date w przyszłości. Następnie sprawdzamy wyjście was_published_recently() – które powinno być False.

Uruchomienie testów

W terminalu możemy uruchomić nasz test:

$ python manage.py test polls
...\> py manage.py test polls

i zobaczysz coś takiego:

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/mysite/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'...

Inny błąd?

Jeśli dostajesz tutaj NameError, być może ominąłeś krok w części 2, gdzie dodaliśmy importy datetime i timezone do polls/models.py. Skopiuj importy z tamtej sekcji i spróbuj uruchomić testy ponownie.

To, co się stało, to to:

  • manage.py test polls wyszukał testy w aplikacji polls
  • znalazł podklasę klasy django.test.TestCase
  • stworzył specjalną bazę danych na potrzebę testowania
  • wyszukał metody testowe – te, których nazwy zaczynają się od test
  • w test_was_published_recently_with_future_question stworzył instancję Question, której pole pub_date jest 30 dni w przyszłości
  • …i używając metody assertls() odkrył, że jej was_published_recently() zwraca True, chociaż chcieliśmy, aby zwracała False

Test informuje nas który test się nie powiódł i nawet w której linii ukazał się błąd.

Naprawianie błędu

Wiemy już, co jest problemem: Question.was_published_recently() powinna zwracać False jeśli jej pub_date jest w przyszłości. Zmień metodę w models.py, aby zwracała True tylko jeśli data jest też w przeszłości:

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

i uruchom test ponownie:

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'...

Po zidentyfikowaniu buga, napisaliśmy test, który go pokazuje i poprawiliśmy błąd w kodzie tak, aby test przechodził.

Wiele innych rzeczy może pójść źle z naszą aplikacją w przyszłości, ale możemy być pewni, że nie zreintrodukujemy przypadkowo tego błędu, ponieważ uruchomienie testu błyskawicznie nas ostrzeże. Możemy traktować tę małą porcję aplikacji na zawsze jako bezpieczną.

Obszerniejsze testy

Kiedy tu jesteśmy, możemy dalej przyprzeć do muru metodę was_published_recently(); w zasadzie byłoby pozytywnie zawstydzające jeśli naprawiając jeden błąd wprowadziliśmy inny.

Dodaj dwie metody testowe więcej do tej samej klasy, aby przetestować zachowanie metody bardziej wszechstronnie:

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)

I mamy teraz trzy testy, które potwierdzają, że Question.was_published_recently() zwraca sensowne wartości dla przeszłych, ostatnich i przyszłych pytań.

Znowu, polls jest małą aplikacją, ale jakkolwiek złożona będzie w przyszłości i z jakimkolwiek kodem będzie w interakcji, mamy teraz pewną gwarancję, że metoda, dla której napisaliśmy testy będzie zachowywała się w przewidywalne sposoby.

Przetestuj widok

Aplikacja ankietowa jest bardzo niedyskryminująca: opublikuje każde pytanie, włącznie z takimi, których pole pub_date wskazuje na datę w przyszłości. Powinniśmy to poprawić. Ustawienie pub_date w przyszłości powinno oznaczać, że Question jest publikowane dopiero w tym przyszłym momencie i niewidoczne do tego czasu.

Test dla widoku

Kiedy naprawiliśmy powyżej błąd, napisaliśmy najpierw test i później kod, który naprawił błąd. Faktycznie to był przykład test-driven dewelopmentu, ale to nie ma wielkiego znaczenia, w jakiej kolejności wykonujemy tę pracę.

W naszym pierwszym teście skupiliśmy się bardzo na wewnętrznym zachowaniu kodu. W tym teście chcemy sprawdzić jego działanie tak, jak byłoby obserwowane przez użytkownika przez przeglądarkę internetową.

Zanim spróbujemy cokolwiek naprawić, spójrzmy na narzędzia, którymi dysponujemy.

Klient testowy Django

Django dostarcza testowe Client do symulacji interakcji użytkownika z kodem na poziomie widoku. Możemy go użyć w tests.py lub nawet w shellu.

Zaczniemy znowu w shellu, gdzie musimy zrobić parę rzeczy, które nie będą koniczne w tests.py. Pierwsze, to uruchomić środowisko testowe w shellu:

$ python manage.py shell
...\> py manage.py shell
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

setup_test_environment() instaluje renderer szablonów, który pozwala nam sprawdzać niektóre dodatkowe atrybuty odpowiedzi, takie jak response.context, które inaczej nie byłyby dostępne. Miej na uwadze, że ta metoda nie uruchamia testowej bazy danych, więc poniższe będzie uruchamiane na istniejącej bazie danych i wynik może nieco się różnić w zależności od pytań, które dotychczas stworzyłeś. Możesz otrzymać nieprzewidywane wyniki, jeśli twoje TIME_ZONE w settings.py nie jest poprawne. Jeśli nie pamiętasz, abyś je wcześniej ustawiał, sprawdź zanim kontynuujesz.

Następnie musimy zaimportować klasę klienta testowego (poźniej w tests.py będziemy używać klasy django.test.TestCase, która ma swojego własnego klienta, więc ten nie będzie wymagany):

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

Z zaimportowaną klasą, możemy zlecić klientowi wykonanie dla nas pracy:

>>> # 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?>]>

Udoskonalanie naszego widoku

Lista ankiet pokazuje ankiety, które nie są jeszcze opublikowane (te, które mają pub_date w przyszłości). Naprawmy to.

w Tutorialu 4 wprowadziliśmy class-based view, oparty na 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]

Musimy zmienić metodę get_queryset() tak, aby sprawdzała też datę porównując ją z timezone.now(). Najpierw musimy dodać import:

polls/views.py
from django.utils import timezone

i później zmienić metodę get_queryset w ten sposób:

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()) zwraca queryset zawierający Questions. których pub_date jest mniejsze lub równe – to znaczy wcześniejsze lub równoczesne – względem timezone.now.

Testowanie naszego nowego widoku

Teraz możesz być się usatysfakcjonować, że działa jak przewidziałeś, odpalając runserver, ładując stronę w swojej przeglądarce, tworząc Questions z datami w przeszłości i przyszłości i sprawdzając, że tylko te, które zostały opublikowane, są na liście. Nie będziesz chciał robić tego za każdym razem, kiedy wprowadzisz jakąkolwiek zmianę, która może mieć na to wpływ – więc stwórzmy też test, oparty na naszej sesji shell powyżej.

Dodaj poniższe do polls/tests.py:

polls/tests.py
from django.urls import reverse

i stworzymy funkcję skrótową do tworzenia pytań oraz nową klasę testową:

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.
        """
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past 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.
        """
        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: Past question.>']
        )

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

Przyjmy się dokładniej niektórym z nich.

PIerwsze to funkcja skrótowa pytań, create_question, wyciąga powtórzenia z procesu tworzenia pytań.

test_no_questions nie tworzy żadnych pytań, ale sprawdza komunikat „No polls are available.” i weryfikuje, czy latest_question_list jest puste. Zwróć uwagę, że django.test.TestCase dostarcza kilka dodatkowych metod asercji. W tych przypadkach używamy assertContains() i assertQuerysetEqual().

W test_past_question tworzymy pytanie i weryfikujemy, że pojawia się na liście.

W test_future_question tworzymy pytanie z pub_date w przyszłości. Baza danych jest resetowana dla każdej testowej metody, więc pierwszego pytania już tutaj nie ma i znów indeks nie powinien zawierać żadnego pytania.

I tak dalej. W efekcie używamy testów, aby opowiedzieć historię o danych wprowadzonych przez administratora i doświadczeniu użytkownika na stronie i sprawdzamy na każdym etapie i dla każdej nowej zmiany w stanie systemu, czy publikowane są oczekiwane rezultaty.

Testowanie DetailView

To co mamy działa dobrze, jednakże mimo to, że przyszłe pytania nie pojawiają się w indeksie, użytkownicy wciąż mogą uzyskać do nich dostęp jeśli znają lub zgadną odpowiedni URL. Musimy więc dodać podobny warunek do 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())

Następnie powinniśmy dodać kilka testów, aby sprawdzić że Question, którego pub_date jest w przeszłości może zostać wyświetlone i takie z pub_date w przyszłości nie może:

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)

Pomysły na więcej testów

Powinniśmy dodać podobną metodę get_queryset do ResultsView i stworzyć nową klasę testową dla tego widoku. Będzie bardzo podobna do tego, co przed chwilą stworzyliśmy; tak naprawdę będzie bardzo dużo powtórzone.

Moglibyśmy ulepszyć naszą aplikację na inne sposoby, dodając po drodze testy. Na przykład głupie jest, że Questions, które nie mają Choices mogą być publikowane na stronie. Więc nasze widoki mogłyby to sprawdzać i wykluczać takie Questions. Nasze testy tworzyłyby Question bez Choices i następnie sprawdzały, czy nie jest opublikowane, jak również tworzyłyby podobne Question z Choices i sprawdzały, czy jest opublikowane.

Prawdopodobnie zalogowani administratorzy powinni móc widzieć nieopublikowane Questions, lecz nie zwykli użytkownicy. Znów: cokolwiek powinno być dodane do oprogramowania, by to osiągnąć, powinnien mu towarzyszyć test. Niezależnie, czy najpierw napiszesz test a później stworzysz kod, który go przechodzi, czy najpierw wykujesz logikę w swoim kodzie a następnie napiszesz test, żeby ją potwierdzić.

W pewnym momencie na pewno spojrzysz na swoje testy i zaczniesz zastanawiać się, czy Twój kod nie cierpi na testowe wzdęcia, co prowadzi nas do:

Testując, im więcej tym lepiej

Może się wydawać, że nasze testy zaczynają wymykać się spod kontroli. W takim tempie wkrótce będzie więcej kodu w naszych testach niż w naszej aplikacji, a powtórzenia są nieestetyczne, w porównaniu do eleganckiej zwięzłości reszty naszego kodu.

To nie ma znaczenia. Pozwól im rosnąć. W większości przypadków możesz napisać test raz i później zapomnieć o nim. Będzie dalej wykazywał przydatność w trakcie twojej pracy nad programem.

Czasem trzeba zaktualizować testy. Załóżmy, że modyfikujemy nasze widoki tak,aby tylko Questions z Choices były publikowane. W tym przypadku wiele naszych istniejących testów nie powiedzie się – mówiąc nam dokładnie które testy powinny zostać zmodyfikowane, aby były na bieżąco, więc do takiego stopnia testy pomagają o siebie zadbać.

W najgorszym przypadku w trakcie pracy możesz zorientować się, że masz jakieś testy, które są teraz redundantne. Nawet to nie jest problemem; w testowaniu redundancja jest rzeczą dobrą.

Tak długo jak twoje testy są sensownie ułożone, nie zaczną być nie do zarządzania. Na praktyczne zasady składa się posiadanie:

  • oddzielnego TestClass dla każdego modelu lub widoku
  • oddzielnej metody testowej dla każdego zbioru warunków, które chcesz sprawdzać
  • nazw metod testowych, które opisują swoje funkcje

Dalsze testowanie

Ten tutorial przedstawia tylko podstawy testowania. Jest bardzo wiele rzeczy, które moesz robić i parę bardzo przydatnych narzędzi do twojej dyspozycji, by osiągać sprytne rzeczy.

Na przykład, podczas gdy nasze testy tutaj pokryły część wewnętrznej logiki modelu i sposobu, w jaki nasze widoki publikują informacje, możesz użyć „przeglądarkowego” frameworku takiego jak Selenium, by testować, w jaki sposób twój HTML tak naprawdę renderuje się w przeglądarce. Takie narzędzia pozwalają na sprawdzenie nie tylko zachowania twojego kodu Django, ale też na przykład twojego JavaScriptu. Jest coś w tym, widzieć testy odpalane w przeglądarce i jak wchodzą w interakcje z twoją stroną, tak jakby robił to człowiek! Django zawiera LiveServerTestCase, aby ułatwić integrację z narzędziami jak Selenium.

Jeśli masz złożoną aplikację, możesz chcieć uruchamiać testy automatycznie z każdym commitem w celu utrzymania ciągłej integracji, aby kontrola jakości była – przynajmniej częściowo – zautomatyzowana.

Dobrym sposobem, aby dostrzec nietestowane części twojej aplikacji, jest sprawdzić pokrycie testami. To pomaga też znaleźć delikatny lub nawet martwy kod. Jeśli nie możesz przetestować kawałka kodu, zazwyczaj znaczy to, że powinien on zostać zrefaktorowany lub usunięty. Pokrycie pomoże ci znaleźć martwy kod. Zobacz Integration with coverage.py po szczegóły.

Testowanie w Django zawiera obszerną informację na temat testowania.

Co dalej?

Po pełną informację na temat testowania, zobacz Testowanie w Django.

Kiedy już czujesz się dobrze z testowaniem widoków Django, przeczytaj część 6 tego tutoriala, aby nauczyć się zarządzania plikami statycznymi.

Back to Top