setUp

 

  • setUp : 모든 test 메소드 이전에 실행한다.
  • tearDown : 모든 test 메소드 마지막에 실행한다.

 

class LoginUserTest(APITestCase):
    def setUp(self):
        self.data = {'username':'John', 'password':'password'}
        # UserManager의 create_user 함수
        self.user = User.objects.create_user('John', 'password')

    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']
        # 토큰이 get으로 돌아옴
        response = self.client.get(
            path=reverse('user_view'),
            # 헤더에 AUTHORIZATION 담아서 bearer에 토큰 보내줌
            HTTP_AUTHORIZATION=f'Bearer {access_token}'
        )
        # 가입한 username이 로그인한 username과 동일한지
        self.assertEqual(response.data['username'], self.data['username'])

 

 

※ 여기서 알아야 할 것 ※

access토큰을 받아와 AUTHORIZATION 헤더에 넣어주는 방법에 대해 알아야 합니다.

 

 

 

@classmethod (팩토리 메소드)

  • 정적 메소드 (클래스에서 직접 접근할 수 있는 메소드)

         * 파이썬에서는 정적 메소드임에도 인스턴스에 접근이 가능하다.

  • 인스턴스를 생성하지 않고 클래스 자체에 대해 작업할 수 있다.
  • 첫 번째 인자로 클래스를 받는다.
from datetime import date

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    # 첫번째 인자는 본인 클래스
    def fromBirthYear(cls, name, birthYear):
        # cls = class Person
        return cls(name, date.today().year - birthYear)
    
    def display(self):
        print(self.name + "'s age is: " + str(self.age))


person = Person("Adam", 19)
person.display()

# 인스턴스가 없어도 classmethod는 바로 실행할 수 있음
person1 = Person.fromBirthYear('John', 1985)
person1.display()

 

 

 

@staticmethod

  • 정적 메소드
  • 추가되는 인자가 없다.
  • 단순히 편리를 위한 메소드이다.
from datetime import date

# 클래스 밖에서 선언하나 staticmethod로 선언하나 차이가 없다
def isAdult(age):
        return age > 18

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    @staticmethod
    def isAdult(age):
        return age > 18

 

'Python > Django' 카테고리의 다른 글

[DRF] 17. Faker  (0) 2023.04.30
[DRF] 16. setUpTestData  (0) 2023.04.30
[DRF] 14.5 테스트 코드 종류  (0) 2023.04.27
[DRF] 14. 테스트 코드란?  (0) 2023.04.27
[DRF] 13. 개인페이지 (Q객체, F객체)  (0) 2023.04.27
 

테스트 종류

 

단위 테스트(Unit Test)

  • 소프트웨어의 각 기능이나 모듈이 의도한 대로 동작하는지를 검증하는 테스트
  • 소프트웨어 변경 시 예상치 못한 부작용을 최소화
  • 코드의 작은 부분(함수, 메서드, 클래스 등)을 개별적으로 검증
  • 빠른 시간 안에 수행할 수 있어 개발 과정에서 가장 많이 사용됨
  • 대표적인 단위 테스트 프레임워크로는 JUnit, pytest, unittest 등이 있음
  • 모든 테스트가 서로 독립적(sateless)

 

통합 테스트(Integration Test)

  • 각 기능이 모두 연결되어 잘 작동하는지를 검증하는 테스트
  • 각 모듈이 단위 테스트에서 검증된 후, 모듈들 간의 상호작용을 검증함
  • 대표적으로 스프링 프레임워크에서 사용되는 스프링 부트 테스트가 있음

 

 

E2E(End-to-End) 테스트

  • 전체 시스템을 대상(DB, 서버, 클라이언트 등)으로 수행하는 테스트
  • 사용자의 관점에서 시스템이 어떻게 동작하는지를 검증
  • 사용자가 시스템을 사용할 때 발생할 수 있는 문제를 대상으로 검증
  • 자동화가 어려우므로 많은 시간과 비용이 소요

 

 

 

통합 테스트와 E2E 테스트 차이

통합 테스트 E2E 테스트
개별 모듈의 동작 확인에 중점 사용자 경험에 초점
모듈 간의 상호작용을 검증 전체 시스템을 대상으로 수행

 

 

 

시스템 테스트(System Test)

  • 전체 시스템의 기능, 성능, 안전성을 검증
  • E2E 테스트를 포괄하는 테스트
  • 시스템이 예상한대로 동작하며 사용자 요구사항을 충족시키는지를 확인함
  • 수행 방식에는 수동 테스트와 자동화된 테스트가 있음

'Python > Django' 카테고리의 다른 글

[DRF] 16. setUpTestData  (0) 2023.04.30
[DRF] 15. setUp, @classmethod, @staticmethod  (0) 2023.04.27
[DRF] 14. 테스트 코드란?  (0) 2023.04.27
[DRF] 13. 개인페이지 (Q객체, F객체)  (0) 2023.04.27
[DRF] 12. 좋아요, 팔로우  (1) 2023.04.26

테스트 코드란?

    작성한 코드들이 원하는 값을 주는지 확인하는 코드

 

 

사용하는 이유

    1.버그를 쉽고 빠르게 찾을 수 있다.

    2. 코드가 얼마나 안전한지 확인할 수 있다.

    3. 코드의 복잡도를 낮출 수 있다.

    4. extreme programming (XP) 개념에서 중요하다.

          XP는 테스트 주도 개발(TDD) 방법을 사용한다. 지속적 통합(CI)과 지속적 배포(CD)를 지원하여 개발자들이 코드 변경 사항을 빠르게 수정할 수 있도록 하는데 목적이 있다.

    5. documentation화 제공한다.

          타인이 코드의 동작을 쉽게 이해하고 코드를 변경하면 문서화를 자동으로 업데이트 할 수 있다.

    6. performance(성능)를 체크한다.

          (ex, 시간이 얼마나 걸리는지)

 

 

 

 

 

Test Driven Development (테스트 주도 개발) 순서

   실패하는  테스트 코드 ->  테스트 코드를 성공시키기 위한 코드 작성 ->  refactoring (효율, 가독성)

 

 

사용 예시

py manage.py test <앱 이름>
# 앱 이름이 없으면 전체 test 코드를 실행
# 앱 이름이 있으면 앱의 test 코드만 실행

 

 

(app - test.py)

from django.test import TestCase


class TestView(TestCase):
    def test_two_is_three(self):
        # 2와 3이 같은지 확인
        self.assertEqual(2, 3)

 

 

https://www.django-rest-framework.org/api-guide/testing/

https://docs.djangoproject.com/en/4.2/topics/testing/overview/

https://docs.python.org/3/library/unittest.html#module-unittest

 

 

 

로그인 테스트 - 1

(app - test.py)

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase

class UserRegistrationTest(APITestCase):
    def test_registration(self):
        # reverse : 해당 URL 패턴을 역으로 해석하여 URL 문자열을 반환하는 함수
        url = reverse('user_view')
        user_data = {
            "username" : "testuser",
            "fullname" : "테스터",
            "email" : "test@testuser.com",
            "password" : "password",
        }
        # 클라이언트로 post를 보냄
        # url로 user_data를 보냄
        response = self.client.post(url, user_data)
        # status 코드가 200인지 확인
        print(response.data)
        self.assertEqual(response.status_code, 200)

    def test_login(self):
        url = reverse('token_obtain_pair')
        user_data = {
            "username" : "testuser",
            "fullname" : "테스터",
            "email" : "test@testuser.com",
            "password" : "password",
        }
        response = self.client.post(url, user_data)
        print(response.data)
        self.assertEqual(response.status_code, 200)

    #{'detail': ErrorDetail(string='No active account found with the given credentials', code='no_active_account')}

 위에서 유저를 만들었는데 밑에서 로그인하니 에러가 난다.

 

이유

  • test 의 메소드를 실행할 때마다 DB 데이터를 초기화한다.
  • 테스트 코드의 실행 순서는 보장 되지 않는다.

전체 게시글 보기 수정

  • SerializerMethodField 사용 방법

 

https://www.django-rest-framework.org/api-guide/relations/

 

 

(app - serializers.py) 수정/추가 하기

class TodoListSerialize(serializers.ModelSerializer):
    user = serializers.SerializerMethodField()
    like_count = serializers.SerializerMethodField()
    comment_count = serializers.SerializerMethodField()

    def get_user(self, obj):
        return obj.user.email
    
    def get_like_count(self, obj):
        return obj.like.count()
    
    def get_comment_count(self, obj):
        return obj.comment_set.count()
    
    class Meta:
        model = Todos
        fields = ('title', 'user', 'comment_count', 'like_count')

 

 

 

프로필 페이지

  • 시리얼라이즈의 StringRelatedField 필드
  • related_name 혹은 set으로 참조

(app - urls.py)

urlpatterns = [
    path('<int:user_id>/profile/', views.ProfileView.as_view(), name='profile_view'),
]

 

(app - serializers.py)

class UserProfileSerializer(serializers.ModelSerializer):
    # 해당 모델의 __str__() 메서드를 호출하여 문자열로 표현된 관련 객체를 반환
    # PrimaryKeyRelatedField 는 아이디로 보여줌 // read_only=True
    follows = serializers.StringRelatedField(many=True)
    following = serializers.StringRelatedField(many=True)
    #related_name이나 set으로 참조
    todos_set = TodoListSerialize(many=True)
    likey = TodoListSerialize(many=True)

    class Meta:
        model = Users
        fields = ('id', 'email', 'follows', 'following', 'todos_set', 'likey')

 

(app - views.py)

class ProfileView (APIView):
    def get (self, request, user_id):
        user = get_object_or_404(Users, id=user_id)
        serialize = UserProfileSerializer(user)
        return Response(serialize.data)

 

 

 

Feed 페이지

  • Q 객체를 사용해 쿼리를 조작할 수 있다.

(app - urls.py)

urlpatterns = [
    path('feed/', views.FeedView.as_view(), name='feed_view'),
    ]

 

 

(app - views.py)

from django.db.models.query_utils import Q

class FeedView (APIView):
    permission_classes = [IsAuthenticated]

    def get (self, request):
        # Q 객체를 사용하여 쿼리 조건을 생성
        q = Q()
        for user in request.user.follows.all():
            # add 메서드를 사용해 Q 객체를 하나씩 추가
            # 팔로우가 비대칭이므로(한쪽만 팔로우 할 경우) OR를 조건으로 Todos 모델 인스턴스를 가져옴
            q.add(Q(user=user),q.OR)
        feeds = Todos.objects.filter(q)
        serialize = TodoListSerialize(feeds, many =True) 
        return Response(serialize.data)

 

 

 

 

 

 

Q 객체

  • 주로 filter( )와 함께 사용한다.
  • 복잡한 쿼리를 처리할 때 Q 객체를 사용한다.
  • Q 객체는 and( & ), or ( | ), xor ( ^ ) 등의 비교연산자와 함께 쓰인다.
  • 장고 ORM 에서 쿼리문처럼 사용한다.

         *장고 ORM 이란? 객체와 DB 데이터를 매핑(Mapping) 하는 역할. 즉,  SQL 쿼리를 작성하지 않고 장고에서 자동으로 SQL문을 생성하는 것을 말한다. 장점으로는 SQL문 문법이 통일성을 갖게 된다는 점이 있다.

from django.db.models import Q

Q(question__startswith="Who") | ~Q(pub_date__year=2005

https://docs.djangoproject.com/en/4.2/ref/models/querysets/#django.db.models.Q

 

 

 

 

 

F 객체

  • 모델의 필드 또는 어노테이트 될 열의 값(F 객체를 사용해 계산한 결과 값)을 나타낸다.
  • 연산을 할 때 사용한다.
  • 연산에 해당하는 쿼리를 생성한다.
  • 데이터베이스에서 연산을 처리한다.

※ 모델을 저장한 후에도 save( )를 실행할 때 마다 적용된다.

from django.db.models import F

reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()

https://docs.djangoproject.com/en/4.2/ref/models/expressions/#django.db.models.F

'Python > Django' 카테고리의 다른 글

[DRF] 14.5 테스트 코드 종류  (0) 2023.04.27
[DRF] 14. 테스트 코드란?  (0) 2023.04.27
[DRF] 12. 좋아요, 팔로우  (1) 2023.04.26
[DRF] 11. 댓글 API(두 개의 모델 참조)  (0) 2023.04.26
[DRF] 10. 게시글 API  (0) 2023.04.26

좋아요 / Todo Done

  • Todos 모델의 like에 로그인한 유저가 없으면 저장, 있으면 제거합니다.
  • Todos 모델의 is_complete 필드를 True로 바꾼다.

 

(app - models.py) 추가하기

class Todos(models.Model):
    like = models.ManyToManyField(Users, related_name='likey')

 

(app - urls.py)

urlpatterns = [
    path('<int:todo_id>/like/', views.LikeView.as_view(), name='like_this'),
    path('<int:todo_id>/done/', views.DoneView.as_view(), name='done_this'),
]

 

(app - views.py)

class LikeView(APIView):
    def post(self, request, todo_id):
        todo = get_object_or_404(Todos, id=todo_id)
        print(todo.like)
        if request.user in todo.like.all():
            todo.like.remove(request.user)
            return Response('헤어져요', status=status.HTTP_200_OK)
        else:
            todo.like.add(request.user)
            return Response('좋아요', status=status.HTTP_200_OK)
    
class DoneView(APIView):
    def post(self, request, todo_id):
        todo = get_object_or_404(Todos, id=todo_id)
        if request.user == todo.user:
            todo.is_complete = True
            todo.save()
            return Response('할 일 완료', status=status.HTTP_200_OK)
        else:
            return Response({'message':'권한이 없습니다'},status=status.HTTP_400_BAD_REQUEST)

 

 

 

 

 

 

팔로우

  • 자신은 팔로우 하지 않는다.
  • 팔로우, 언팔로우 모두 같은 모델을 참조해야한다.

(app - urls.py)

urlpatterns = [
    path('<int:user_id>/follow/', views.FollowView.as_view(), name='follow_view'),
]

 

(app - models.py)

    # 한쪽이 팔로우 하면 다른 한쪽도 팔로우를 걸지 않도록
    # 대칭(symmetrical) 설정을 False로 만듬
    follows = models.ManyToManyField('self', symmetrical=False, related_name='following', blank=True)

 

(app - views.py)

class FollowView(APIView):
    def post(self, request, user_id):
        other = get_object_or_404(Users, id=user_id)
        me = request.user
        # 팔로우 하려는 사람이 내가 아닐 때
        if me != other:
            if other not in me.follows.all():
                me.follows.add(other)
                return Response('팔로우', status=status.HTTP_200_OK)
            else:
                me.follows.remove(other)
                return Response('언팔로우', status=status.HTTP_200_OK)
        else:
            return Response({'message':'나를 팔로우 할 수 없습니다'}, status=status.HTTP_400_BAD_REQUEST)

 

+ Recent posts