Narzędzia użytkownika

Narzędzia witryny


wiki:django_tests

Testy w Django

Poniżej plik tests.py przygotowany dla aplikacji polls z tutoriala do Django. Działa poprawnie w Django 3.1 oraz 4.0. Jedyna różnica w porównaniu z oryginałem to dodana funkcja fix_votes w modelu Choice:

def fix_votes(self):
    elif self.votes == 0:
      self.votes = 0
      return True
    if self.votes < 0:
      self.votes = 0
      return True
    elif self.votes % (ceil(self.votes)-1) != 1:
      self.votes = ceil(self.votes)-1
      return True

https://docs.djangoproject.com/en/4.0/intro/tutorial01/

import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse

# Create your tests here.

from .models import Question, Choice

# ---------------------------------------------
# --------- HELPERS ---------------------------
# ---------------------------------------------

def create_question(question_text,days):
  '''
  Create question with with given "question_text"
  and puslished with "days" from now:
  + positive for questions puslished in the past,
  - negative for questions which will be published in the future.
  '''
  time = timezone.now() + datetime.timedelta(days=days)
  return Question.objects.create(question_text=question_text, pub_date=time)

def create_choice(question, choice_text, votes):
  '''
  Create choice for question with id "question"
  with given "answer_text" and "votes" number
  of initial votes.
  '''
  return Choice.objects.create(
           question=question,
           choice_text=choice_text,
           votes=votes,
         )

# ---------------------------------------------
# --------- MODEL TESTS -----------------------
# ---------------------------------------------

class QuestionModelTest(TestCase):

  def test_question_text_attribute(self):
    '''
    Test question_text attribute
    '''
    question = Question(question_text = 'Que?')
    self.assertIs(question.question_text, 'Que?')

  def test_was_published_recently_with_future_question(self):
    '''
    was_published_recently returns False for
    question published in the future
    '''
    time = timezone.now() + datetime.timedelta(days=30)
    future_question = Question(pub_date = time)
    self.assertIs(future_question.was_published_recently(), False)

  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)

  def test_too_long_question_over_200_characters(self, toolong='safe'):
    '''
    question_text of lenght over 200 characters should be allowed by Django.
    '''
    try:
      question = Question(question_text='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    except TypeError:
      pass
    self.assertIs(toolong, 'safe')


class ChoiceModelTest(TestCase):
  
  def test_choice_text_attribute(self):
    '''
    Test question_text attribute
    '''
    choice = Choice(choice_text = 'that')
    self.assertIs(choice.choice_text, 'that')

  def test_votes_attribute(self):
    '''
    Checks Choice vote attribute.
    '''
    votes_above_zero = Choice(votes=5)
    self.assertIs(votes_above_zero.votes, 5)

  def test_fix_votes_with_negative_votes_value(self):
    '''
    fix_votes() should return 0 votes for choice
    instance with -3 votes (which is below zero)
    '''
    votes_below_zero = Choice(votes=-3)
    votes_below_zero.fix_votes()
    self.assertIs(votes_below_zero.votes, 0)

  def test_fix_votes_with_good_votes_value(self):
    '''
    fix_votes() should return 3 votes for choice
    instance with 3 votes
    '''
    votes_below_zero = Choice(votes=3)
    votes_below_zero.fix_votes()
    self.assertIs(votes_below_zero.votes, 3)

  def test_fix_votes_with_zero_votes_value(self):
    '''
    fix_votes() should return 0 votes for choice
    instance with 0 votes
    '''
    votes_below_zero = Choice(votes=0)
    votes_below_zero.fix_votes()
    self.assertIs(votes_below_zero.votes, 0)

  def test_fix_votes_with_float_votes(self):
    '''
    fix_votes() should return 4 votes for choice
    instance with 4.7 votes
    '''
    votes_float = Choice(votes=4.7)
    votes_float.fix_votes()
    self.assertIs(votes_float.votes, 4)

  def test_too_long_choice_over_200_characters(self, toolong='safe'):
    '''
    choice_text of over 200 characters long should not be allowed by Django.
    '''
    try:
      choice = Choice(choice_text='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    except TypeError:
      pass
    self.assertIs(toolong, 'safe')


# ---------------------------------------------
# --------- VIEW TESTS ------------------------
# ---------------------------------------------

class QuestionIndexTestView(TestCase):

  def test_no_question(self):
    '''
    If no questions exists, view should return appropriate message.
    '''
    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):
    '''
    Question with the past publication date have to be displayed on the index page.
    '''
    create_question("Can I have a test?", days=-30)
    response = self.client.get(reverse('polls:index')) 
    self.assertEqual(response.status_code, 200)
    self.assertContains(response, "Can I have a test?")
    self.assertQuerysetEqual(response.context['latest_question_list'],
                          ["<Question: Can I have a test?>"])

  def test_future_question(self):
    '''
    Question with the future publication date should not be listed at index page.
    '''
    create_question("Back from the future?", days=1)
    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_and_future_question(self):
    '''
    Only past question should be displayed on the index page.
    '''
    create_question("Can I have a test?", days=-30)
    create_question("Back from the future?", days=1)
    response = self.client.get(reverse('polls:index')) 
    self.assertEqual(response.status_code, 200)
    self.assertContains(response, "Can I have a test?")
    self.assertQuerysetEqual(response.context['latest_question_list'],
                          ["<Question: Can I have a test?>"])

  def test_two_past_questions(self):
    '''
    Both past questions should be
    displayed on the index page.
    '''
    create_question("Can I have a first test?", days=-30)
    create_question("Can I have a second test?", days=-20)
    response = self.client.get(reverse('polls:index')) 
    self.assertEqual(response.status_code, 200)
    self.assertContains(response, "Can I have a first test?")
    self.assertQuerysetEqual(response.context['latest_question_list'],
                          ["<Question: Can I have a second test?>",
                           "<Question: Can I have a first test?>",
                          ])

class QuestionDetailViewTest(TestCase):
  
  def test_future_question_status_code(self):
    '''
    Detail view of question from future should return 404.
    '''
    future_question = create_question(question_text='Future?', 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_status_code(self):
    '''
    Detail view of question from the past should return 200.
    '''
    past_question = create_question(question_text='Past?', days=-5)
    url = reverse('polls:detail', args=(past_question.id,))
    response = self.client.get(url)
    self.assertEqual(response.status_code, 200)

  def test_past_question_response(self):
    '''
    Detail view of question from the past should return question_text.
    '''
    past_question = create_question(question_text='Past?', days=-5)
    url = reverse('polls:detail', args=(past_question.id,))
    response = self.client.get(url)
    self.assertContains(response, past_question.question_text)

Zapisywanie do bazy danych w testach Django

Dodatkowa baza danych w pliku settings.py. Ważne jest, by baza wykorzystywana do testów miała zdefiniowaną samę siebie jako bazę testową.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'produkcja',
        'USER': 'produkcja',
        'PASSWORD': 'qwerty123',
        'HOST': 'localhost',   # Or an IP Address that your DB is hosted on
        'PORT': '3306',
      },
    'TEST': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'testowa',
        'USER': 'testowa',
        'PASSWORD': 'qwerty234',
        'HOST': 'localhost',   # Or an IP Address that your DB is hosted on
        'PORT': '3306',
        'TEST': {'NAME': 'testowa'}
    }
}

W pliku tests.py należy utworzyć klasę dziecziczącą po TestCase i zdefiniować używaną bazę danych. Nazwa klasy może być dowolna:

class TestCaseD(TestCase):
  databases = {'default' ,'TEST'}

Wszystkie testy muszą być egzamplarzami klasy TestCaseD.

Wszystkie „ręczne” zapisy do bazy danych w pliku (lub plikach) tests.py należy opatrzyć parametrem using:

jakis_obiekt.save(using='TEST')

Należy utworzyć bazę danych testowa, użytkownika testowa oraz nadać mu pełne uprawnienia do wszystkich tabel.

Teraz można uruchomić testy. Agrument –keepdb jest opcjonalny i pozwala na przejrzenie zawartości bazy danych testowa po zakończeniu testów automatycznych. Oczekiwany efekt:

python manage.py migrate --database TEST
[Applying contenttypes.0001_initial... OK] [...]

python manage.py test --debug-mode --keepdb
Found 18 test(s).
Using existing test database for alias 'default'...
Using existing test database for alias 'TEST'...
System check identified no issues (0 silenced).
..........
........
----------------------------------------------------------------------
Ran 18 tests in 6.086s

OK
Preserving test database for alias 'default'...
Preserving test database for alias 'TEST'...

Po każdym wykonaniu testów bez parametru –keepdb należy ponownie utworzyć testową bazę danych oraz wykonać migrację.

wiki/django_tests.txt · ostatnio zmienione: 2022/12/04 13:17 przez lukasz

Wszystkie treści w tym wiki, którym nie przyporządkowano licencji, podlegają licencji: Public Domain
Public Domain Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki