Тесты в Python класса, который запрашивает REST API - PullRequest
0 голосов
/ 02 апреля 2020

Я работаю в настольном клиентском приложении, которое обращается к API REST OneDrive (Microsoft Graph) для загрузки и выгрузки файлов. Класс Onedrive проверяет, нужно ли обновлять маркер доступа перед каждым запросом.

Должен ли я тестировать этот класс модулем? Есть ли способ смоделировать эти запросы?

Вот исходный код класса:

import json
from datetime import datetime, timedelta
from urllib.request import Request, urlopen

TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
DRIVE_BY_ID_URL = 'https://graph.microsoft.com/v1.0/drives/'
CLIENT_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

class Onedrive:

    def __init__(self, access_token, refresh_token, expire_date):
        self._access_token = access_token
        self._refresh_token = refresh_token
        self._expire_date = expire_date
        self.timeout = 5

    def download(self, drive_id, file_id, filename):
        self._renew_token()
        url = f'{DRIVE_BY_ID_URL}/{drive_id}/items/{file_id}/content?AVOverride=1'
        with open(filename, 'wb+') as fp:
            headers = {'Authorization': self._access_token}
            request = Request(url, headers=headers)
            with urlopen(request, timeout=self.timeout) as response:
                fp.write(response.read())

    def upload(self, local_path, parent_drive_id, parent_id, filename):
        self._renew_token()
        url = f'{DRIVE_BY_ID_URL}/{parent_drive_id}/items/{parent_id}:/{filename}:/content'
        headers = {'Authorization': self._access_token, "Content-Type": "application/octet-stream"}
        with open(local_path, 'rb') as fp:
            data = fp.read()
            request = Request(url, headers=headers, data=data, method='PUT')
            with urlopen(request, timeout=self.timeout) as response:
                return json.load(response)

    def _renew_token(self):
        if self._expire_date <= datetime.now():
            body = f'client_id={CLIENT_ID}&refresh_token={self._refresh_token}&grant_type=refresh_token'
            self._acquire_token(body)

    def _acquire_token(self, body):
        now = datetime.now()
        request = Request(TOKEN_URL, data=body.encode(), method='POST')
        with urlopen(request, timeout=self.timeout) as response:
            data = json.load(response)
            self._access_token = data['access_token']
            self._refresh_token = data['refresh_token']
            expire = data['expires_in']
            self._expire_date = now + timedelta(seconds=int(expire))

1 Ответ

0 голосов
/ 02 апреля 2020

здесь указано, как вы можете улучшить код выше:

  1. Сначала на конструкторе или init вам не нужны access_token, refresh_token и дата истечения срока действия. Чтобы упростить любые модули, использующие этот класс, все связанные с токенами вещи обрабатываются внутри OneDrive Class. Класс будет просто получать токен каждый раз, когда вы используете методы загрузки или загрузки. это удалит self._renew_token() при любых других методах.

  2. Вы можете извлечь строку ниже:
request = Request(url, headers=headers)
with urlopen(request, timeout=self.timeout) as response:

Которая использовала несколько методов в своей собственной функции. это будет соответствовать одной ответственности, поэтому позже новый метод, называемый request, будет отвечать за обработку HTTP-запроса, а другой метод, использующий этот метод, заботится только о возврате. С этим дизайном это также возможно позже в будущем, если вы решите изменить библиотеку на что-то другое, например Requests или AIOHTTP, вам нужно всего лишь изменить этот метод.

Для тестирования теперь вы можете использовать только методы request, исправив urllib, и теперь для upload или download есть несколько подходов, которые вы можете сделать, вы можете высмеять request или все же просто исправить urllib, который используется внутри метода запроса. Я рекомендую вам попробовать модульный тест (проверить все модули и смоделировать другой модуль, на который полагается этот модуль) и последующий интеграционный тест (все модули соединяются с другим модулем)

onedrive.py

import json
from datetime import datetime, timedelta
from urllib.request import Request, urlopen

TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
DRIVE_BY_ID_URL = "https://graph.microsoft.com/v1.0/drives/"
CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"


class Onedrive:
    def __init__(self):
        access_token, refresh_token, expire_date = self._renew_token()
        self._access_token = access_token
        self._refresh_token = refresh_token
        self._expire_date = expire_date

    @staticmethod
    def request(url, headers, method="GET", data=None):
        """
            this function handle the actual HTTP request to actual endpoint
            behind by providing url, headers, method and data
        """
        request = Request(url, headers=headers, data=data, method=method)
        with urlopen(request, timeout=5) as response:
            return response

    def download(self, drive_id, file_id, filename):
        url = f"{DRIVE_BY_ID_URL}/{drive_id}/items/{file_id}/content?AVOverride=1"
        with open(filename, "wb+") as fp:
            headers = {"Authorization": self._access_token}
            response = self.request(url, headers)
            fp.write(response.read())

    def upload(self, local_path, parent_drive_id, parent_id, filename):
        url = f"{DRIVE_BY_ID_URL}/{parent_drive_id}/items/{parent_id}:/{filename}:/content"
        headers = {
            "Authorization": self._access_token,
            "Content-Type": "application/octet-stream",
        }
        with open(local_path, "rb") as fp:
            response = self.request(
                url=url, headers=headers, data=fp.read(), method="PUT"
            )
            return json.load(response)

    def _renew_token(self):
        access_token = self._access_token
        refresh_token = self._refresh_token
        expire_date = self._expire_date

        if self._expire_date <= datetime.now():
            body = f"client_id={CLIENT_ID}&refresh_token={self._refresh_token}&grant_type=refresh_token"
            access_token, refresh_token, expire_date = self._acquire_token(body)
        return access_token, refresh_token, expire_date

    def _acquire_token(self, body):
        now = datetime.now()
        response = self.request(
            url=TOKEN_URL, headers=None, data=body.encode(), method="POST"
        )
        data = json.load(response)

        access_token = data["access_token"]
        refresh_token = data["refresh_token"]
        expire = data["expires_in"]
        expire_date = now + timedelta(seconds=int(expire))

        return access_token, refresh_token, expire_date

test_onedrive.py

from unittest import TestCase
from unittest.mock import patch, MagicMock
from example import Onedrive


class TestOneDrive(TestCase):
    @patch("urllib.request.urlopen")
    def test_request(self, mock_urlopen):
        mock = MagicMock()
        mock.getcode.return_value = 200
        mock.read.return_value = "some-contents"
        mock.__enter__.return_value = mock
        mock_urlopen.return_value = mock

        response = Onedrive.request(
            url="https://login.microsoftonline.com/common/oauth2/v2.0/token",
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            method="POST",
            data={"data": "some_data"},
        )

        self.assertEqual(response.getcode(), 200)
        self.assertEqual(response.read(), "some-contents")

    @patch("urllib.request.urlopen")
    def test_download(self, mock_urlopen):
        mock = MagicMock()
        mock.getcode.return_value = 200
        mock.read.return_value = "some-contents"
        mock.__enter__.return_value = mock
        mock_urlopen.return_value = mock

        Onedrive().download(
            drive_id="drive-id", file_id="file_id", filename="some_filename"
        )

        # now you just check whether the file actual written

ссылка: https://docs.python.org/3/howto/urllib2.html https://docs.python.org/3/library/unittest.mock.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...