Django는 인빌트(inbuilt) 단위의 테스트 기능을 제공
단위 테스트
개별적인 방법을 테스트해 올바른 값을 반환하는지 여부와 무효한 데이터를 처리하는 방법, 사용자 입력 시퀀스가 원하는 결과를 얻을 수 있도록 모든 방법을 테스트하는 등 다양한 수준에서 수행할 수 있음
네 가지 기본 개념을 기반으로 함
- 테스트 픽스처(Test Fixture)
- 테스트를 수행하는 데 필요한 설정
- DB, 샘플 데이터 세트, 서버 설정 등
- 테스트 수행 후 필요한 모든 정리 작업이 포함될 수 있음
- 테스트 케이스(Test Case)
- 테스트의 기본 단위
- 주어진 입력 세트가 예상 결과 세트가 되는지 여부를 확인
- 테스트 스위트(Test Suite)
- 그룹으로 실행되는 여러 테스트 케이스 또는 다른 테스트 스위트
- 테스트 러너(Test Runner)
- 테스트 실행을 제어하고 테스트 결과를 사용자에게 다시 제공하는 소프트웨어 프로그램
자동화 테스트
이전 글에서 해오던 모든 작업들(Shell을 통한 함수의 작동 여부 테스트, 입력이 어떻게 출력 되는지 예상하기 등)은 테스트라고 할 수 있음
자동화 테스트는 다음을 수행함
- 시간 절약 - 큰 응용 프로그램의 구성 요소 사이의 무수한 복잡한 상호작용을 수동으로 테스트하는 것은 시간이 많이 소요
- 문제 예방 - 테스트 코드는 내부 동작을 강조하기 때문에 문제가 발생한 부분을 확인할 수 있음
- 전문가 보기 - 전문가가 테스트를 작성(코드를 테스트 없이 작성하면 설계 단계에서 망가지기 때문)
- 팀워크 향상 - 테스트를 통해 실수로 동료의 코드를 손상 시키지 않음
테스트 작성
Book 모델에 버그를 넣어보자
import datetime
from django.utils import timezone
class Book(models.Model):
title = models.CharField(max_length=30)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE)
publication_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.title
# 책의 게시 날짜가 더 최근인 경우 true 반환
def recent_publication(self):
return self.publication_date >= timezone.now().date() - datetime.timedelta(days=13)
게시글 작성일은 2023.01.22이며 13일 전이면 2023.01.09이므로 책의 게시 날짜와 같으므로 True를 반환함
각각 2023.01.12, 2023.01.10 으로 book.publication_date 이후 이므로 False을 반환함
원래 목적인 "책의 게시 날짜가 더 최근일 경우 True"와 반대로 동작하고 있음 - 버그 발생
Django의 startapp 명령을 사용해 앱을 만들면 디렉터리에 tests.py라는 파일이 만들어짐
이 파일에서 앱의 테스트가 진행되어야 함
from django.test import TestCase
import datetime
from django.utils import timezone
from .models import Book
class BookMethodTests(TestCase):
def test_recent_pub(self):
"""
recent_publication()은 False을 미래의 출판 날짜를 위해 반환해야 함
"""
futuredate = timezone.now().date() + datetime.timedelta(days=5)
future_pub = Book(publication_date=futuredate)
self.assertEqual(future_pub.recent_publication(),False)
다음 코드는 클래스에서 테스트 코드를 캡슐화하고 recent_publication() 메서드를 미래의 날짜와 비교해 테스트하는 가정을 생성
- futuredate : 현재로 부터 5일 후의 날짜
- future_pub : futuredate를 publication_date 인 Book 모델 객체
- assertEqual() : future_pub.recnet_publication()과 False가 동일한지 테스트
각종 assert 메서드(Assertions)
테스트 실행
python manage.py test books
- Python manage.py test books 은 books 앱에서 테스트를 찾음
- django.test.TestCase 클래스의 자식 클래스 BookMethodTests를 찾음
- 위의 클래스는 테스트 목적으로 특별한 데이터베이스를 생성
- 'test'로 시작하는 메서드를 찾음
- test_recent_pub에서 Book 인스턴스가 만들어졌고 publication_date 필드는 현재 날짜에서 5일 후의 날짜가 됨
- assertEqual() 메서드를 사용해, recent_publication()의 반환값이 False라고 예측을 했지만 True를 반환하여 오류가 발생
테스트 도구
Django는 테스트를 작성할 때 편리하게 사용할 수 있는 툴 세트를 제공
테스트 클라이언트
- 더미 웹 브라우저 역할을 하는 파이썬 클래스, 뷰를 테스트하거나 Django 기반 응용 프로그램과 프로그래밍 방식으로 상호작용할 수 있음
- 수행할 수 있는 작업
- URL에서 GET, POST 요청을 시뮬레이트하고 하위 수준의 HTTP에서부터 웹 페이지 콘텐츠에 이르는 모든 내용 관찰
- 각 단계에서 리디렉션 체인을 확인, URL 및 상태 코드 확인
- 주어진 요청이 특정 값을 포함하는 Template Context와 함께 주어진 Django Template에 의해 렌더링 되는지 테스트
- Selenium과 같은 웹 브라우저 내 프레임워크를 사용해 렌더링된 HTML과 Javascript 기능을 테스트, 자세한 내용은 LiveServerTestCase 문서 참조
제공되는 TestCase 클래스
- unittest.TestCase 기본 클래스를 확장함
- 일부 DB 에서 가동을 테스트할 수 없을 때 TransactionTestCase를 사용
- 테스트 코드에서 커밋 및 롤백의 영향을 테스트
- 나머지 기능은 TestCase와 동일
unittest.TestCase는 django.test.TestCase로 테스트의 기본 클래스를 변경하는 것으로 쉽게 변환할 수 있음
- 유닛 테스트의 기능은 계속 사용 가능함
- 추가 기능
- fixture의 자동 삭제
- 테스트를 2개의 중첩된 원자 블록 내에서 래핑(1. 전체 클래스용, 2. 개별 테스트용)
- TestClient 인스턴스를 생성
- Redirection이나 Form Error 같은 것들을 테스트하기 위한 Django 특유의 Assertions임
- TestCase는 TrasactionTestCase에서 상속됨
기본 테스트 클라이언트
- unittest.TestCase
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_detatils(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code,200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code,200)
- django.test import TestCase
from django.test import TestCase
class SimpleTest(TestCase):
def test_detatils(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code,200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code,200)
settings()
- 테스트 목적으로 테스트 코드를 실행한 후 일시적으로 설정을 변경하고 원래 값으로 되돌리는 것이 유용한 경우에 사용
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# 기본 동작에 대한 확인
response = self.client.get('/sekrit/')
self.assertRedirects(response,'/accounts/login/?next=/sekrit/')
# LOGIN_URL 설정 재정의(Overriding)
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response,'/accounts/login/?next=/sekrit/')
# LOGIN_URL 설정 원상 복구
modify_settings()
- 값 목록이 포함된 설정을 재정의 하는 것은 쉽지 않음, 실제로는 값을 추가하거나 제거하는 것으로 충분
- 이러한 기능을 하는 메서드
- 각 작업(append, prepend, remove)에 대해 리스트나 문자열을 제공할 수 있음
- 값이 이미 존재하면 작업에 아무런 효과가 없음, 값이 존재하지 않으면 제거도 하지 않음
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
MIDDLEWARE_CLASSES = {
'append':'django.middleware.cache.FetchFromCacheMiddleWare',
'prepend':'django.middleware.cache.UpdateCacheMiddleware',
'remove':[
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
],
}
with self.modify_settings(MIDDLEWARE_CLASSES):
response = self.client.get('/')
# ...
override_settings()
- 테스트 메서드에 대한 설정을 재정의하기 원하는 경우 사용하는 데커레이터
- 클래스에도 적용 가능함
from django.test import TestCase, override_settings
# @override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase)
@override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response,'/accounts/login/?next=/sekrit/')
modify_settings()
- modify_settings() 메서드와 같은 기능을 하는 데커레이터
from django.test import TestCase, modify_settings
@modify_settings(MIDDLEWARE_CLASSES = {
'append':'django.middleware.cache.FetchFromCacheMiddleWare',
'prepend':'django.middleware.cache.UpdateCacheMiddleware',
'remove':[
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
],}
)
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get('/')
# ...
'Back-end & Server > Django' 카테고리의 다른 글
[Django] 비 HTML 콘텐츠와 세션 (0) | 2023.02.01 |
---|---|
[Django] 배포 (0) | 2023.01.31 |
[Django] 고급 기능들 (0) | 2023.01.20 |
[Django] 폼(Form) (0) | 2023.01.20 |
[Django] Admin 사이트 (0) | 2023.01.16 |