당신은 멋쟁이, 우리는 장고쟁이~

0%

Writing your first Django app, part5 - 5편

새로운 view 테스트하기



이제 우리는 runserver 를 통해서 만족스러운 결과를 가질수 있습니다.


브라우저로 사이트를 열어서 과거와 미래 날짜를 가진 Question 을 생성하고, 발행이 완료된 Question 만 리스트 되어야 합니다. shell 세션을 기반으로, tests.py 를 작성해 줍니다.



첫번째로, polls/tests.py 에 reverse 를 추가해 줍니다.


1
from django.urls import reverse


그 다음에, 질문들을 생성하기 위해서 단축 함수를 만들어주고,


새로운 클래스도 생성해 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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 question 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 QuestionIndexViewTest(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 quesiton.>']
)

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


세부사항들을 잠시 체크하고 넘어갑니다.


첫번째로, quesiton 숏컷 함수인, create_question 은 질문 생성하는데에 반복되는 부분을 책임집니다.


test_no_questions 는 질문을 생성하지 않습니다. 하지만 “No polls are available” 이라는 메시지를 체크 하고, latest_question_list 가 비어있는것을 검증합니다.
django.test.TestCase 클래스는 추가적인 assertion 메써드들을 제공합니다.

해당 예시에서는, assertContains() 그리고 assertQuerysetEqual() 을 사용합니다.



test_past_question 에서는, 질문을 생성하고 리스트에 표시되는지 확인 합니다.


test_future_question 에서는, pub_date 가 미래일인 질문을 생성합니다.
데이터베이스는 각 테스트 메써드 마다 재설정 됩니다.

따라서, 첫번째 질문은 더이상 데이터베이스에 존재하지 않습니다. 그러므로, 인덱스는 아무 질문들도 가지고 있지 않아야 합니다.



추가적으로, 우리는 관리자 입력값과 사용자 경험 그리고 각 상태 와 각 변경사항, 예상된 결과들의 발행 같은 것들을 테스트를 통해 하나의 이야기를 만들어 가는겁니다.


python manage.py test polls 를 실행하면, 8개의 테스트가 진행되고 OK 가 됩니다.


1
2
3
4
5
6
7
8
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........
----------------------------------------------------------------------
Ran 8 tests in 0.031s

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


DetailView 테스트 하기


우리가 또 해결해야 하는것은, 미래의 질문들이 인덱스에 표시가 되지 않는다 하더라도,


사용자들이 정확한 URL 을 알고 있거나, 추측할수 있다면, 사용자들은, 여전히 질문들에 접근할수 있습니다.


따라서, DetailView 에 비슷한 제약을 추가해 줍니다.


1
2
3
4
5
6
7
class DetailView(generic.DetailView):
...
def get_queryset(self):
"""
아직 발행되지 않은 질문들을 제외합니다.
"""
return Question.objects.filter(pub_date__lte=timezone.now())


그리고, 물론, 몇개의 테스트를 추가해 줄겁니다. pub_date 가 미래는 출력이 되지 않는 상황에서,

pub_date 가 과거값인 Question 이 출력될수 있는지,



polls/tests.py 파일을 아래와 같이 업데이트 해줍니다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

python manage.py test polls 를 실행하면,

아래와 같이 10가지의 테스트 케이스가 OK 됩니다.

1
2
3
4
5
6
7
8
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 10 tests in 0.047s

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



더많은 테스트를 위한 아이디어



비슷한 개념의 get_queryset 메써드를 ResultsView 에 추가해 주어야 하고 새로운 테스트 클래스를 생성 해주어야 합니다..
방금 DetailView 를 위해 한 작업과 굉장히 비슷한 작업이 될것이고,
사실 반복적인 작업이 될것 입니다.



또한 다른 방식으로도 어플리케이션을 향상 시킬수 있습니다.
예를 들어, Choices 가 없는 Questions 를 사이트에 발행 시키는것은 어리석은 짓입니다.
따라서, 우리의 뷰들은 이것에 대해 체크하고 Choice 가 없는 Questions 들을 제외할수 있습니다.

우리의 테스트들은 Choices 가 없는 Question 을 생성하고, 발행이 되지 않았는지 테스트 하고 비슷한 Question 인데 Choices 가 있는 것을 생성해서, 발행 되는지 테스트 합니다.



아마도, 로그인이 되어있는 관리자들은 발행이 취소된 Questions 도 볼수 있어야 합니다. 하지만, 일반 사이트 방문자는 볼수 없어야 하겠죠. 다시한번, 이것을 위해서 소프트웨어에 추가되어야 하는 그 어떤것이던지,



테스트와 함께 병행 되어야 합니다. 테스트를 먼저 작성하고 테스트를 통과하는 코드를 작성하던,

로직을 먼저 생각한다음에 테스트를 작성하여 증명하던지.



어떤 방식으로던, 테스트와 같이 병행되어야 합니다.


마치며..



view 들을 테스트 해보았는데.

아직은 test 를 작성할 상황이 많지 않았어서 그런지.. 전반적인 순서라던지, 감이 오지 않네요.

작동은 완벽하게 되지만, 코드 동작 자체의 연계성에 대한 이해가 좀 부족한것 같습니다;;;


조금 더 공부 진행하면서 알아봐야 할것 같습니다.