티스토리 뷰

728x90
반응형

테스트 코드를 작성할 때 BaseTestCase 클래스를 별도로 작성하면 테스트 코드를 더 간편하게 관리할 수 있다.

예를 들어, setUp 메서드를 통해 초기 데이터를 생성하고, 여러 테스트에서 별도의 추가 작성 없이 재사용하는 것이다.

하지만 SimpleUploadedFile 사용한다면 이미지 포스팅 테스트를 작성할 때 다음과 같은 문제가 발생할 수 있다.

  • BaseTestCase 클래스에서 setUp 메서드를 통해 이미지와 함께 게시글(Post)을 생성하고, 이를 PostTest 클래스의 테스트 메서드에서 재사용하려고 할 때, 새로운 포스팅 요청이 실패하며 에러가 발생한다.
# response.data 
{'image': [ErrorDetail(string='제출한 파일이 비어있습니다.', code='empty')]}

문제의 원인

이 에러는 SimpleUploadedFile 객체가 한 번 읽으면 파일 포인터가 끝으로 이동하기 때문에 발생한다.

파일 포인터가 끝에 위치한 상태에서는 이미지 데이터를 다시 읽을 수 없기 때문에 파일이 비어있다고 판단하게 된다.

그렇기 때문에 재요청 시에 empty 에러가 발생하는 것이다.

해결 방법

이 문제를 해결할 수 있는 방법 세 가지를 소개하려고 한다.

 

  • 파일 포인터를 초기화(seek(0)) 하여 객체를 재사용.
  • 새로운 SimpleUploadedFile 객체 생성.
  • 각 테스트 클래스에서 독립적으로 객체 생성.

1. 파일 포인터 초기화

  • SimpleUploadedFile 객체를 재사용하기 위해서 파일 포인터를 맨 처음 위치로 돌려놓는 방법이 있다.
  • seek(0) 메서드를 사용하면 초기화가 가능하다.
  • 초기화를 한다면 동일한 객체를 여러번 사용할 수 있다.
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from rest_framework.test import APIClient

class BaseTestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.image = SimpleUploadedFile("test_image.jpg", b"image_content", content_type="image/jpeg")
        
        # 첫 번째 포스팅 생성
        self.client.post(
            "/posts/create/",
            {"title": "First Post", "content": "This is the first post.", "image": self.image},
            format="multipart"
        )
        
class PostTest(BaseTestCase):
    def test_create_post(self):
        # 파일 포인터 초기화
        self.image.seek(0)
        
        # 두 번째 포스팅 생성 (이미지 재사용)
        response = self.client.post(
            "/posts/create/",
            {"title": "Second Post", "content": "This is the second post.", "image": self.image},
            format="multipart"
        )
        self.assertEqual(response.status_code, 201)

2. 새로운 SimpleUploadedFile 객체 생성하기

  • 이미지를 요청할때마다 새로운 SimpleUploadedFile 객체를 생성하여 사용한다.
  • 새롭게 생성함으로써 파일 포인터 문제를 관리할 필요가 없어진다.
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from rest_framework.test import APIClient

class BaseTestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        image = SimpleUploadedFile("test_image.jpg", b"image_content", content_type="image/jpeg")
        
        # 첫 번째 포스팅 생성
        self.client.post(
            "/posts/create/",
            {"title": "First Post", "content": "This is the first post.", "image": image},
            format="multipart"
        )
        
class PostTest(BaseTestCase):
    def test_create_post(self):
        # 새 이미지 생성
        new_image = SimpleUploadedFile("test_image.jpg", b"image_content", content_type="image/jpeg")
        
        # 두 번째 포스팅 생성 (이미지 재사용)
        response = self.client.post(
            "/posts/create/",
            {"title": "Second Post", "content": "This is the second post.", "image": new_image},
            format="multipart"
        )
        self.assertEqual(response.status_code, 201)

3. 테스트 클래스별로 setUp 메서드 정의

  • 클래스마다 필요시에 독립적으로 setUp 메서드를 추가 작성하여 SimpleUploadedFile 객체를 생성한다.
  • 테스트마다 새롭게 객체가 생성됨으로 파일 포인터 문제를 회피할 수 있다.
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from rest_framework.test import APIClient

class BaseTestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        image = SimpleUploadedFile("test_image.jpg", b"image_content", content_type="image/jpeg")
        
        # 첫 번째 포스팅 생성
        self.client.post(
            "/posts/create/",
            {"title": "First Post", "content": "This is the first post.", "image": image},
            format="multipart"
        )
        
class PostTest(BaseTestCase):
    def setUp(self):
        super().setUp()
        self.image = SimpleUploadedFile("test_image.jpg", b"image_content", content_type="image/jpeg")
        
    def test_create_post(self):
        # 두 번째 포스팅 생성 (이미지 재사용)
        response = self.client.post(
            "/posts/create/",
            {"title": "Second Post", "content": "This is the second post.", "image": self.image},
            format="multipart"
        )
        self.assertEqual(response.status_code, 201)
728x90
반응형
댓글