python pytest-django
# 聊聊 pytest-django 这个工具最近在项目里又用到了 pytest-django感觉有些想法可以写下来。这东西在 Django 测试领域已经存在好几年了但真正能把它用明白的团队其实不多。很多人只是用它来跑几个简单的测试其实它背后有不少值得琢磨的地方。它到底是什么pytest-django 本质上是个桥梁。如果你熟悉 Django 自带的测试框架就知道那个python manage.py test命令还有那些继承自TestCase的测试类。Django 这套东西用起来没什么大问题但总觉得少了点什么。pytest-django 就是让 Django 项目能够用 pytest 来运行测试。pytest 本身是个独立的测试框架比 Python 自带的 unittest 灵活得多。你可以把它想象成Django 自带的是标准配置的测试工具而 pytest-django 让你能用上更高级的测试工具箱。它不是要取代 Django 的测试框架而是在上面加了一层。底层还是 Django 的那套东西数据库回滚、请求模拟这些核心机制都还在只是换了个更舒服的调用方式。它能做什么最直接的就是让写测试变得更简单。举个例子在 Django 原生的测试里你要测试一个视图返回的数据得写好几行代码来模拟请求、检查响应。用 pytest-django 配合 pytest 的一些特性有时候一行就能搞定。但它的价值不止于此。pytest 有个很好的功能叫 fixture你可以理解为测试用的“预制件”。比如你要测试一个需要用户登录的功能在 Django 原生的测试里你可能要在每个测试方法里都创建用户、登录一遍。用 fixture你可以把这个过程定义一次然后在任何需要的地方直接引用。数据库处理也变得灵活了。Django 默认每个测试用例跑完都会回滚数据库保证测试之间互不干扰。但有些场景下你可能希望多个测试共享同一个数据库状态或者用更轻量级的内存数据库。pytest-django 提供了不同的数据库配置策略可以根据测试的需要来选择。还有测试报告。pytest 生成的测试报告比 Django 默认的详细得多哪个测试失败了、为什么失败、失败时的具体数据是什么都列得很清楚。这对于调试复杂的测试问题特别有帮助。怎么开始用安装很简单pip install pytest-django就行。但关键是要配置好。首先需要在项目根目录下创建个pytest.ini文件告诉 pytest-django 你的 Django 项目在哪里。这个文件里最基本的就是设置DJANGO_SETTINGS_MODULE指向你的 settings 模块。然后就可以开始写测试了。pytest-django 的测试文件通常还是放在各应用的tests目录下但命名有个小讲究文件名最好以test_开头这样 pytest 能自动发现它们。测试函数或类也以test_开头这是 pytest 的约定。写测试的时候你可以用django_db这个标记来控制数据库访问。比如你有个测试不需要数据库就在函数上面加个pytest.mark.django_db标记告诉框架这个测试需要数据库支持。如果不加测试跑起来会快一点。请求测试可以用clientfixture。这个client就是 Django 测试客户端但用起来更直接。比如测试一个返回 JSON 的 API你可以这样写deftest_api_endpoint(client):responseclient.get(/api/data/)assertresponse.status_code200assertresultsinresponse.json()比 Django 原生的写法简洁不少。一些实际用下来的经验刚开始用的时候最容易犯的错误是想把所有 Django 测试习惯都直接搬过来。其实更好的做法是逐步迁移先在新写的测试里用 pytest-django老的测试慢慢改。fixture 是个好东西但别滥用。见过有些项目里fixture 套 fixture最后谁都搞不清楚测试到底依赖什么。简单的测试用简单的 fixture复杂的业务逻辑才需要复杂的 fixture 组合。有个原则挺实用如果一个 fixture 被超过三个测试文件用到才考虑把它放到公共的地方。数据库策略的选择要看测试类型。单元测试尽量用transactionTrue这样测试之间完全隔离。集成测试或需要测试数据库交互的场景可以用reuse_db能节省不少测试时间。但要注意reuse_db用不好会导致测试之间的脏数据问题。测试数据工厂比直接使用 Django 的create方法更好管理。比如用factory_boy这样的库来生成测试数据配合 pytest-django 的 fixture测试数据的管理会清晰很多。这样测试读起来也更容易理解看到user_factory就知道这是个测试用户而不需要去查这个用户到底有哪些属性。还有一个细节pytest-django 默认会收集所有静态文件这在大项目里会让测试启动变慢。如果测试不涉及静态文件可以在pytest.ini里关掉这个特性。和其他方案对比最常见的比较当然是和 Django 原生的测试框架比。简单来说如果你项目的测试已经很成熟团队也熟悉 Django 那套不一定非要换。但如果是新项目或者测试写得不多直接从 pytest-django 开始会省事很多。和unittest比pytest 的语法更灵活。unittest里要用self.assertEqual(a, b)pytest 里直接assert a b就行。这种小差别积累起来写测试的心情都会不一样。还有个选择是django-nose这也是个让 Django 用其他测试框架的工具。但 nose 项目现在基本不维护了pytest 是更活跃的选择。生态上 pytest 也丰富得多各种插件、工具链都更完整。如果项目里已经有大量的unittest测试也不用担心。pytest 可以直接运行unittest风格的测试所以迁移可以慢慢来。有些团队的做法是新测试用 pytest 风格写老的等有时间再改两者可以共存。最后说两句测试工具说到底是个效率问题。好的工具不会改变测试的本质但能让写测试、维护测试的成本降低。pytest-django 就属于这种工具它不会让你的测试自动变好但如果你愿意花点时间学习它的特性长期来看是值得的。刚开始用可能会觉得有点别扭特别是习惯了 Django 那套方式之后。但用上一段时间尤其是写过一些复杂的测试场景后你会感受到它的好处。测试代码也是代码写得舒服了人才愿意多写测试覆盖率自然就上去了。不过任何工具都有适用场景。如果就是个简单的小项目几个模型几个视图Django 自带的测试完全够用。但当项目规模上去测试数量多了测试场景复杂了pytest-django 提供的那些额外灵活性就开始显现价值了。说到底选什么工具还是看团队和项目的实际情况。但至少知道有这么个选项需要的时候能想起来这就够了。