python mock
先聊聊什么是mock它到底是干嘛用的。你可以把mock想象成一个替身演员。在电影拍摄里替身演员代替主角完成高难度动作避免主角受伤或者耽误档期。在代码世界里mock就是这么一个替身它代替真实的对象去执行任务——尤其是那些依赖外部环境的、难以控制的、或者运行起来代价很高的任务。举个例子你的代码里有一段逻辑需要调用一个第三方支付接口。这个接口真的付出去真金白银测试环境里当然不能真的调用否则每天光测试费就得亏掉一顿饭钱。这时候mock就上场了它假扮成那个支付接口不管你怎么调用它它都会返回一个预设的“成功”响应。这样你的代码该跑的分支都能跑到测试也能通过而你的钱包依然安全。更深入一点看mock解决的是代码中的耦合问题。当你的函数需要在另一个函数的结果上做决策时那另一个函数可能依赖数据库、网络、文件系统。在单元测试里我们不希望真的去连数据库、发网络请求或者打开文件因为这些操作慢、不稳定、而且让测试变得脆弱。mock把这些依赖全部替换成可控的、可预测的伪装品你就只测试你那一段逻辑本身对不对而不是测试整个系统。下面说说它能做到什么。mock的核心能力就几项返回值可控、行为可控、行为可追溯。可控的返回值让你能模拟各种边界情况——比如刚才的支付接口你可以让它返回“成功”也可以让它返回“余额不足”、“网络超时”、“服务器内部错误”。这样你代码里的所有条件分支都能被覆盖到。行为可控是说你还能模拟函数抛出异常比如一个数据解析函数在字符串格式不对时会抛ValueError你想测试自己的代码对这种异常的处理是否健壮那就可以让mock在调用时主动抛出一个ValueError。行为可追溯这一点非常实用——你能知道在测试里某个mock是否被调用过、调用了多少次、传入了什么参数。这在你写一个回调机制或者事件监听机制的时候尤其重要你得确认你的代码确实调对了函数而且传对了参数。具体怎么用呢Python的标准库里自带一个unittest.mock模块不需要装第三方包。最常用的两个类就是Mock和MagicMock。区别不大MagicMock多了几个魔法方法的默认实现比如__len__、__iter__这些。一般用MagicMock就行了省得你踩坑。一个典型的用法是这样的假设我写了一个发送邮件的模块里面有个send_email函数。这个函数内部调用了smtplib.SMTP来真正发邮件。测试时我不想真的连邮件服务器那就mock掉SMTP这个类。fromunittest.mockimportMagicMock,patchimportmy_mail_moduledeftest_send_email():# patch装饰器会临时替换my_mail_module中的smtplib.SMTPwithpatch(my_mail_module.smtplib.SMTP)asmock_smtp:# 创建一个实例对象返回这个mock实例instancemock_smtp.return_value# 设置sendmail方法的返回值instance.sendmail.return_valueTrueresultmy_mail_module.send_email(toexample.com,Subject,Body)assertresultTrue# 确认sendmail被调用过而且参数正确instance.sendmail.assert_called_once_with(...)这里用了一个上下文管理器with语句来包裹测试逻辑patch完成后自动恢复原样不影响其他测试。也可以把patch当作装饰器放在函数头上效果一样。有时候需要对对象的某个属性做mock比如有一个requests.Session对象想mock它的get方法返回一个特定响应。可以用return_value嵌套mock_sessionMagicMock()mock_responseMagicMock()mock_response.status_code200mock_response.json.return_value{key:value}mock_session.get.return_valuemock_response这样当代码里调用session.get(…)时就会得到这个预制的响应。关于最佳实践有几条是我自个儿踩坑之后总结的。第一尽量mock边界而不是核心。mock应该用来替换外部依赖网络、数据库、文件系统、时间函数但不应该mock掉你自己写的业务逻辑。如果你发现需要mock你的某个内部函数那很可能那个函数已经耦合得太深值得考虑重构了。第二别过度使用mock。有时候开发者为了图省事把整个模块都mock了结果测试成了纯演出来证明“依赖关系正确”实际上什么都没测到。理想的单元测试是真实的——只有那些“没法真实”的部分才交给mock。任何你自己写的、能真实运行的代码就应该让它真的跑一遍。第三记得校验调用行为。只设return_value不够你还得检查是否正确调用了那些外部接口。比如调用了一个日志写入函数如果没写参数校验那实际传错了参数你的测试还是绿的这就是假阳性。assert_called_once_with、assert_has_calls这些断言方法是mock留给你的把柄得用上。第四注意mock的生命周期。使用patch或patch.object时如果在测试函数内创建了对象引用出了with块mock就恢复了引用还在但对象已经变回真的了。这不容易发现错误所以通常建议把mock的注入和释放限制在最小的作用域里。最后聊聊同类技术。Python里非标准库的方案有pytest-mock这是对unittest.mock的一层轻量封装用起来更简洁而且和pytest的fixture机制融合得很好。如果你是pytest用户强烈推荐。还有Faker它不是mock但用于生成假数据。它和mock正好互补mock负责替换行为Faker负责填充数据。另一个相关的是responses库专门用来mock HTTP请求。它是基于requests库的插件可以约定哪些URL对应哪些响应比直接用MagicMock来模拟requests更自然、也更安全。因为如果你直接用MagicMock一旦requests内部实现变了一点比如依赖了某个你没mock的属性测试就会莫名其妙挂掉。使用responses可以更精确地模拟HTTP协议层面的交互。还有一个是freezegun用来模拟时间。测试时间相关逻辑时非常有用比如你想测试一个延迟任务用freezegun把系统时间冻结在一个时刻代码检查时间时就会得到一个固定值而不是真实时间。说到底mock的核心哲学是让测试只关注你自己写的逻辑而不被外部环境的不可控因素干扰。用好它能让你的测试干净、快速且可靠。