在非可选输入参数上测试"无"



假设我有一个具有以下功能的python模块:

def is_plontaria(plon: str) -> bool:
if plon is None:
raise RuntimeError("None found")
return plon.find("plontaria") != -1

对于该功能,我进行了以下单元测试:

def test_is_plontaria_null(self):
with self.assertRaises(RuntimeError) as cmgr:
is_plontaria(None)
self.assertEqual(str(cmgr.exception), "None found")

给定函数中的类型提示,输入参数应该始终是一个已定义的字符串。但是类型提示是。。。提示。没有什么可以阻止用户传递它想要的任何内容,当以前的操作无法返回预期结果并且没有检查这些结果时,"无"是一个非常常见的选项。

因此,我决定在单元测试中测试None,并检查函数中的输入是否为None。

问题是:类型检查器(pylance(警告我,我不应该在那个调用中使用None:

Argument of type "None" cannot be assigned to parameter "plon" of type "str" in function "is_plontaria"
Type "None" cannot be assigned to type "str"

我已经知道了,这就是测试的目的。

消除这个错误的最佳方法是什么?告诉pylance忽略每个测试/文件中的此类错误?或者假设传递的参数总是正确的类型,并删除该测试和函数中的None检查?

这是个好问题。我认为在你的测试中消除这种类型的错误不是正确的做法。

不要光顾用户

虽然我不会说这是普遍正确的方法,但在这种情况下,我肯定会建议取消is_plontariaNone检查。

想想你用这张支票完成了什么。假设用户调用is_plontaria(None),即使您用str对其进行了注释。在不进行检查的情况下,他导致具有回溯到线return plon.find("plontaria") != -1AttributeError: 'NoneType' object has no attribute 'find'。用户对自己认为"oops,该函数期望CCD_。在您的检查中,他会导致RuntimeError,理想情况下会告诉他plon应该是str

支票的用途是什么?我不会争辩任何事。无论哪种方式,都会因为您的函数被误用而引发错误。

如果用户意外通过float怎么办?还是bool?或者除了str之外的其他东西?是否要握住用户的手以获取您编写的every函数的every参数?

我不相信;CCD_ 14是一个特殊的情况-论点当然,这是一种常见的类型;躺着";在代码中,但正如您自己指出的那样,这仍然取决于用户。

如果您使用的是正确类型的注释代码(正如您应该使用的那样(,并且用户也是,那么这种情况就永远不会发生。假设用户有另一个功能foo,他想这样使用:

def foo() -> str | None:
...
s = foo()
b = is_plontaria(s)

最后一行应该会导致任何称职的静态类型检查器都会引发一个错误,即is_plontaria只接受str,但提供了strNone的并集。甚至大多数IDE都认为这条线有问题。

用户应该在运行代码之前看到这一点。然后,他被迫重新思考,或者更改foo,或者在调用您的函数之前引入他自己的类型检查

s = foo()
if isinstance(s, str):
b = is_plontaria(s)
else:
# do something else

限定符

公平地说,在某些情况下,错误消息非常模糊,不能正确地告诉调用者出了什么问题。在这种情况下,介绍一下自己可能会很有用。但除此之外,我总是本着Python的精神,认为用户应该足够成熟,可以做自己的功课。如果他不这样做,那就怪他,而不是你。(只要你做了你的作业。(

可能在其他情况下,提出自己的类型错误是有意义的,但我认为这是例外。


如果必须,请使用Mock

作为一个小奖励,如果您确实do想要保留该检查,并且需要在测试中覆盖if-分支,那么您可以简单地传递一个Mock作为参数,前提是您的if-语句被调整为检查str:以外的任何内容

from unittest import TestCase
from unittest.mock import Mock

def is_plontaria(plon: str) -> bool:
if not isinstance(plon, str):
raise RuntimeError("None found")
return plon.find("plontaria") != -1

class Test(TestCase):
def test_is_plontaria(self) -> None:
not_a_string = Mock()
with self.assertRaises(RuntimeError):
is_plontaria(not_a_string)
...

大多数类型检查器认为Mock是一种特殊情况,并且不会抱怨它的类型,假设您正在运行测试。例如,mypy对这样的代码非常满意。

这在其他情况下也很有用。例如,当被测试的函数期望您的某个自定义类的实例作为其参数时。很明显,您希望将函数与该类隔离开来,因此可以通过这种方式向其传递mock。类型检查器不会介意的。

希望这能有所帮助。

您可以在带有注释的特定行上禁用类型检查。

def test_is_plontaria_null(self):
with self.assertRaises(RuntimeError) as cmgr:
is_plontaria(None) # type: ignore
self.assertEqual(str(cmgr.exception), "None found")

最新更新