Testing
Testing은 각 기능의 테스트를 일일히 하는 것이 아닌, 주요 기능들을 컴퓨터 스스로 할 수 있도록 자동화하는 것을 뜻한다. 개발 시간을 줄여주고, 문제를 발견 및 예방하는데 도움이 된다. 테스트 코드로 인해 코드도 간결해지고, 협업에 도움이 된다는 장점도 있다.
polls_api 폴더로 이동해보자. 생성한 앱에는 자동으로 test.py 파일이 존재하는 것을 볼 수 있다.
이 test.py 파일에 testing 코드를 넣고 파일을 실행해 테스트를 자동화할 수 있다.
Testing Serializer
먼저 시리얼라이저에 대한 테스트 코드를 작성해보자.
from django.test import TestCase
from polls_api.serializers import QuestionSerializer
import unittest
# Create your tests here.
class QuestionSerializerTest(TestCase):
# 유효한 데이터에 대한 테스트
def test_with_valid_data(self):
serializer = QuestionSerializer(data={"question_text": "What is your name?"})
# assertTrue() 메서드: 주어진 조건이 True인지 확인
self.assertTrue(serializer.is_valid())
question = serializer.save()
# assertIsNotNone() 메서드: 주어진 객체가 None이 아닌지 확인
self.assertIsNotNone(question.id)
# 유효하지 않은 데이터에 대한 테스트
def test_with_invalid_data(self):
serializer = QuestionSerializer(data={"question_text": ""})
self.assertFalse(serializer.is_valid())
self.assertEqual(serializer.errors["question_text"][0], "This field may not be blank.")
Question 시리얼라이저에 대한 테스트 함수 2개를 작성해주었다. 유효한 데이터와 유효하지 않은 데이터를 넣었을 때의 각각 제대로 처리되는지에 대한 테스트 함수로, assertTrue, assertIsNotNone 등과 같은 메서드를 통해 시리얼라이저의 메서드가 어떻게 응답되는지 판단한다.
터미널에서 테스트 코드를 실행해보자.
❯ python manage.py test
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Destroying test database for alias 'default'...
테스트 케이스 2개를 찾았고, 2개 모두 정상적으로 처리되었다는 메시지를 확인할 수 있다.
이제 VoteSerializer를 test하는 코드를 만들어 전체적인 과정을 테스트해보자.
from django.test import TestCase
from polls_api.serializers import QuestionSerializer, VoteSerializer
from django.contrib.auth.models import User
from polls.models import Question, Choice, Vote
class VoteSerializerTest(TestCase):
# 각 테스트 메서드가 실행되기 전에 실행되는 메서드로, 테스트에 필요한 데이터를 설정
def setUp(self):
self.user = User.objects.create(username='testuser')
self.question = Question.objects.create(
question_text='테스트 질문입니다.',
owner=self.user,
)
self.choice = Choice.objects.create(
question=self.question,
choice_text='테스트 선택지'
)
# 정상적인 상황 테스트
def test_vote_serializer(self):
self.assertEqual(User.objects.all().count(), 1)
data = {
'question': self.question.id,
'choice': self.choice.id,
}
serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
self.assertTrue(serializer.is_valid())
vote = serializer.save()
self.assertEqual(vote.question, self.question)
self.assertEqual(vote.choice, self.choice)
self.assertEqual(vote.voter, self.user)
# 중복 투표 테스트
def test_vote_serializer_with_duplicate_vote(self):
Vote.objects.create(question=self.question, choice=self.choice, voter=self.user)
data = {
'question': self.question.id,
'choice': self.choice.id,
}
serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
self.assertFalse(serializer.is_valid())
self.assertIn('non_field_errors', serializer.errors)
# 질문과 선택지가 일치하지 않는 경우 테스트
def test_vote_serializer_with_unmatched_question_and_choice(self):
question2 = Question.objects.create(
question_text='다른 테스트 질문입니다.',
owner=self.user,
)
choice2 = Choice.objects.create(
question=question2,
choice_text='다른 테스트 선택지'
)
data = {
'question': self.question.id,
'choice': choice2.id,
}
serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
self.assertFalse(serializer.is_valid())
self.assertIn('non_field_errors', serializer.errors)
정상적인 상황/중복투표/질문-선택지 일치하지 않는 경우 총 3가지로 나누어 테스트 코드를 작성했다. 각 테스트 코드가 실행되기 전에 실행되는 setUp() 메서드는 테스트에 필요한 데이터를 미리 설정해주는 메서드다.
Testing View
이번엔 View에 대해서 테스트 코드를 작성해보자. View는 리스트를 불러오거나, 레코드를 생성하는 등의 테스트 케이스가 필요하다.
from rest_framework.test import APITestCase
from django.urls import reverse
from rest_framework import status
from django.utils import timezone
import unittest
# view를 테스트할 때는 APITestCase를 사용
class QuestionListTest(APITestCase):
# 테스트 메서드가 실행되기 전에 실행되는 메서드로, 테스트에 필요한 데이터를 설정
def setUp(self):
self.question_data = {'question_text': '테스트 질문입니다.'}
self.url = reverse('question-list')
# 질문 생성 테스트
def test_create_question(self):
user = User.objects.create_user(username='testuser', password='testpass')
self.client.force_authenticate(user=user)
response = self.client.post(self.url, self.question_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Question.objects.count(), 1)
self.assertEqual(Question.objects.first().question_text, self.question_data['question_text'])
self.assertLess((timezone.now() - Question.objects.first().pub_date).total_seconds(), 1)
# 인증되지 않은 사용자(로그인하지 않은 경우) 질문 생성 테스트
def test_create_question_without_authentication(self):
response = self.client.post(self.url, self.question_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
# 질문 목록 조회 테스트
def test_list_questions(self):
question = Question.objects.create(question_text='Question1')
choice = Choice.objects.create(question=question, choice_text='Question1')
Question.objects.create(question_text='Question2')
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
self.assertEqual(response.data[0]['choices'][0]['choice_text'], choice.choice_text)
Coverage
coverage는 작성한 코드의 어떤 부분이 얼마나 잘 테스트 되고 있는지를 판단하는 파이썬 라이브러리이다.
coverage 라이브러리를 설치한 뒤, Django 프로젝트의 테스트 코드를 실행하면서 실행된 코드의 커버리지를 측정하는 명령어를 아래와 같이 입력할 수 있다.
# 설치
pip install coverage
# 커버리지 측정
coverage run manage.py test
# 코드 커버리지 결과 요약
coverage report
>>>
Name Stmts Miss Cover
----------------------------------------------------------------------------------------------
manage.py 11 2 82%
mysite/__init__.py 0 0 100%
mysite/settings.py 21 0 100%
mysite/urls.py 3 0 100%
polls/__init__.py 0 0 100%
polls/admin.py 13 0 100%
polls/apps.py 4 0 100%
polls/migrations/0001_initial.py 6 0 100%
polls/migrations/0002_question_owner_alter_question_pub_date_and_more.py 6 0 100%
polls/migrations/0003_alter_choice_question_vote_and_more.py 6 0 100%
polls/migrations/__init__.py 0 0 100%
polls/models.py 29 5 83%
polls/tests.py 1 0 100%
polls/urls.py 5 0 100%
polls/views.py 34 15 56%
polls_api/__init__.py 0 0 100%
polls_api/migrations/__init__.py 0 0 100%
polls_api/permissions.py 9 4 56%
polls_api/serializers.py 48 7 85%
polls_api/tests.py 69 0 100%
polls_api/urls.py 3 0 100%
polls_api/views.py 46 10 78%
----------------------------------------------------------------------------------------------
TOTAL 314 43 86%
'Minding's Programming > Django' 카테고리의 다른 글
[Django] 투표 기능 구현하기 (0) | 2024.10.11 |
---|---|
[Django] RelatedField (0) | 2024.10.11 |
[Django] User 항목 모델에 추가 / User 관리 / User생성 / User 권한 (2) | 2024.10.10 |
[Django] 클래스 기반의 View, Mixin, Generic View (0) | 2024.10.10 |
[Django] GET, POST, PUT, DELETE (1) | 2024.10.10 |