Notice
Recent Posts
Recent Comments
Link
오늘도 개발
Django로 Unit Test해보기 본문
위코드 Software Testing 세션을 정리한 내용입니다.
1. Unit Test란?
프로그램을 이루는 가장 작은 유닛을 테스트하는 것.
즉 함수 또는 클래스를 테스트하는 것.
(참고 - Python unittest 모듈로 Unit Test 해보기)
2. Django에서 Unit Test하기
Django에서는 Python에서 기본적으로 제공하는 unittest를 사용할 수 있다.
unittest를 사용하면 TestCase 클래스와 Client() 객체를 사용할 수 있다.
테스트 코드는 앱 생성 시 자동으로 생성되는 test.py에 작성하고,
실행은 manage.py가 있는 위치에서 다음과 같이 실행한다.
python manage.py test <앱 이름>
3. Django에서 Unit Test할 때 기억할 것
1. 테스트 케이스를 위한 클래스를 만들 때는 항상 TestCase 클래스를 상속받아 만들기
2. 테스트 클래스 내부에 작성하는 테스트 메서드는 항상 test_로 시작하기(아니면 파이썬이 인식 못함)
3. Client() 객체를 이용하여 함수 호출하기
예 1) 엔드포인트 테스트하기
<views.py>
import json
from django.views import View
from django.http import HttpResponse, JsonResponse
class JustView(View):
def get(self, request):
return JsonResponse({'message':'This is for unit test'}, status = 200)
<test.py>
from django.test import TestCase, Client
from units.models import *
class JustTest(TestCase):
def setUp(self):
# 테스트 코드에서 만드는 오브젝트는 DB에 들어가지 않는다.
Product.objects.create()
Product.objects.create()
Product.objects.create()
def test_success_just_view_get_method(self):
# Client() 객체를 사용하면 클라이언트인 척 하고 요청을 보낼 수 있다.
client = Client()
# 클라이언트가 /just 엔드포인트로 get 요청을 보낸 경우를 시뮬레이션 한다.
# 호스트는 생략하고 작성한다.
response = client.get('/just')
# 성공하는 경우를 테스트하는 메서드이기 때문에 200 코드와 성공 메시지를 넣어 확인해본다.
# 성공 시 응답 코드가 맞게 뜨는지 확인
self.assertEqual(response.status_code, 200)
# 성공 시 json 메시지가 맞게 뜨는지 확인
self.assertEqual(response.json(), {
'message' : 'This is for unit test'
})
<터미널> : 테스트 실행
python manage.py test units
# 결과
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.033s
OK
Destroying test database for alias 'default'...
예 2) 모델 테스트하기
<models.py>
class Book(models.Model):
title = models.CharField(max_length=50)
authors = models.ManyToManyField('Author', through = 'AuthorBook')
class Meta:
db_table = 'books'
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
class Meta:
db_table = 'authors'
class AuthorBook(models.Model):
book = models.ForeignKey('Book', on_delete= models.SET_NULL, null=True)
author = models.ForeignKey('Author', on_delete= models.SET_NULL, null=True)
class Meta:
db_table = 'author_book'
<views.py>
import json
import jwt
import requests
from django.views import View
from django.http import JsonResponse, HttpResponse
from .models import Book, Author, AuthorBook
class AuthorView(View):
# 클라이언트가 작가 정보를 보내면 작가를 DB에 저장하는 api
def post(self, request):
data = json.loads(request.body)
try:
if Author.objects.filter(name=data['name']).exists():
return JsonResponse({'message':'DUPLICATED_NAME'}, status = 400)
Author(
name = data['name'],
email = data['email']
).save()
return JsonResponse({'message':'SUCCESS'}, status = 200)
except:
return JsonResponse({'message':'INVALID_KEYS'}, status = 400)
class AuthorBookView(View):
# 패스 파라미터에 책 아이디를 넣어 보내면 책의 저자 리스트를 응답하는 api
def get(self, request, book_id):
try:
if Book.objects.filter(id = book_id).exists():
book = Book.objects.get(id = book_id)
authors = list(AuthorBook.objects.filter(book = book).values('author'))
return JsonResponse({'authors':authors}, status = 200)
return JsonResponse({'message':'NO_AUTHOR'}, status = 400)
except KeyError:
return JsonResponse({'message':'INVALID_KEYS'}, status = 400)
<test.py>
import json
import bcrypt
from django.test import TestCase, Client
from .models import Book, Author, AuthorBook
# Author 뷰의 테스트케이스
class AuthorTest(TestCase):
def setUp(self):
# db에 다음과 같은 오브젝트가 이미 존재한다고 설정함
Author.objects.create(
id = 1,
name = 'Snoopy',
email = 'snoopy@snoopy.com'
)
Author.objects.create(
id = 2,
name = 'Garfield',
email = 'garfield@cat.com'
)
def tearDown(self):
Author.objects.all().delete()
# db에 없는 작가 정보를 post 요청했을 때의 상황
def test_fail_authorview_post_duplicate_name(self):
client = Client()
author = {
'name' : 'cheddar',
'email' : 'cheddar@cheddar.com'
}
# 클라이언트 인 척 하고 /authors에 포스트 메서드로 요청을 보내봄
response = client.post('/authors', json.dumps(author), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(),
{
'message' : 'SUCCESS'
}
)
# db에 이미 존재하는 작가 정보를 post 요청했을 때의 상황
def test_fail_authorview_post(self):
client = Client()
author = {
'name' : 'snoopy',
'email' : 'snoopy@snoopy.com'
}
response = client.post('/authors', json.dumps(author), content_type='application/json')
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json(),
{
'message' : 'DUPLICATE_NAME'
}
)
# 'name' 이라는 키가 아니라 'first_name'이라는 키를 잘못 사용해서 요청한 경우
def test_fail_authorview_post_invalid_keys(self):
client = Client()
author = {
'first_name' : 'Snoopy',
'email' : 'snoopy@snoopy.com'
}
response = client.post('/authors', json.dumps(author), content_type='application/json')
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json(),
{
'message':'INVALID_KEYS'
}
)
# AuthorBook 뷰의 테스트케이스
class AuthorBookTest(TestCase):
def setUp(self):
Book.objects.create(
id = 1,
title = 'python'
)
Book.objects.create(
id = 2,
title = 'javascript'
)
Author.objects.create(
id = 1,
name = 'Snoopy',
email = 'snoopy@gmail.com'
)
Author.objects.create(
id = 2,
name = 'Garfield',
email = 'garfield@gmail.com'
)
AuthorBook.objects.create(
book = Book.objects.get(id=1),
author = Author.objects.get(id=1)
)
AuthorBook.objects.create(
book = Book.objects.get(id=1),
author = Author.objects.get(id=2)
)
AuthorBook.objects.create(
book = Book.objects.get(id=2),
author = Author.objects.get(id=1)
)
AuthorBook.objects.create(
book = Book.objects.get(id=2),
author = Author.objects.get(id=2)
)
def tearDown(self):
Book.objects.all().delete()
Author.objects.all().delete()
AuthorBook.objects.all().delete()
# /author-books/<int:book_id>에 보낸 요청이 성공적인 경우
def test_success_authorbook_get(self):
client = Client()
response = client.get('/author-books/1')
self.assertEqual(response.json(),
{
"authors" : [
{'author': 1},
{'author': 2}
],
}
)
self.assertEqual(response.status_code, 200)
예 3) Mock(거짓된 값)으로 테스트하기
결제 API, 소셜 로그인 등 실제로 실행할 수 없는 코드를 테스트 할 때
Mock을 이용하여 테스트한다.
<views.py>
import requests
class ...
def post(self, request):
...
# 카카오 api 엔드포인트 /kakao/user/me에 토큰을 넣어 요청을 보냄
response = requests.get('/kakao/user/me', token = {...}, )
..
# 카카오 서버로 온 응답으로부터 id를 가져옴
kakao_user_id = response.json()["id"].
...
<test.py>
from django.test import TestCase, Client
# mock을 사용하기 위해 필요한 모듈
from unittest.mock import patch, MagicMock
class KakaoLoginTest(TestCase):
# mock을 사용할 때는 모듈에서 제공하는 patch 데코레이터를 붙여야 함
# users의 views.py에서 requests를 사용한 곳은 실제로 요청을 보내지 않을 것이며
# 요청하지 않았으므로 받지 못하는 응답은 mock으로 대체한다는 뜻
@patch("users.views.requests")
def test_success_kakao_signin_new_user(self, mocked_requests):
client = Client()
# 테스트에 사용할 가짜 응답(mock)
class KaKaoResponse:
def json(self):
return {
"id":123456789,
"kakao_account": {
"profile_needs_agreement": false,
"profile": {
"nickname": "홍길동",
}
}
mocked_requests.get = MagicMock(return_value = KaKaoResponse())
headers = {"HTTP_Authoriazation": "가짜 access_token"}
response = client.get("/users/signin/google", **headers)
self.assertEqual(response.status_code, 201)'웹 프로그래밍 > Django' 카테고리의 다른 글
| Django Rest Framework 튜토리얼 따라하기 1. Serialization (6) | 2023.01.07 |
|---|---|
| Django ORM 최적화 - select_related, prefetch_related (0) | 2022.08.05 |
| Django 앱 세팅하는 법 (0) | 2022.07.17 |
| Django 프로젝트 세팅하는 법 (0) | 2022.07.17 |
| 내 컴퓨터로 runserver 하기 (0) | 2022.07.13 |