본문 바로가기

Back-end & Server/Django

[Django] 테스팅

728x90
반응형

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)

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

테스트 실행

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 문서 참조
 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

제공되는 TestCase 클래스

  • unittest.TestCase 기본 클래스를 확장함
  • 일부 DB 에서 가동을 테스트할 수 없을 때 TransactionTestCase를 사용
    • 테스트 코드에서 커밋 및 롤백의 영향을 테스트
    • 나머지 기능은 TestCase와 동일
 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

unittest.TestCasedjango.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('/')
        # ...
728x90
반응형

'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