针对Python的自以为是的类型化存根和验证库
deco的Python项目详细描述
诱饵
Opinionated, typed stubbing and verification library for Python
https://mike.cousins.io/decoy/
诱饵库允许您为Python单元测试创建、存根和验证test double对象,因此您的测试是:
- 由于无条件留茬,不太容易出现不充分的测试
- 通过排版
- 更容易适应Arrange-Act-Assert模式
decoyapi的灵感来自于优秀的testdouble.js和{a6}项目。
安装
# pip pip install decoy # poetry poetry add --dev decoy
设置
您需要创建一个测试装置来重置每个测试运行之间的诱饵状态。在pytest中,可以使用fixture为每个测试创建一个新的诱饵实例。
以下示例假设以下全局测试夹具:
^{pr2}$为什么这很重要?Decoy
容器跟踪测试期间创建的每个伪对象,以便您可以使用测试double的完全类型排练来定义断言。重要的是要在每个测试中清除这个石板,这样就不会泄漏内存或在测试之间保留任何状态。
Mypy设置
如果所讨论的mock应该返回None
,那么Decoy的预演语法可能会让mypy感到困惑。通常,mypy will complain如果您试图使用None
-返回表达式作为值,因为这几乎总是一个错误。
然而,在Decoy中,这是API有意的一部分,not是个错误。为了抑制这些错误,Decoy提供了一个mypy插件,您应该将它添加到配置文件中:
# mypi.ini# ...plugins=decoy.mypy# ...
使用
留茬
存根是测试中使用的一个对象,它预先配置为在根据规范调用时返回结果或引发错误。在Decoy中,您可以使用“prefary”来指定存根的调用期望值,它只是对decoy.when
包装内的存根的调用。
通过预先配置特定排练的存根,可以获得以下好处:
- 只有正确调用了模拟值,测试double才会返回它
- 您可以避免单独的“setupmock return value”和“assert mock called correctly”步骤
- 如果您用实际的类型注释了您的测试double,那么如果调用不正确,排练将失败
importpytestfromtypingimportcast,OptionalfromdecoyimportDecoyfrom.databaseimportDatabase,Modeldefget_item(uid:str,db:Database)->Optional[Model]:returndb.get_by_id(uid)deftest_get_item(decoy:Decoy):mock_item=cast(Model,{"foo":"bar"})mock_db=decoy.create_decoy(spec=Database)# arrange stub using rehearsalsdecoy.when(mock_db.get_by_id("some-id")).then_return(mock_item)# call code under testsome_result=get_item("some-id")other_result=get_item("other-id")# assert code resultassertsome_result==mock_itemassertother_resultisNone
验证交互作用
如果您来自unittest.mock
,那么您可能习惯于调用正在测试的代码,,然后验证是否正确调用了依赖项。Decoy使用stubbing API使用的相同“预演”机制提供类似的调用验证。
importpytestfromtypingimportcast,OptionalfromdecoyimportDecoy,verifyfrom.loggerimportLoggerdeflog_warning(msg:str,logger:Logger)->None:logger.warn(msg)deftest_log_warning(decoy:Decoy):logger=decoy.create_decoy(spec=Logger)# call code under testsome_result=log_warning("oh no!",logger)# verify double called correctly with a rehearsaldecoy.verify(logger.warn("oh no!"))
断言调用发生在事实之后是有用的,但是只有在依赖项被调用时才应该使用,因为它的副作用才被调用。以这种方式验证相互作用应被视为最后手段,因为:
- 如果调用依赖项来获取数据,则可以使用stubbing更精确地描述该关系
- 副作用比单纯的功能更难理解和维护,所以一般来说,您应该尽量避免副作用
在测试中,诱饵的存根和验证是互斥的。如果你发现自己想要同时使用存根和验证同一个诱饵,那么其中一个或多个是正确的:
- 断言是多余的
- 依赖项基于它的输入做了太多工作(例如,副作用和计算复杂数据),应该重构
使用async/await
诱饵支持异步/等待开箱即用!将带有异步方法的异步函数或类分别传递给decoy.create_decoy_func
或{spec
,Decoy将解决其余问题。
在编写有关异步函数和方法的排练时,请记住在排练调用中包含await
:
decoy.when(awaitmock_db.get_by_id("some-id")).then_return(mock_item)
火柴
有时候,当你在打麦茬的时候如果调用调用(或者确实在测试中执行任何类型的相等断言),则需要松开给定的断言。例如,您可能希望断言使用字符串调用依赖项,但是您不关心该字符串的完整内容。
Decoy包括一组matcher,它们只是定义了__eq__
方法的Python类,可以在排练和/或断言中使用。
importpytestfromtypingimportcast,OptionalfromdecoyimportDecoy,matchersfrom.loggerimportLoggerdeflog_warning(msg:str,logger:Logger)->None:logger.warn(msg)deftest_log_warning(decoy:Decoy):logger=decoy.create_decoy(spec=Logger)# call code under testsome_result=log_warning("Oh no, something went wrong with request ID abc123efg456",logger=logger)# verify double called correctlydecoy.verify(logger.warn(matchers.StringMatching("request ID abc123efg456")))
- 项目
标签: