针对Python的自以为是的类型化存根和验证库

deco的Python项目详细描述


诱饵

ci badgepypi version badgelicense badge

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")))

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java为什么即使我已经给出了代码中的所有权限,该代码也没有在emulator中运行?   java Android Studio正在抛出“线程中的异常”main“javax.net.ssl.SSLException:收到致命警报:协议\版本”   java中的for循环嵌套foreach语句   java读取/src/main/resources和/webinf/classes下的文件   java无法以此格式构造JSON响应   身份验证尝试从CAS secure rest api获取响应,但从java客户端获取登录页面作为响应   如何在java中使用excel从第1列和第3列获取单元格值,并将其作为键值对放入map中   在Java程序中打开Windows虚拟键盘   java有没有递归调用findMatch的方法?   java Pig脚本/命令,用于根据多个字符串筛选文件   java最小数量应匹配,应与POST匹配   java打开/关闭声音按钮不工作   Java嵌入式数据库持久性   java在方法调用时引发异常   java文本文件被覆盖而不是保存的问题   java Hibernate sql注释