PyTest对@singledispatch和@typechecked没有引发预期错误。

huangapple go评论69阅读模式
英文:

PyTest for @singledispatch and @typechecked not raising expected error

问题

目标: 成功通过test_score_not_implemented_error()中的NotImplementedError测试用例。

@singledispatchdef score()的目的是在提供给count_negcount_pos的参数不匹配Tuple[int, int]Tuple[List[int], List[int]]时引发NotImplementedError异常。

我想要通过test_score_not_implemented_error()来测试异常处理。

然而,出乎意料的是,在其他多态函数上实现了@typechecked后,我遇到了错误。

我对需要多态函数的方法有信心,我的测试函数具有适当的测试用例。我怀疑问题出在我如何实现def score()的多态函数上。

调整:
从多态函数中删除@typechecked会引发以下错误:

FAILED tests/test_tps.py::test_score_not_implemented_error[0-01] - TypeError: can only concatenate str (not "int") to str
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg3-count_pos3] - TypeError: unsupported operand type(s) for +: 'int' and 'str'
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg4-count_pos4] - TypeError: unsupported operand type(s) for +: 'int' and 'str'
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg5-count_pos5] - TypeError: unsupported operand type(s) for +: 'int' and 'str'

tests/test_score.py

from typeguard import typechecked
from functools import singledispatch

import pytest
from pytest_cases import parametrize
from typing import Any, List, Tuple, Type, Union


@singledispatch
def score(count_neg: Any, count_pos: Any) -> None:
    raise NotImplementedError(f'{type(count_neg)} and or {type(count_pos)} are not supported.')


@score.register(int)
@typechecked
def score_int(count_neg: int, count_pos: int) -> float:
    return round(100 * count_pos / (count_pos + count_neg), 1)


@score.register(list)
@typechecked
def score_list(count_neg: List[int], count_pos: List[int]) -> float:
    return round(100 * sum(count_pos) / (sum(count_pos) + sum(count_neg)), 1)


@parametrize('count_neg, count_pos',
             [('0', 0),
              (0, '0'),
              ('0', '0'),
              (['0'], [0]),
              ([0], ['0']),
              (['0'], ['0']),
              (None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
                                     count_pos: Union[str, int, List[str], List[int], None],
                                     error: Type[BaseException] = NotImplementedError):
    with pytest.raises(error) as exc_info:
        score(count_neg, count_pos)

    assert exc_info.type is error

跟踪:

(venv) me@laptop:~/BitBucket/project $ python -m pytest tests/test_score.py 
=====================================================================================================================
test session starts
=====================================================================================================================
platform linux -- Python 3.9.16, pytest-7.4.0, pluggy-1.0.0
rootdir: /home/danielbell/BitBucket/pdl1-lung
plugins: hydra-core-1.3.2, typeguard-3.0.2, mock-3.11.1, cases-3.6.13, dvc-3.2.3, anyio-3.5.0
collected 7 items                                                                                                                                                                                                                                             

tests/test_tps.py .F.FFF.                                                                                                                                                                                                                               [100%]

==========================================================================================================================
FAILURES
==========================================================================================================================
___________________________________________________________________________________________________________ test_score_not_implemented_error[0-01] ____________________________________________________________________________________________________________

count_neg = 0, count_pos = '0', error = <class 'NotImplementedError'>

    @parametrize('count_neg, count_pos',
                 [('0', 0),
                  (0, '0'),
                  ('0', '0'),
                  (['0'], [0]),
                  ([0], ['0']),
                  (['0'], ['0']),
                  (None, None)])
    def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
                                         count_pos: Union[str, int, List[str], List[int], None],
                                         error: Type[BaseException] = NotImplementedError):
        with pytest.raises(error) as exc_info:
>           score(count_neg, count_pos)

tests/test_tps.py:38: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../miniconda3/envs/pdl1lung/lib/python3.9/functools.py:888: in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
tests/test_tps.py:16: in score_int
    def score_int(count_neg: int, count_pos: int) -> float:
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_functions.py:113: in check_argument_types
    check_type_internal(value, expected_type, memo=memo)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

value = '0', annotation = <class 'int'>, memo = <typeguard.CallMemo object at 0x7f591213ccc0>

    def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -> None:
        """
        Check that the given object is compatible with the given type annotation.

        This function should only be used by type checker callables. Applications should use
        :func:`~.check_type` instead.

        :param value:

<details>
<summary>英文:</summary>

Goal: Successfully pass test cases, for `NotImplementedError` in `test_score_not_implemented_error()`.

The purpose of `@singledispatch` `def score()` is to raise `NotImplementedError`, if when the provided arguments for `count_neg` and `count_pos` do not match `Tuple[int, int]` nor `Tuple[List[int], List[int]]`.

I want to test for this exception handling via. `test_score_not_implemented_error()`.

However, unexpectedly, I get an error for having implemented `@typechecked` on the other polymorphic functions.

I&#39;m confident in my approach to needing a polymorphic function and my test function has the appropriate test cases. I suspect the issue lies in how I&#39;ve implemented `def score()`&#39;s polymorphic functions.

Tweak:
Removing `@typechecked` from polymorphic functions throws:

FAILED tests/test_tps.py::test_score_not_implemented_error[0-01] - TypeError: can only concatenate str (not "int") to str
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg3-count_pos3] - TypeError: unsupported operand type(s) for +: 'int' and 'str'
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg4-count_pos4] - TypeError: unsupported operand type(s) for +: 'int' and 'str'
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg5-count_pos5] - TypeError: unsupported operand type(s) for +: 'int' and 'str'


---
`tests/test_score.py`:

from typeguard import typechecked
from functools import singledispatch

import pytest
from pytest_cases import parametrize
from typing import Any, List, Tuple, Type, Union

@singledispatch
def score(count_neg: Any, count_pos: Any) -> None:
raise NotImplementedError(f'{type(count_neg)} and or {type(count_pos)} are not supported.')

@score.register(int)
@typechecked
def score_int(count_neg: int, count_pos: int) -> float:
return round(100 * count_pos / (count_pos + count_neg), 1)

@score.register(list)
@typechecked
def score_list(count_neg: List[int], count_pos: List[int]) -> float:
return round(100 * sum(count_pos) / (sum(count_pos) + sum(count_neg)), 1)

@parametrize('count_neg, count_pos',
[('0', 0),
(0, '0'),
('0', '0'),
(['0'], [0]),
([0], ['0']),
(['0'], ['0']),
(None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
count_pos: Union[str, int, List[str], List[int], None],
error: Type[BaseException] = NotImplementedError):
with pytest.raises(error) as exc_info:
score(count_neg, count_pos)

assert exc_info.type is error

Traceback:

(venv) me@laptop:~/BitBucket/project $ python -m pytest tests/test_score.py
===================================================================================================================== test session starts =====================================================================================================================
platform linux -- Python 3.9.16, pytest-7.4.0, pluggy-1.0.0
rootdir: /home/danielbell/BitBucket/pdl1-lung
plugins: hydra-core-1.3.2, typeguard-3.0.2, mock-3.11.1, cases-3.6.13, dvc-3.2.3, anyio-3.5.0
collected 7 items

tests/test_tps.py .F.FFF. [100%]

========================================================================================================================== FAILURES ===========================================================================================================================
___________________________________________________________________________________________________________ test_score_not_implemented_error[0-01] ____________________________________________________________________________________________________________

count_neg = 0, count_pos = '0', error = <class 'NotImplementedError'>

@parametrize(&#39;count_neg, count_pos&#39;,
[(&#39;0&#39;, 0),
(0, &#39;0&#39;),
(&#39;0&#39;, &#39;0&#39;),
([&#39;0&#39;], [0]),
([0], [&#39;0&#39;]),
([&#39;0&#39;], [&#39;0&#39;]),
(None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
count_pos: Union[str, int, List[str], List[int], None],
error: Type[BaseException] = NotImplementedError):
with pytest.raises(error) as exc_info:

> score(count_neg, count_pos)

tests/test_tps.py:38:


../../miniconda3/envs/pdl1lung/lib/python3.9/functools.py:888: in wrapper
return dispatch(args[0].class)(*args, **kw)
tests/test_tps.py:16: in score_int
def score_int(count_neg: int, count_pos: int) -> float:
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_functions.py:113: in check_argument_types
check_type_internal(value, expected_type, memo=memo)


value = '0', annotation = <class 'int'>, memo = <typeguard.CallMemo object at 0x7f591213ccc0>

def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -&gt; None:
&quot;&quot;&quot;
Check that the given object is compatible with the given type annotation.
This function should only be used by type checker callables. Applications should use
:func:`~.check_type` instead.
:param value: the value to check
:param annotation: the type annotation to check against
:param memo: a memo object containing configuration and information necessary for
looking up forward references
&quot;&quot;&quot;
if isinstance(annotation, ForwardRef):
try:
annotation = evaluate_forwardref(annotation, memo)
except NameError:
if global_config.forward_ref_policy is ForwardRefPolicy.ERROR:
raise
elif global_config.forward_ref_policy is ForwardRefPolicy.WARN:
warnings.warn(
f&quot;Cannot resolve forward reference {annotation.__forward_arg__!r}&quot;,
TypeHintWarning,
stacklevel=get_stacklevel(),
)
return
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
return
# Skip type checks if value is an instance of a class that inherits from Any
if not isclass(value) and SubclassableAny in type(value).__bases__:
return
extras: tuple[Any, ...]
origin_type = get_origin(annotation)
if origin_type is Annotated:
annotation, *extras_ = get_args(annotation)
extras = tuple(extras_)
origin_type = get_origin(annotation)
else:
extras = ()
if origin_type is not None:
args = get_args(annotation)
# Compatibility hack to distinguish between unparametrized and empty tuple
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
args = ((),)
else:
origin_type = annotation
args = ()
for lookup_func in checker_lookup_functions:
checker = lookup_func(origin_type, args, extras)
if checker:
checker(value, origin_type, args, memo)
return
if not isinstance(value, origin_type):

> raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
E typeguard.TypeCheckError: argument "count_pos" (str) is not an instance of int

../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:680: TypeCheckError
___________________________________________________________________________________________________ test_score_not_implemented_error[count_neg3-count_pos3] ___________________________________________________________________________________________________

count_neg = ['0'], count_pos = [0], error = <class 'NotImplementedError'>

@parametrize(&#39;count_neg, count_pos&#39;,
[(&#39;0&#39;, 0),
(0, &#39;0&#39;),
(&#39;0&#39;, &#39;0&#39;),
([&#39;0&#39;], [0]),
([0], [&#39;0&#39;]),
([&#39;0&#39;], [&#39;0&#39;]),
(None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
count_pos: Union[str, int, List[str], List[int], None],
error: Type[BaseException] = NotImplementedError):
with pytest.raises(error) as exc_info:

> score(count_neg, count_pos)

tests/test_tps.py:38:


../../miniconda3/envs/pdl1lung/lib/python3.9/functools.py:888: in wrapper
return dispatch(args[0].class)(*args, **kw)
tests/test_tps.py:22: in score_list
def score_list(count_neg: List[int], count_pos: List[int]) -> float:
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_functions.py:113: in check_argument_types
check_type_internal(value, expected_type, memo=memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:676: in check_type_internal
checker(value, origin_type, args, memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:273: in check_list
check_type_internal(v, args[0], memo)


value = '0', annotation = <class 'int'>, memo = <typeguard.CallMemo object at 0x7f5854383770>

def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -&gt; None:
&quot;&quot;&quot;
Check that the given object is compatible with the given type annotation.
This function should only be used by type checker callables. Applications should use
:func:`~.check_type` instead.
:param value: the value to check
:param annotation: the type annotation to check against
:param memo: a memo object containing configuration and information necessary for
looking up forward references
&quot;&quot;&quot;
if isinstance(annotation, ForwardRef):
try:
annotation = evaluate_forwardref(annotation, memo)
except NameError:
if global_config.forward_ref_policy is ForwardRefPolicy.ERROR:
raise
elif global_config.forward_ref_policy is ForwardRefPolicy.WARN:
warnings.warn(
f&quot;Cannot resolve forward reference {annotation.__forward_arg__!r}&quot;,
TypeHintWarning,
stacklevel=get_stacklevel(),
)
return
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
return
# Skip type checks if value is an instance of a class that inherits from Any
if not isclass(value) and SubclassableAny in type(value).__bases__:
return
extras: tuple[Any, ...]
origin_type = get_origin(annotation)
if origin_type is Annotated:
annotation, *extras_ = get_args(annotation)
extras = tuple(extras_)
origin_type = get_origin(annotation)
else:
extras = ()
if origin_type is not None:
args = get_args(annotation)
# Compatibility hack to distinguish between unparametrized and empty tuple
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
args = ((),)
else:
origin_type = annotation
args = ()
for lookup_func in checker_lookup_functions:
checker = lookup_func(origin_type, args, extras)
if checker:
checker(value, origin_type, args, memo)
return
if not isinstance(value, origin_type):

> raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
E typeguard.TypeCheckError: item 0 of argument "count_neg" (list) is not an instance of int

../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:680: TypeCheckError
___________________________________________________________________________________________________ test_score_not_implemented_error[count_neg4-count_pos4] ___________________________________________________________________________________________________

count_neg = [0], count_pos = ['0'], error = <class 'NotImplementedError'>

@parametrize(&#39;count_neg, count_pos&#39;,
[(&#39;0&#39;, 0),
(0, &#39;0&#39;),
(&#39;0&#39;, &#39;0&#39;),
([&#39;0&#39;], [0]),
([0], [&#39;0&#39;]),
([&#39;0&#39;], [&#39;0&#39;]),
(None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
count_pos: Union[str, int, List[str], List[int], None],
error: Type[BaseException] = NotImplementedError):
with pytest.raises(error) as exc_info:

> score(count_neg, count_pos)

tests/test_tps.py:38:


../../miniconda3/envs/pdl1lung/lib/python3.9/functools.py:888: in wrapper
return dispatch(args[0].class)(*args, **kw)
tests/test_tps.py:22: in score_list
def score_list(count_neg: List[int], count_pos: List[int]) -> float:
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_functions.py:113: in check_argument_types
check_type_internal(value, expected_type, memo=memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:676: in check_type_internal
checker(value, origin_type, args, memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:273: in check_list
check_type_internal(v, args[0], memo)


value = '0', annotation = <class 'int'>, memo = <typeguard.CallMemo object at 0x7f58540d45e0>

def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -&gt; None:
&quot;&quot;&quot;
Check that the given object is compatible with the given type annotation.
This function should only be used by type checker callables. Applications should use
:func:`~.check_type` instead.
:param value: the value to check
:param annotation: the type annotation to check against
:param memo: a memo object containing configuration and information necessary for
looking up forward references
&quot;&quot;&quot;
if isinstance(annotation, ForwardRef):
try:
annotation = evaluate_forwardref(annotation, memo)
except NameError:
if global_config.forward_ref_policy is ForwardRefPolicy.ERROR:
raise
elif global_config.forward_ref_policy is ForwardRefPolicy.WARN:
warnings.warn(
f&quot;Cannot resolve forward reference {annotation.__forward_arg__!r}&quot;,
TypeHintWarning,
stacklevel=get_stacklevel(),
)
return
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
return
# Skip type checks if value is an instance of a class that inherits from Any
if not isclass(value) and SubclassableAny in type(value).__bases__:
return
extras: tuple[Any, ...]
origin_type = get_origin(annotation)
if origin_type is Annotated:
annotation, *extras_ = get_args(annotation)
extras = tuple(extras_)
origin_type = get_origin(annotation)
else:
extras = ()
if origin_type is not None:
args = get_args(annotation)
# Compatibility hack to distinguish between unparametrized and empty tuple
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
args = ((),)
else:
origin_type = annotation
args = ()
for lookup_func in checker_lookup_functions:
checker = lookup_func(origin_type, args, extras)
if checker:
checker(value, origin_type, args, memo)
return
if not isinstance(value, origin_type):

> raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
E typeguard.TypeCheckError: item 0 of argument "count_pos" (list) is not an instance of int

../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:680: TypeCheckError
___________________________________________________________________________________________________ test_score_not_implemented_error[count_neg5-count_pos5] ___________________________________________________________________________________________________

count_neg = ['0'], count_pos = ['0'], error = <class 'NotImplementedError'>

@parametrize(&#39;count_neg, count_pos&#39;,
[(&#39;0&#39;, 0),
(0, &#39;0&#39;),
(&#39;0&#39;, &#39;0&#39;),
([&#39;0&#39;], [0]),
([0], [&#39;0&#39;]),
([&#39;0&#39;], [&#39;0&#39;]),
(None, None)])
def test_score_not_implemented_error(count_neg: Union[str, int, List[str], List[int], None],
count_pos: Union[str, int, List[str], List[int], None],
error: Type[BaseException] = NotImplementedError):
with pytest.raises(error) as exc_info:

> score(count_neg, count_pos)

tests/test_tps.py:38:


../../miniconda3/envs/pdl1lung/lib/python3.9/functools.py:888: in wrapper
return dispatch(args[0].class)(*args, **kw)
tests/test_tps.py:22: in score_list
def score_list(count_neg: List[int], count_pos: List[int]) -> float:
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_functions.py:113: in check_argument_types
check_type_internal(value, expected_type, memo=memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:676: in check_type_internal
checker(value, origin_type, args, memo)
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:273: in check_list
check_type_internal(v, args[0], memo)


value = '0', annotation = <class 'int'>, memo = <typeguard.CallMemo object at 0x7f58541444f0>

def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -&gt; None:
&quot;&quot;&quot;
Check that the given object is compatible with the given type annotation.
This function should only be used by type checker callables. Applications should use
:func:`~.check_type` instead.
:param value: the value to check
:param annotation: the type annotation to check against
:param memo: a memo object containing configuration and information necessary for
looking up forward references
&quot;&quot;&quot;
if isinstance(annotation, ForwardRef):
try:
annotation = evaluate_forwardref(annotation, memo)
except NameError:
if global_config.forward_ref_policy is ForwardRefPolicy.ERROR:
raise
elif global_config.forward_ref_policy is ForwardRefPolicy.WARN:
warnings.warn(
f&quot;Cannot resolve forward reference {annotation.__forward_arg__!r}&quot;,
TypeHintWarning,
stacklevel=get_stacklevel(),
)
return
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
return
# Skip type checks if value is an instance of a class that inherits from Any
if not isclass(value) and SubclassableAny in type(value).__bases__:
return
extras: tuple[Any, ...]
origin_type = get_origin(annotation)
if origin_type is Annotated:
annotation, *extras_ = get_args(annotation)
extras = tuple(extras_)
origin_type = get_origin(annotation)
else:
extras = ()
if origin_type is not None:
args = get_args(annotation)
# Compatibility hack to distinguish between unparametrized and empty tuple
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
args = ((),)
else:
origin_type = annotation
args = ()
for lookup_func in checker_lookup_functions:
checker = lookup_func(origin_type, args, extras)
if checker:
checker(value, origin_type, args, memo)
return
if not isinstance(value, origin_type):

> raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
E typeguard.TypeCheckError: item 0 of argument "count_neg" (list) is not an instance of int

../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/typeguard/_checkers.py:680: TypeCheckError
====================================================================================================================== warnings summary =======================================================================================================================
../../miniconda3/envs/pdl1lung/lib/python3.9/site-packages/cytomine/models/collection.py:26
/home/danielbell/miniconda3/envs/pdl1lung/lib/python3.9/site-packages/cytomine/models/collection.py:26: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import MutableSequence

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=================================================================================================================== short test summary info ===================================================================================================================
FAILED tests/test_tps.py::test_score_not_implemented_error[0-01] - typeguard.TypeCheckError: argument "count_pos" (str) is not an instance of int
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg3-count_pos3] - typeguard.TypeCheckError: item 0 of argument "count_neg" (list) is not an instance of int
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg4-count_pos4] - typeguard.TypeCheckError: item 0 of argument "count_pos" (list) is not an instance of int
FAILED tests/test_tps.py::test_score_not_implemented_error[count_neg5-count_pos5] - typeguard.TypeCheckError: item 0 of argument "count_neg" (list) is not an instance of int
=========================================================================================================== 4 failed, 3 passed, 1 warning in 0.94s ============================================================================================================


</details>
# 答案1
**得分**: 1
以下是翻译好的部分:
问题在于您期望所有测试都引发`NotImplementedError`,但在几种情况下,您传递了不正确类型的参数,因此实际上引发了`TypeCheckError`。让我们来看看您的测试用例。
- `('0', 0)` -- 通过
这引发了预期的`NotImplementedError`,因为除了第一个参数是字符串的情况下,您的所有`score`方法都不接受字符串。
- `(0, '0')` -- 失败
这会引发`TypeCheckError`,因为`score_int`需要`(int, int)`,但您传递了`(int, str)`。
- ('0', '0') -- 通过
这引发了预期的`NotImplementedError`,因为除了第一个参数是字符串的情况下,您的所有`score`方法都不接受字符串。
- `(['0'], [0])` - 失败
这会引发`TypeCheckError`,因为`score_list`需要`list[int]`,但您传递了`list[str]`。
- `([0], ['0'])` -- 失败
这与前一个测试失败的原因相同,只是问题出现在`count_pos`参数而不是`count_neg`参数。
- `(['0'], ['0'])` -- 失败
这与前两个测试失败的原因相同。
- `(None, None)])` -- 通过
这通过了,因为没有`score`方法接受None作为参数类型。
为了使所有测试用例都通过,您需要对`error`进行参数化。也许可以像这样:
```python
from typeguard import typechecked, TypeCheckError
from functools import singledispatch
import pytest
from typing import Any, Type
@singledispatch
def score(count_neg: Any, count_pos: Any) -> Any:
raise NotImplementedError(
f"{type(count_neg)} and or {type(count_pos)} are not supported."
)
@score.register(int)
@typechecked
def score_int(count_neg: int, count_pos: int) -> float:
return round(100 * count_pos / (count_pos + count_neg), 1)
@score.register(list)
@typechecked
def score_list(count_neg: list[int], count_pos: list[int]) -> float:
return round(100 * sum(count_pos) / (sum(count_pos) + sum(count_neg)), 1)
@pytest.mark.parametrize(
"count_neg, count_pos, error",
[
("0", 0, NotImplementedError),
(0, "0", TypeCheckError),
("0", "0", NotImplementedError),
(["0"], [0], TypeCheckError),
([0], ["0"], TypeCheckError),
(["0"], ["0"], TypeCheckError),
(None, None, NotImplementedError),
],
)
def test_score_not_implemented_error(
count_neg: str | int | list[str] | list[int] | None,
count_pos: str | int | list[str] | list[int] | None,
error: Type[BaseException],
):
with pytest.raises(error):
score(count_neg, count_pos)

请注意,我已经做了以下更改:

  • 我使用了pytest内置的parametrize装饰器,而不是pytest_cases的装饰器。

  • 我使用了更现代的类型注释。

  • 我修改了初始的score定义,以返回Any而不是None;这可以防止与其他方法的类型错误。

  • 我从您的测试中删除了assert,因为这是由with pytest.raises()逻辑隐式执行的(如果引发的异常与预期的异常不同,测试将失败)。

英文:

The problem is that you're expecting all of your tests to raise a NotImplementedError, but in several cases you're passing parameters of the incorrect type so instead you're getting a TypeCheckError. Let's take a look at your test cases..

  • (&#39;0&#39;, 0) -- PASSES

    This raises a NotImplementedError as expected because none of your score methods except a string in the first parameter.

  • (0, &#39;0&#39;) -- FAILS

    This fails with a TypeCheckError because score_int requires (int, int), but you're passing (int, str).

  • (&#39;0&#39;, &#39;0&#39;) -- PASSES

    This raises a NotImplementedError as expected because none of your score methods except a string in the first parameter.

  • ([&#39;0&#39;], [0]) - FAILS

    This raises a TypeCheckError because score_list requires list[int] but you've passed it a list[str].

  • ([0], [&#39;0&#39;]) -- FAILS

    This fails because for the same reason as the previous test, except the problem is with the count_pos parameter instead of count_neg.

  • ([&#39;0&#39;], [&#39;0&#39;]) -- FAILS

    This fails for the same reason as the previous two tests.

  • (None, None)]) -- PASSES

    This passes because no score method accepts None as a parameter type.


To get this test to pass for all your test cases you're going to need to parametrize error as well. Maybe something like:

from typeguard import typechecked, TypeCheckError
from functools import singledispatch

import pytest
from typing import Any, Type


@singledispatch
def score(count_neg: Any, count_pos: Any) -&gt; Any:
    raise NotImplementedError(
        f&quot;{type(count_neg)} and or {type(count_pos)} are not supported.&quot;
    )


@score.register(int)
@typechecked
def score_int(count_neg: int, count_pos: int) -&gt; float:
    return round(100 * count_pos / (count_pos + count_neg), 1)


@score.register(list)
@typechecked
def score_list(count_neg: list[int], count_pos: list[int]) -&gt; float:
    return round(100 * sum(count_pos) / (sum(count_pos) + sum(count_neg)), 1)


@pytest.mark.parametrize(
    &quot;count_neg, count_pos, error&quot;,
    [
        (&quot;0&quot;, 0, NotImplementedError),
        (0, &quot;0&quot;, TypeCheckError),
        (&quot;0&quot;, &quot;0&quot;, NotImplementedError),
        ([&quot;0&quot;], [0], TypeCheckError),
        ([0], [&quot;0&quot;], TypeCheckError),
        ([&quot;0&quot;], [&quot;0&quot;], TypeCheckError),
        (None, None, NotImplementedError),
    ],
)
def test_score_not_implemented_error(
    count_neg: str | int | list[str] | list[int] | None,
    count_pos: str | int | list[str] | list[int] | None,
    error: Type[BaseException],
):
    with pytest.raises(error):
        score(count_neg, count_pos)

Note that I've made the following changes:

  • I'm using pytest's built-in parametrize decorator, rather than the one from pytest_cases.

  • I'm use more modern type annotations.

  • I've modified the initial score definition to return Any rather than None; this prevents typing errors with the other methods.

  • I've removed the assert from your test because that's performed implicitly by the with pytest.raises() logic (which will fail the test if it raises an exception other than the expected one).

huangapple
  • 本文由 发表于 2023年7月27日 19:06:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76779124.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定