코딩한걸음
728x90
반응형

Today I Learned

DRF project 진행 중 testcode를 작성해봤다.

백엔드에서 가장 중요한 부분이라고 할 수 있고, 이번에 처음 배운 개념이라 조금 어려웠던 부분도 있었다


어떤 문제가 있었는지

이번 프로젝트에서 맡은 부분이 상품 페이지였는데 GET 요청은 모든유저가,

나머지 POST, PUT, DELETE 요청은 admin만 가능하게 하는 API를 만들었다.

이를 테스트 하고싶었음

 


내가 시도해 본 것들

## products/views.py
class ProductView(APIView):
    # IsAuthenticatedOrReadOnly : 인증된 사람은 쓰기 가능, 그 외 읽기만 가능[GET, HEAD, OPTIONS]
    # IsAdminUser : 관리자만 쓰기 가능[POST, PUT, PATCH, DELETE]
    permission_classes = [IsAdminUserOrReadonly]

    def get(self, request):
        products = Product.objects.all()
        serializer = ProductListSerializer(products, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    def post(self,request):
        serializer = ProductCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class ProductDetailView(APIView):
    permission_classes = [IsAdminUserOrReadonly]

    def get(self, request, product_id):
        product = get_object_or_404(Product, id=product_id)
        serializer = ProductSerializer(product)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def put(self, request, product_id):
        product = get_object_or_404(Product, id=product_id)
        serializer = ProductCreateSerializer(product, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, product_id):
        product = get_object_or_404(Product, id=product_id)
        product.delete()
        return Response({"massage":"삭제 완료"}, status=status.HTTP_204_NO_CONTENT)

먼저 해당 view를 만들었다. 그리고 custom permission를 써서 관리자 외에 다른요청은 불가로 만들어줌

 

custom permission은 아래 글에서 확인 가능

https://raoneli-coding.tistory.com/106

 

23-05-08 TIL 일지 : DRF custom permission

Today I Learned 어떤 문제가 있었는지 진행하고있는 DRF project에서 인증관련으로 permission을 사용해봤는데 내가 원하는 조건- GET : 모두 허용, 그 외 : admin만 가능 -으로 할 수가 없었다 내가 시도해 본

raoneli-coding.tistory.com

 

이제 여기서 test를 하기위한 준비를 해야함


어떻게 해결 했는지

1. model에서 준비

## users/models.py
class UserManager(BaseUserManager):
    def create_user(self, email, username, password=None):
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(
            email=self.normalize_email(email),
            username=username,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, password=None):
        user = self.create_user(
            email,
            username=username,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

create_user와 create_superuser 를 test에서 쓰기 위해 셋팅 

 

 

2. Faker

## console
pip install Faker

 

https://pypi.org/project/Faker/

 

Faker

Faker is a Python package that generates fake data for you.

pypi.org

 

쉽게 생각하면 파이썬에서 쓸 수 있는 로렌입숨같은거다. 무지성 단어/이름/이메일/문장 생성기

 

 

3. Testcode 작성

## products/tests.py
class ProductReadTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        # Faker를 사용해서 10개의 랜덤 product를 만듦
        cls.faker = Faker()
        cls.products = []
        for i in range(10):
            cls.products.append(Product.objects.create(name=cls.faker.sentence(), introduction=cls.faker.text()))

    # 랜덤 생성한 product의 response와 serializer의 값이 같은지 확인
    def test_get_product(self):
        for product in self.products:
            url = product.get_absolute_url()
            response = self.client.get(url)
            serializer = ProductSerializer(product).data
            for key, value in serializer.items():
                self.assertEqual(response.data[key], value)
                
class ProductCreateTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        cls.admin = User.objects.create_superuser('admin@naver.com','admin_joonyeol','password')
        cls.admin_data = {'email':'admin@naver.com','password':'password'}
        cls.user = User.objects.create_user('test@naver.com','test_joonyeol','password')
        cls.user_data = {'email':'test@naver.com','password':'password'}
        cls.product_data = {'name':'product test name','introduction':'product test introduction','brand':'product test brand'}

    # admin, user의 access token을 받아옴
    def setUp(self):
        self.admin_access_token = self.client.post(reverse('token_obtain_pair'), self.admin_data).data['access']
        self.user_access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access']
        
    # 로그인 없이 post요청 보내면 401 확인
    def test_fail_if_not_logged_in(self):
        url = reverse("product_list")
        response = self.client.post(url, self.product_data)
        self.assertEqual(response.status_code, 401)

    # 로그인해도 admin이 아닌 user가 post요청 보내면 403 확인
    def test_fail_if_not_admin(self):
        response = self.client.post(
            path=reverse("product_list"),
            data=self.product_data,
            HTTP_AUTHORIZATION=f"Bearer {self.user_access_token}",
            )
        self.assertEqual(response.status_code, 403)

    # admin이면 product 생성 확인
    def test_create_product(self):
        response = self.client.post(
            path=reverse("product_list"),
            data=self.product_data,
            HTTP_AUTHORIZATION=f"Bearer {self.admin_access_token}",
            )
        self.assertEqual(response.status_code, 201)

class ProductUpdateTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        cls.admin = User.objects.create_superuser('admin@naver.com','admin_joonyeol','password')
        cls.admin_data = {'email':'admin@naver.com','password':'password'}
        cls.user = User.objects.create_user('test@naver.com','test_joonyeol','password')
        cls.user_data = {'email':'test@naver.com','password':'password'}
        cls.product_data = {'name':'product test name','introduction':'product test introduction','brand':'product test brand'}
        # Faker를 사용해서 랜덤 product를 만듦
        cls.faker = Faker()
        cls.product = Product.objects.create(name=cls.faker.sentence(), introduction=cls.faker.text())

    # admin, user의 access token을 받아옴
    def setUp(self):
        self.admin_access_token = self.client.post(reverse('token_obtain_pair'), self.admin_data).data['access']
        self.user_access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access']

    # 로그인 없이 put요청 보내면 401 확인
    def test_fail_if_not_logged_in(self):
        url = reverse("product_detail", kwargs={"product_id":self.product.id})
        response = self.client.put(url, self.product_data)
        self.assertEqual(response.status_code, 401)

    # 로그인해도 admin이 아닌 user가 put요청 보내면 403 확인
    def test_fail_if_not_admin(self):
        response = self.client.put(
            path=reverse("product_detail", kwargs={"product_id":self.product.id}),
            data=self.product_data,
            HTTP_AUTHORIZATION=f"Bearer {self.user_access_token}",
            )
        self.assertEqual(response.status_code, 403)

    # admin이 put요청 보내면 200 확인
    def test_update_product(self):
        response = self.client.put(
            path=reverse("product_detail", kwargs={"product_id":self.product.id}),
            data=self.product_data,
            HTTP_AUTHORIZATION=f"Bearer {self.admin_access_token}",
            )
        self.assertEqual(response.status_code, 200)

        # product 정보가 수정됐는지 확인
        for key, value in self.product_data.items():
            self.assertEqual(response.data[key], value)
            
class ProductDeleteTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        cls.admin = User.objects.create_superuser('admin@naver.com','admin_joonyeol','password')
        cls.admin_data = {'email':'admin@naver.com','password':'password'}
        cls.user = User.objects.create_user('test@naver.com','test_joonyeol','password')
        cls.user_data = {'email':'test@naver.com','password':'password'}
        # Faker를 사용해서 랜덤 product를 만듦
        cls.faker = Faker()
        cls.product = Product.objects.create(name=cls.faker.sentence(), introduction=cls.faker.text())

    # admin, user의 access token을 받아옴
    def setUp(self):
        self.admin_access_token = self.client.post(reverse('token_obtain_pair'), self.admin_data).data['access']
        self.user_access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access']

    # 로그인 없이 delete 요청 보내면 401 확인
    def test_fail_if_not_logged_in(self):
        url = reverse("product_detail", kwargs={"product_id":self.product.id})
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 401)

    # 로그인해도 admin이 아닌 user가 delete 요청 보내면 403 확인
    def test_fail_if_not_admin(self):
        response = self.client.delete(
            path=reverse("product_detail", kwargs={"product_id":self.product.id}),
            HTTP_AUTHORIZATION=f"Bearer {self.user_access_token}",
            )
        self.assertEqual(response.status_code, 403)

    # admin이 delete요청 보내면 204 확인
    def test_update_product(self):
        response = self.client.delete(
            path=reverse("product_detail", kwargs={"product_id":self.product.id}),
            HTTP_AUTHORIZATION=f"Bearer {self.admin_access_token}",
            )
        self.assertEqual(response.status_code, 204)

 

 


무엇을 새롭게 배웠는지

testcode 작성, Faker 사용방법

 

 

 

728x90
반응형
profile

코딩한걸음

@Joonyeol_Yoon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!