Django, DRF, JWT 및 OpenApi를 사용하여 적절한 RESTAPI 테스트 생성

이것은

이 글의 요점.

  • pytest를 설치하고 Django 프로젝트
  • 에 설정합니다.
  • 프로필과 사용자 수를 검사하는 테스트 작성(동일해야 함)
  • 사용자가 프로필을 편집할 수 있는지 확인하는 테스트 작성
  • 사용자가 자신의 프로필 이외의 다른 프로필을 편집할 수 없는지 확인하는 테스트를 작성합니다.
  • 테스트에서 발견된 오류 수정
  • Github 작업을 작성하여 master 분기
  • 에 대한 각 라우팅 요청을 검증합니다.
  • Github 작업 중 구축 작업
  • 단계 #1 - pytest 설치 및 구성


    본문에서 나는 당신이 pytest에 대해 잘 알고 있다고 가정합니다.없으면 먼저 읽으십시오this article
    RESTAPI를 위한 테스트 작성부터 시작하겠습니다.우선, 우리는 pytest django (pytest의 포장기를 설치해야 한다. 이것은 우리가 더욱 복잡한 테스트를 쉽게 작성하는 데 도움을 줄 것이다) 를 설치하고 다음 명령을 사용하여 설치해야 한다.
    pip install pytest-django
    
    그리고pytest를 만들어야 합니다.ini 파일, pytest 우리 응용 프로그램에서 테스트를 실행하는 설정으로 사용됩니다.
    [pytest]
    DJANGO_SETTINGS_MODULE = restapi_article.settings
    python_files = tests.py test_*.py *_tests.py
    

    2단계 - 프로필과 사용자 수를 검사하는 테스트를 작성합니다.


    이제 우리는 테스트를 작성하기 시작할 수 있다.이전 글에서 우리는 신호를 사용하여 사용자와 함께 프로필을 만들었기 때문에, DB의 사용자 수와 프로필 수가 같은지 확인하기 위해 테스트를 작성합니다. (새 사용자를 만들고 프로필 수가 같은지 확인하기 바랍니다.)@pytest.mark.django_dbdecorator는 테스트에서 깨끗한 DB를 사용할 수 있도록 합니다. (현재는 기본 SQLite를 사용합니다.)그래서 실행pytest은 우리의 settings.py 를 사용하여 설정을 사용할 것이다
    테스트 후, 모든 것이 테스트를 실행하기 전의 상태로 회복됩니다.첫 번째 간단한 테스트는 이렇습니다.
    import pytest
    
    from django.contrib.auth.models import User
    from .models import Profile
    
    
    @pytest.mark.django_db
    def test_user_create_creates_profile():
        User.objects.create_user('michal', '[email protected]', 'michalpassword')
        assert Profile.objects.count() == 1
        assert User.objects.count() == 1
    

    3단계 - 사용자가 프로필을 편집할 수 있는지 확인하기 위한 테스트 작성


    현재, 우리는 사용자가 개인 자료를 업데이트할 수 있는지 테스트해야 한다.따라서 테스트를 작성합시다. (앞의 테스트를 테스트 개요 파일로 확장할 것입니다.)수정된 테스트 용례는 다음과 같습니다.
  • 사용자를 만들고 DB에서 프로필을 만듭니다
  • 생성된 프로필의 사용자 필드가 올바른 사용자 URL에 연결됩니다
  • 다음에 생성된 사용자의 영패를 가져와 스크립트에 저장하여 다음 API 호출을 허가합니다.
  • 이 영패를 사용하면 일부 데이터로 프로필을 채울 것입니다.
  • PUT API 호출에 대한 응답에 이전 요청과 동일한 데이터가 포함되어 있는지 확인합니다.
  • 우선, 우리는 ApiClient 에서 사용할 집게 rest_framework.test 를 추가해야 한다.추가 정보 tests.py
    @pytest.fixture
    def api_client():
        from rest_framework.test import APIClient
        return APIClient()
    
    현재 우리는 이 클러치를 사용하여 이전의 테스트를 확장할 수 있다.테스트 이름을 변경했지만 사용자 수를 확인합니다.
    @pytest.mark.django_db
    def test_user_can_update_his_profile(api_client):
        user = User.objects.create_user('michal', '[email protected]', "michalpassword")
        assert Profile.objects.count() == 1
        assert User.objects.count() == 1
    
        profile_url = reverse('profile-detail', args=[user.id])
        user_url = reverse('user-detail', args=[user.id])
    
        # check that profile was created for created user
        response = api_client.get(profile_url)
        assert response.status_code == 200
        assert response.data['user'].endswith(user_url)
    
        #create bio
        bio_data = {
            "user": response.data['user'],
            "bio": "This is test user",
            "location": "Wroclaw"
        }
        #create login data as user.password contains now encrypted string
        login_data = {
            "username": user.username,
            "password": "michalpassword"
        }
    
        # get token
        token_url = reverse('token_obtain_pair')
        token = api_client.post(token_url, login_data, format='json')
        # check that access token was sent in response
        assert token.data['access'] is not None
        # add http authorization header with Bearer prefix
        api_client.credentials(HTTP_AUTHORIZATION='Bearer ' + token.data['access'])
        # update profile
        response = api_client.put(profile_url, bio_data, format='json')
        # validate response
        assert response.status_code == 200
        assert response.data['bio'] == bio_data['bio']
        assert response.data['location'] == bio_data['location']
    
    이제 터미널 프로젝트 루트에 pytest 를 입력하여 테스트를 실행할 수 있습니다.우리의 테스트는 반드시 통과해야 한다. 어떠한 잘못도 없다.

    4단계 - 사용자가 자신의 프로필 이외의 다른 프로필을 편집할 수 없는지 확인하는 테스트를 작성합니다.


    지금까지 우리의 응용 프로그램은 아무런 문제가 없었다.단, 다음 테스트에서 사용자 이외의 다른 프로필을 채워 보겠습니다.잘못된 JWT 영패로 다른 개요 파일을 업데이트할 때 오류 403을 볼 수 있습니다.다른 사람의 개인 정보를 변경할 수는 없지만...
    @pytest.mark.django_db
    def test_user_should_not_be_able_to_update_other_profile(api_client):
        first_user = User.objects.create_user('michal', '[email protected]', "michalpassword")
        second_user = User.objects.create_user('michal2', '[email protected]', "michalpassword2")
        assert Profile.objects.count() == User.objects.count()
    
        #get token for first_user
        token_url = reverse('token_obtain_pair')
        login_data = {
            "username": first_user.username,
            "password": "michalpassword"
        }
        token = api_client.post(token_url, login_data, format='json')
        api_client.credentials(HTTP_AUTHORIZATION='Bearer ' + token.data['access'])
        # now update second_user Profile with first_user token
        profile_url = reverse('profile-detail', args=[second_user.id])
        response = api_client.get(profile_url)
        bio_data = {
            "user": response.data['user'],
            "bio": "This is test user",
            "location": "Wroclaw"
        }
        response = api_client.put(profile_url, bio_data, format='json')
        assert response.status_code == 403
    
    pytest로 이 테스트를 다시 실행합시다. 이제 두 번째 테스트가 실패했습니다. 왜냐하면 우리는 우리를 제외한 다른 프로필을 업데이트할 수 있기 때문입니다!
    FAILED restapi/tests.py::test_user_should_not_be_able_to_update_other_profile - assert 200 == 403
    

    단계 5 - 테스트에서 발견된 오류 수정


    코드에서 이 버그를 복구해야 합니다. (이것은 보안 버그입니다.)우선, 우리는 permissions.py 파일을 추가하고 그 중에서 적당한 권한 논리를 만듭니다.
    from rest_framework import permissions
    
    
    class IsProperUserOrReadOnly(permissions.BasePermission):
        """
        Custom permission to only allow owners of an object to edit it.
        """
        def has_object_permission(self, request, view, obj):
            # Read permissions are allowed to any request,
            # so we'll always allow GET, HEAD or OPTIONS requests.
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # Write permissions are only allowed to the User linked with the Profile.
            return obj.user == request.user
    
    그리고 우리는 이 논리를 포함하기 위해 우리의 ProfileViewSet 에서 views.py 를 변경할 수 있다.새로 만든 권한을 가져오는 것을 잊지 마십시오.
    from .permissions import IsProperUserOrReadOnly
    # some code here
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsProperUserOrReadOnly]
    
    지금 우리는 pytest로 다시 테스트를 실행할 수 있다. 우리는 반드시 테스트를 통과해야 한다.간단하게:)

    6단계- Github 작업을 생성하여 마스터 브랜치에 대한 각 pull 요청을 확인합니다.


    테스트가 통과되면 GitHub 및 GitHub 작업을 설정할 수 있습니다.저장소가 모든 컨텐츠를 직접 master 지점으로 전송하는 것을 차단합니다.
    "설정"-> "분기"로 들어가 초보자를 위한 분기 정책을 추가할 수 있습니다.분기 이름 모드에서 "master"를 입력하고 "합병하기 전에 심사 요청"을 선택하십시오.
    그리고 Github repo의 작업으로 이동하여 Python 응용 프로그램을 추가합니다. 이것은 첫 페이지에서 건의해야 합니다.

    이것은 디렉터리 python-app.yml 에 yaml 파일 .github/workflows/ 을 추가합니다.우리의 파일은 다음과 같이 해야 한다.
    # This workflow will install Python dependencies, run tests and lint with a single version of Python
    # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
    
    name: Python application
    
    on:
      pull_request:
        branches: [ master ]
    
    jobs:
      build:
    
        runs-on: ubuntu-latest
    
        steps:
        - uses: actions/checkout@v2
        - name: Set up Python 3.8
          uses: actions/setup-python@v2
          with:
            python-version: 3.8
        - name: Install dependencies
          run: |
            python -m pip install --upgrade pip
            pip install flake8 pytest
            if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
        - name: Lint with flake8
          run: |
            # stop the build if there are Python syntax errors or undefined names
            flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
            # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
            flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
        - name: Test with pytest
          run: |
            pytest
    

    단계 8 - Github 작업에서 구문 사용하기


    로컬에서 만든 지점에서 테스트를 중단하고 다음을 수행합니다.
    git pull
    git checkout -b feature/broken-test
    
    tests.py의 마지막 줄을 403에서 200으로 변경합니다.
    assert response.status_code == 200
    
    이제 코드를 추가, 제출 및 푸시하고 Github 작업을 트리거하는 요청을 만들 수 있습니다.이 코드를 분기로 전송한 후 Github 저장소 페이지에서 Pull 요청을 작성합니다.
    "pytest 테스트"단계의 오류를 GitHub 작업에서 보았어야 합니다.

    로그는 다음과 같이 표시됩니다.
    >       assert response.status_code == 200
    E       assert 403 == 200
    E        +  where 403 = <Response status_code=403, "application/json">.status_code
    
    restapi/tests.py:79: AssertionError
    ------------------------------ Captured log call -------------------------------
    WARNING  django.request:log.py:224 Forbidden: /api/v1/profile/2/
    
    이러한 오류는 Pull 요청 뷰로 채워지고 "모든 검사 실패"를 표시합니다.이것은 변경 사항을 master 지점에 통합하지 말라는 좋은 경고입니다.
    tests.py의 마지막 줄을 복구하고 상태 403을 다시 기대하며 코드를 다시 전송합니다.이제 테스트가 동작을 전달하고 있는 것을 보아야 합니다. 이제 마스터에 안전하게 통합할 수 있습니다. (pull 요청도 보기를 업데이트합니다.)

    다음:

  • Heroku
  • 에 배포
  • Dockerization
  • 좋은 웹페이지 즐겨찾기