작성한 코드들이 원하는 값을 내놓는지 확인하는 코드!
예) unittest(파이썬)
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
결과)
test_isupper (\__main__.TestStringMethods) ... ok
from django.test import TestCase
class TestView(TestCase):
# def test_two_is_three(self):
# self.assertEqual(2, 3)
def test_two_is_two(self):
self.assertEqual(2, 2)
장고의 테스트툴이다!
유닛 테스트 관련 자료 : https://velog.io/@fcfargo/django-Unit-Test
python testing
drf testing 공식문서
drf tdd 예시
Testing in DRF
django testing 공식문서
python testing
장고에서 테스팅(mozilla)
simplejwt에서 테스트하기
multipart 테스트하기
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
class UserResgistrationAPIViewTestCase(APITestCase):
def test_registration(self):
url = reverse("user_view")
user_data = {
"email":"test@naver.com",
"password":"test",
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 201)
reverse로 user_view라는 이름을 가지고 있는 url을 불러온다!
client를 사용하는 이유는 내가 클라이언트가 되어 서버에 요청을 보내는 식으로 테스트 하기 위함!!
class UserResgistrationAPIViewTestCase(APITestCase):
def test_login(self):
url = reverse("token_obtain_pair")
user_data = {
"email":"test@naver.com",
"password":"test",
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 200)
하지만 결과로 401 에러가 나온다.
그 이유는 test메소드를 실행할 때마다 test메소드 용 db를 생성하고 지우기 때문이다!
위처럼 오류가 나는 이유는 unit-testing 때문이라고 한다.
모든 테스트가 모두 독립적이어야 하기 때문에!!
연달아서 test를 진행하고 싶으면 따로 함수를 적을것이 아니라 같은 함수내에 바로 작성해야 한다!
하지만 매번 모두 작성하기는 힘드니!! 이 때 사용하는것이 setup이다.
class LoginUserTest(APITestCase):
def setUp(self):
self.data = {"email":"test@naver.com", "password":"test"}
self.user = User.objects.create_user("test@naver.com", "test")
def test_login(self):
response = self.client.post(reverse("token_obtain_pair"), self.data)
self.assertEqual(response.status_code, 200)
setup 관련 자료 : https://docs.djangoproject.com/en/4.0/topics/testing/overview/
테스트코드를 작성할 때 중요한 원칙이 있는데 테스트 대상이 파이썬의 메소드나 장고의 라이브러리가 되어서는 안된다!!
class LoginUserTest(APITestCase):
def setUp(self):
self.data = {'email':'test@naver.com', 'password':'test'}
self.user = User.objects.create_user('test@naver.com', 'test')
def test_login(self):
response = self.client.post(reverse('token_obtain_pair'), self.data)
self.assertEqual(response.status_code, 200)
def test_get_user_data(self):
access_token = self.client.post(reverse('token_obtain_pair'), self.data).data['access']
response = self.client.get(
path=reverse("user_view"),
HTTP_AUTHORIZATION=f"Bearer {access_token}"
)
print(response.data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["email"], self.data["email"])
이렇게 사용자의 정보를 터미널에 가져올 수 있다!!
테스트를 하다 보면 user데이터같은 것은 모든 데이터마다 동일하게 유지하고 싶을 가 있다.
setup을 매번 하지 않고 한번 한 후 모든 테스트에 대해서 사용할 수 있는 방법인 setuptestdata에 대해서 알아보자!!
class Person:
def__init__(self, name, age):
self.name = name
self.age = age
@classmethod
def fromBirthYear(cls, name, birthYear): # 본인 클래스, 이름, 태어난 해
return cls(name, date.today().year - birthYear) # 클래스에 이름과 태어난 해를 입력하여 return
def display(self):
print(self.name + "'s age is:" + str(self.age))
person1 = Person.fromBirthYear('John', 1985)
person1.display()
위처럼 클래스 메소드를 사용하면 인스턴스 없이 바로 클래스이름.함수이름 형식으로 바로 실행할 수 있다.
클래스 메소드에 들어가는 첫 번째 인자는 self가 아닌 본인클래스 이름(cls)이다. (본인의 클래스를 다시 실행 시키는 것이 가능)
class Person:
def__init__(self, name, age):
self.name = name
self.age = age
@classmethod
def fromBirthYear(cls, name, birthYear):
return cls(name, date.today().year - birthYear)
@staticmethod
def isAdult(age):
return age > 18
person1 = Person.('John', 21)
person2.Person.fromBirthYear('John', 1996)
print(Person.isAdult(22))
스테틱 메소드는 self, 본인클래스 둘다 들어가지 않는다.
스테틱 메소드를 사용하지 않고는 class바깥에 함수를 작성해야 하기 때문에 코드를 깔끔하게 작성하기 위해 많이 사용한다.
class ArticleCreateTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.user_data = {'username':'john', 'password':'johnpassword'}
cls.article_data = {'title':'some title', 'content':'some content'}
cls.user = User.objects.create_user('john', 'johnpassword')
def setUp(self):
self.access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access']
여기세어 setUp에 access_token에 대한 코드를 따로 작성해주는 이유는 .client가 클래스메소드가 아니기 때문에 cls.client를 하면 오류가 나기 때문에 이 부분만 setup으로 작성해야 한다!!
class ArticleCreateTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.user_data = {'email':'john@naver.com', 'password':'johnpassword'}
cls.article_data = {'title':'some title', 'content':'some content'}
cls.user = User.objects.create_user('john@naver.com', 'johnpassword')
def setUp(self):
print()
self.access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data["access"]
# def setUp(self):
# self.user_data = {'email':'test@naver.com', 'password':'test'}
# self.article_data = {'title':'title', 'content':'hi'}
# self.user = User.objects.create_user('test@naver.com', 'test')
# self.access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access'] # .client가 클래스메소드가 아니기 때문에 cls.client를 하면 오류가 나기 때문에 이 부분만 setup으로
def test_fail_if_not_logged_in(self): #테스트 함수에는 무조건 앞에 test를 붙이기!
url = reverse('article_view')
response = self.client.post(url, self.article_data)
self.assertEqual(response.status_code, 401)
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from user.models import User
class ArticleCreateTest(APITestCase):
def test_create_article(self):
response = self.client.post(
path=reverse("article_view"),
data=self.article_data,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
# self.assertEqual(response.data["message"], "글 작성 완료!")
self.assertEqual(response.status_code, 200)
이미지를 포함한 게시글을 작성하는 방법은 조금 더 어렵다.
이는 아래와 같이 2단계로 나눠서 진행할 수 있다.
1. 임시 이미지 파일 생성
2. 이미지 파일 전송
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from user.models import User
# 이미지 업로드
from django.test.client import MULTIPART_CONTENT, encode_multipart, BOUNDARY
from PIL import Image
import tempfile # 임시파일 생성
def get_temporary_image(temp_file): # 임시 파일을 생성하여 이를 이용해 임시 이미지를 생성
size = (200, 200)
color = (255, 0, 0, 0)
image = Image.new("RGBA", size, color)
image.save(temp_file, 'png') # tempt_file이라는 폴더를 가져와서 양식만 png로 변경 후 이미지 저장
return temp_file
class ArticleCreateTest(APITestCase):
def test_create_article_with_image(self):
# 임시 이미지 파일 생성
temp_file = tempfile.NamedTemporaryFile()
temp_file.name = "image.png"
image_file = get_temporary_image(temp_file)
# 여기까지가 이미지 파일 생성
image_file.seek(0) #이미지의 첫 번째 프레임 받아오기
self.article_data["image"] = image_file
# 전송
response = self.client.post(
path=reverse("article_view"),
data = encode_multipart(data = self.article_data, boundary=BOUNDARY),
content_type=MULTIPART_CONTENT,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
self.assertEqual(response.status_code, 200)
pip install Faker
from faker import Faker를 한 후 인스턴스를 생성하는 방식으로 사용
from faker import Faker
faker = Faker() # 영어
faker = Faker("ko_KR") # 한국어(name만 된다.)
print(faker.name()) # 랜덤한 이름
print(faker.first_name())
print(faker.last_name())
print(faker.word()) # 랜덤한 단어
print(faker.sentence()) # 랜덤한 한 문장
print(faker.text()) # 랜덤한 긴 문장
class ArticleReadTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.faker = Faker()
cls.articles=[]
for i in range(10):
cls.user = User.objects.create_user(cls.faker.name(), cls.faker.word())
cls.articles.append(Article.objects.create(title=cls.faker.sentence(), content=cls.faker.text(), user=cls.user))
class Article(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length = 50)
content = models.TextField()
image = models.ImageField(blank=True, upload_to='%Y/%m/')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
likes = models.ManyToManyField(User, related_name="like_articles") #manytomany 필드는 related_name을 설정안하면 이름이 중복되기 때문에 꼭 설정을 해주어야 한다!!
def __str__(self):
return str(self.title)
def get_absolute_url(self):
return reverse('article_detail_view', kwargs={"article_id":self.id})
딕셔너리는 키 벨류 형식으로 이루어져 있는데 이 형태는 for문을 돌리기가 어려운 형태이다.
이 때 for문을 돌리면서 키값과 벨류값을 뽑아내고 싶을 때 items를 사용한다.
my_dict = {"뭔가 키값": "뭔가 벨류값", "some key":"some value", "name":"young"}
for key, value in my_dict.items():
print(key)
print(value)
키값은 key로 벨류값은 value로 들어가 각자 for문에 돌려진다.
print(key)를 했을 때는 뭔가 키값, some key, name이 나오고
print(value)를 했을 때는 뭔가 벨류값, some value, young이 나온다.
class ArticleReadTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.faker = Faker()
cls.articles=[]
for i in range(10):
cls.user = User.objects.create_user(cls.faker.name(), cls.faker.word())
cls.articles.append(Article.objects.create(title=cls.faker.sentence(), content=cls.faker.text(), user=cls.user))
def test_get_article(self):
for article in self.articles:
url = article.get_absolute_url()
response = self.client.get(url)
serializer = ArticleSerializer(article).data
for key, value in serializer.items():
self.assertEqual(response.data[key], value) # response.data에서 돌아온 데이터에 대해 가지고 있는 key값을 넣으면 그에 대한 value값이 나오게
결과가 email의 pk값이 아닌 email이 나오게 해보자!!
class ArticleSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
comment_set = CommentSerializer(many=True)
likes = serializers.StringRelatedField(many=True)
def get_user(self, obj):
return obj.user.email
class Meta:
model = Article
fields = "__all__"