0

我正在为使用库 http.Client 的项目创建一个 API 类。

问题在于单元测试,当我尝试引发错误并使用 assertRaises 对其进行测试时,即使引发了正确的异常,assertRaises 也会失败。

我有一个模块'Foo'。

+---Foo
¦    __init__.py
¦    api.py

在我的__init__.py里面只有我的异常类和装饰器

import errno
import json
import os
import signal
from dataclasses import dataclass
from functools import wraps
from http.client import HTTPMessage, HTTPException
from typing import Optional


@dataclass
class APIResponse:
    method: str
    code: int
    reason: str
    length: Optional[int]
    headers: HTTPMessage
    message: HTTPMessage


@dataclass
class Token:
    access_token: str
    expires_in: int
    token_type: str


@dataclass
class ClientCredentials:
    client_id: str
    client_secret: str
    audience: str
    grant_type: str = "client_credentials"


class ClientCredentialsEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, ClientCredentials):
            return o.__dict__
        return json.JSONEncoder.default(self, o)


class AuthenticationError(HTTPException):
    pass


def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return wraps(func)(wrapper)

    return decorator

我的api.py包含我正在测试身份验证方法以获取访问令牌的 API 类

import http.client
import json
import logging

from Foo import ClientCredentials, Token, ClientCredentialsEncoder, AuthenticationError, timeout
from settings import config, API_HOST, AUTH_DOMAIN


class API:

    def __init__(self, auth0_domain, client_credentials: ClientCredentials):
        self.auth0_domain: str = auth0_domain
        self.client_credentials = client_credentials

        self._session = http.client.HTTPSConnection(self.auth0_domain)

        self.token: Token = self.authenticate()

    @property
    def session(self):
        return self._session

    @timeout(10, "API Takes too long to respond.")
    def api_request(self, method: str, url: str, body: str, headers: dict):
        logging.debug(f"Senging {method} request to {url} for data: {body}...")

        self.session.request(method=method, url=url, body=body, headers=headers)

        res = self.session.getresponse()

        return res

    def authenticate(self) -> Token:
        logging.debug("Getting API authentiation...")
        method = "POST"
        url = "/oauth/token"
        body = json.dumps(
            obj=self.client_credentials,
            cls=ClientCredentialsEncoder
        )
        headers = {'content-type': "application/json"}

        response = self.api_request(method=method, url=url, body=body, headers=headers)
        status = response.status
        reason = response.reason

        data = response.read()

        json_dict = json.loads(data)
        self._session.close()

        if status == 403:
            logging.error(f"Error {status}: {reason}. Authentication fail.")
            raise AuthenticationError(
                f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
            )

        if status == 401:
            logging.error(f"Error {status}: {reason}. Authentication fail.")
            raise AuthenticationError(
                f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
            )

        logging.info(f"Response {status}. Authentication successful!")

        return Token(
            access_token=json_dict['access_token'],
            expires_in=json_dict['expires_in'],
            token_type=json_dict['token_type'],

        )

现在我正在尝试测试我的身份验证,如果它获得了无效的凭据,它是否会引发正确的错误。这是我的测试脚本

import logging
import unittest
from typing import Tuple

from Foo import ClientCredentials, Token, AuthenticationError
from Foo.api import API
from settings import config, API_HOST


class MyTestCase(unittest.TestCase):
    def setUp(self):
        pass

    def test_token(self):
        api = API(*self.good_credentials)

        self.assertIsInstance(api.authenticate(), Token)

    def test_invalid_credentials(self):
        api = API(*self.invalid_credentials)

        # self.assertRaises(AuthenticationError, api.authenticate)
        with self.assertRaises(AuthenticationError) as error_msg:
            api.authenticate()
        self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")

    @unittest.skip("Can't get the right assertRaises")
    def test_invalid_auth_domain(self):
        api = API(*self.invalid_auth_domain)
        with self.assertRaises(TimeoutError) as e:
            api.authenticate()
        self.assertEqual(e, "API Takes too long to respond.")
        # self.assertRaises(TimeoutError, api2.authenticate())

    @property
    def good_credentials(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            client_id=config.read_param('api.value.client-id'),
            # client_id='aw;oieja;wlejf',
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials

    @property
    def invalid_auth_domain(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            client_id=config.read_param('api.value.client-id'),
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials

    @property
    def invalid_credentials(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            # client_id=config.read_param('api.value.client-id'),
            client_id='aw;oieja;wlejf',
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials


if __name__ == '__main__':
    unittest.main()

正如您在我的测试中看到的那样,我已经为 assertRaises 尝试了两种方法:

# Trial 1
with self.assertRaises(AuthenticationError) as e:
    api.authenticate()
self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")

# Trial 2
self.assertRaises(AuthenticationError, api.authenticate)

即使引发了正确的异常,assertRaises 也会失败。 这是我在运行单元测试后从终端获得的日志:

Ran 3 tests in 0.842s

FAILED (errors=1, skipped=1)

Error
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/test/api_authentication_test.py", line 22, in test_invalid_credentials
    api = API(*self.invalid_credentials)
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 17, in __init__
    self.token: Token = self.authenticate()
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 66, in authenticate
    f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
Foo.AuthenticationError: Invalid Credentials. Error: 'access_denied', Description: Unauthorized


Assertion failed

Assertion failed

Process finished with exit code 1

Assertion failed

Assertion failed

从这个问题:即使引发异常,AssertRaises 也会失败

似乎他和我有同样的问题,但他从 2 个不同的路径导入错误,其中对我来说,我很确定我的异常是从我的 Foo 包上的__init__.py导入的。

真的希望有人能帮助我。我真的觉得我在这里忽略了一些东西。

非常感谢!

4

1 回答 1

2

如果您阅读堆栈跟踪,它会在此行引发:

        api = API(*self.invalid_credentials)

这是 the 之前的一行assertRaises,因此不会被它捕获

这是因为API.__init__它本身调用self.authenticate()

于 2020-10-24T19:51:21.790 回答