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'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've implemented def score()
'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('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: 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
"""
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"Cannot resolve forward reference {annotation.__forward_arg__!r}",
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('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: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) -> 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: 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
"""
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"Cannot resolve forward reference {annotation.__forward_arg__!r}",
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('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: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) -> 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: 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
"""
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"Cannot resolve forward reference {annotation.__forward_arg__!r}",
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('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: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) -> 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: 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
"""
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"Cannot resolve forward reference {annotation.__forward_arg__!r}",
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 ============================================================================================================
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 aTypeCheckError
. Let's take a look at your test cases..('0', 0)
-- PASSESThis raises a
NotImplementedError
as expected because none of yourscore
methods except a string in the first parameter.(0, '0')
-- FAILSThis fails with a
TypeCheckError
becausescore_int
requires(int, int)
, but you're passing(int, str)
.('0', '0')
-- PASSESThis raises a
NotImplementedError
as expected because none of yourscore
methods except a string in the first parameter.(['0'], [0])
- FAILSThis raises a
TypeCheckError
becausescore_list
requireslist[int]
but you've passed it alist[str]
.([0], ['0'])
-- FAILSThis fails because for the same reason as the previous test, except the problem is with the
count_pos
parameter instead ofcount_neg
.(['0'], ['0'])
-- FAILSThis fails for the same reason as the previous two tests.
(None, None)])
-- PASSESThis 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:Note that I've made the following changes:
I'm using pytest's built-in
parametrize
decorator, rather than the one frompytest_cases
.I'm use more modern type annotations.
I've modified the initial
score
definition to returnAny
rather thanNone
; this prevents typing errors with the other methods.I've removed the
assert
from your test because that's performed implicitly by thewith pytest.raises()
logic (which will fail the test if it raises an exception other than the expected one).