9.2 集成测试与测试技巧


文档摘要

9.2 集成测试与测试技巧 本节摘要:测试客户端让你不发一个真实网络包就能对视图发起请求、断言状态码、检查上下文与跳转目标。本节讲客户端的用法、登录态的模拟、夹具与工厂两种造数方案、参数化压缩重复用例,最后谈覆盖率与"该测什么"的取舍。这些技巧合起来,构成上线前的回归安全网。 测试客户端:不发网络的 HTTP 客户端的 get 或 post 模拟完整请求:穿过中间件、路由、视图、模板——但不出进程,毫秒级完成。断言工具箱值得记全:assertEqual 状态码、assertContains 内容包含、assertTemplateUsed 模板命中、assertRedirects 跳转链、assertQuerysetEqual 上下文查询集。

9.2 集成测试与测试技巧

本节摘要:测试客户端让你不发一个真实网络包就能对视图发起请求、断言状态码、检查上下文与跳转目标。本节讲客户端的用法、登录态的模拟、夹具与工厂两种造数方案、参数化压缩重复用例,最后谈覆盖率与"该测什么"的取舍。这些技巧合起来,构成上线前的回归安全网。

测试客户端:不发网络的 HTTP

from django.test import TestCase, Client from django.urls import reverse class ArticleViewTest(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user("author", password="pw12345") self.article = Article.objects.create( title="集成测试", body="x", author=self.user, status=Article.PUBLISHED) def test_list_returns_published(self): resp = self.client.get(reverse("blog:article-list")) self.assertEqual(resp.status_code, 200) self.assertContains(resp, "集成测试") def test_detail_404_for_draft(self): draft = Article.objects.create( title="草稿", body="x", author=self.user) resp = self.client.get( reverse("blog:article-detail", kwargs={"pk": draft.pk})) self.assertEqual(resp.status_code, 404)

客户端的 get 或 post 模拟完整请求:穿过中间件、路由、视图、模板——但不出进程,毫秒级完成。断言工具箱值得记全:assertEqual 状态码、assertContains 内容包含、assertTemplateUsed 模板命中、assertRedirects 跳转链、assertQuerysetEqual 上下文查询集。

第二条用例值得品味:草稿访问必须 404,守护的正是第 2 章"published 管理器"在视图层的贯通。集成测试的价值就在这里——单元都绿,链路仍可能断

登录态模拟

涉及权限的页面,客户端可一键登录:

class ProtectViewTest(TestCase): def test_anonymous_redirected(self): resp = self.client.get(reverse("blog:article-create")) self.assertEqual(resp.status_code, 302) # 未登录踢去登录页 def test_author_can_create(self): self.client.force_login(self.user) # 免密码模拟登录 resp = self.client.post( reverse("blog:article-create"), {"title": "新文", "body": "内容"}) self.assertEqual(Article.objects.count(), 2)

force_login 不校验密码直接建立会话,专供测试;想连登录流程一起测就用 login 方法带密码。第 7 章的权限决定(作者组、对象归属)在这里全部可以验证。

造数:夹具与工厂

数据准备两种流派。夹具是写死的 JSON 数据文件,用例声明加载:

class WithFixtureTest(TestCase): fixtures = ["articles.json"]

夹具直观,但维护是噩梦:模型一改字段,全部夹具手工同步。现代实践更偏工厂——用代码造数,随模型演进:

class ArticleFactory: @staticmethod def make(title="工厂文章", status=Article.PUBLISHED, **kw): defaults = {"body": "默认正文", "author": User.objects.create_user("f")} defaults.update(kw) return Article.objects.create(title=title, **defaults) class FactoryTest(TestCase): def test_count(self): ArticleFactory.make(title="甲") ArticleFactory.make(title="乙", status=Article.DRAFT) self.assertEqual(Article.published.count(), 1)

工厂的精髓是"默认值加按需覆盖":每条用例只声明与本用例相关的差异,噪点降到最低。第三方库把这个思路产品化(factory_boy 的 Faker 集成),但上面这个十几行的静态工厂已经能覆盖中小项目的需求——先用最朴素的,痛了再引库。

参数化与提速

一个校验器要试十组坏数据,写十条测试太笨。用参数化装饰器:

from unittest import param from django_testing_examples import parametrize # 或标准库 subTest class ParamTest(TestCase): def test_bad_contents(self): for bad in ["广告", "http1 http2 http3 http4", ""]: with self.subTest(bad=bad): form = CommentForm(data={"reviewer": "a", "content": bad}) self.assertFalse(form.is_valid())

subTest 让一组输入在一个用例里各自报告,一组数据一次写完。提速方面两条路:测试数据库本来就在内存;用例特别多时加并行参数让多个进程分摊,机器几个核就近似快几倍。

取舍:测什么,不测什么

全测是伪目标。墨迹博客的取舍清单:

必测 可不测
自写的校验与业务规则 框架自身的功能(不必测 CharField 存得进库)
权限边界(未登录、无权限、非本人) 第三方库内部行为
发布主路径(列表、详情、发文) 纯展示性分支的颜色文案类细节
历次出过 bug 的位置(回归锚点) 会随重构频繁重写的试验代码

覆盖率数字当参考不当目标——追 100% 覆盖率会把测试写成实现的镜像,重构时全盘报废。真正该盯的指标是:改一处坏三处时,测试能不能红给你看。

⚠️ 测试里最大的坑是"共享状态泄漏":类属性放可变对象、用例间偷懒复用数据。记住每条用例只依赖 setUp 与自己造的数据,红线就不破。

把这套安全网接进发布流程,是本章最后也最有价值的一步。做法很朴素:发布脚本在执行迁移之前先跑全量测试,退出码非零则整个发布立即中止,不给任何"先上线再修"的侥幸留口子。这样一来,"测试全绿"从一种自觉变成了一道闸门——任何人、任何一次发布都不可能绕过它。墨迹博客上线后的第二个月,这条闸门真的拦下过一次事故:一处模型改动破坏了草稿不可见的约束,本机手动点点点没发现,发布前自动测试红了,修复只花了十分钟。团队从那次起对测试的态度从"额外负担"变成了"发布许可",这种观念转变比任何测试技巧都重要。安全网织好不是终点,让它永远张在发布路径上才是。

本节要点回顾

  • 客户端零网络模拟全链路,断言箱覆盖状态、内容、模板、跳转。
  • force_login 免密建会话,权限边界逐一验证。
  • 工厂优于夹具:默认值加差异覆盖,随模型演进。
  • subTest 参数化一组输入一个用例;覆盖率先保规则与边界,不追数字。

安全网织好,下一章把墨迹博客真正送上生产环境。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U