go-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Testing
Go 测试
How a Go test is written, in every repo. What to test is the repo's when it
ships one: the kinds of test it uses, what each is worth, which a feature needs, and the reusable
patterns to copy as the codebase grows. It never restates how a test is written, so the two do not
overlap. For the red-green-refactor loop, use .
testing-policytdd在每个仓库中,Go 测试都按如下方式编写。如果仓库提供了 ,那么测试什么就以它为准:
它使用哪些测试类型、每种测试的价值、功能需要哪些测试,以及随着代码库发展可以复用的
模式。它绝不会重复说明测试如何编写,因此二者不会重叠。对于 red-green-refactor 循环,
请使用 。
testing-policytddYour defaults that are wrong here
此处不应沿用的默认做法
These are the habits that show up unprompted. Check your test against this table before
writing it, and again before finishing, because they creep back in late in a long task.
| You reach for | Do this instead |
|---|---|
testify | Plain |
| |
A table with | Two tests: a success table and an error table checked with |
gomock, mockery, | A stateful in-memory fake; assert on the state it ends in |
| A real Postgres from |
| |
| A fresh database per test, so |
| A channel the code signals, a deadline poll, or |
| |
| A new test dependency | Only |
以下习惯常常未经提示就出现。编写测试前对照下表检查,结束前再检查一次,因为在长任务的
后期这些习惯容易重新出现。
| 你会使用 | 改为 |
|---|---|
testify | 原生 |
| |
含有 | 两个测试:success table 和使用 |
gomock、mockery、 | 有状态的内存 fake;断言其最终状态 |
| 使用 |
| |
测试之间使用 | 每个测试使用全新的 database,因此可以安全使用 |
使用 | 使用代码发出信号的 channel、带 deadline 的条件轮询,或 |
| 使用 |
| 添加新的测试依赖 | 仅允许 |
Rules
规则
- Test behaviour through the exported API, in the black-box . Use
package foo_testonly when the unit is unexported by design, andpackage fooas a last resort.export_test.go - Assert on outputs and state, not on which calls were made. A test that breaks on a refactor that changes no behaviour is a change-detector: delete it.
- Pick the most faithful double: real, then fake, then stub, then mock. Mock only a state-changing call across a system boundary (an email sent, a card charged), and then a hand-written recorder, not a mocking library.
- A fake is stateful and honest: the same sentinels, the same not-found behaviour. One contract suite runs against the fake and the real implementation. Force an error path with a one-method stub that embeds the interface, never with failure knobs on the fake.
- Test SQL against a real Postgres through . If the repo has no
internal/pgtest, install it from this skill's assets: loadinternal/pgtest.references/integration-db.md - Compare with and report
cmp.Diff(want, got). If(-want +got)lacksgo.mod, add it withgithub.com/google/go-cmpon the first test you touch, and fix any lint rule or doc in the repo that contradicts this in the same change. When the user or the repo has explicitly ruled out new dependencies ("stdlib only"), compare scalars withgo get, slices and maps with==/slices.Equal, structs field by field. Nevermaps.Equaleither way.reflect.DeepEqual - Failure messages read . Match errors with
Func(in) = got, want want/errors.Is; a caller that needs to tell errors apart gets an exported sentinel or type.errors.As - Use a table only when every case is checked the same way: a slice of structs with a field run with
name. A branch on a case field means split the test.t.Run(tt.name, ...) - to keep going,
t.Errorwhen the rest can't run. Nevert.Fatalfrom a goroutine the test didn't start on; send the error back on a channel.t.Fatal - Helpers take , call
t, fail on setup errors and registert.Helper(). They set up; they don't assert. No generict.Cleanup.assertEqual - No to wait. Wait on a channel, poll a condition with a deadline, or run the test in
time.Sleep(Go 1.25+) when it involves timers.synctest.Test - only for tests that share no state: no globals,
t.Parallel(),t.Setenvor shared rows. At.Chdirdatabase is never shared, so DB tests can be parallel.pgtest - Compare JSON by decoding it, never as bytes. Normalise golden output before comparing; a
human reviews every diff.
-update - Fuzz parsers of untrusted input with a property (no panic, round trip); commit
.
testdata/fuzz/ - Test HTTP handlers through the real router and middleware, built the way builds them.
main
- 通过 exported API 测试行为,使用 black-box 的 。仅当被测单元在设计上 就是 unexported 时才使用
package foo_test;package foo仅作为最后手段。export_test.go - 断言输出和状态,而不是断言调用了哪些方法。如果一次不改变行为的重构会导致测试失败, 那么它只是 change-detector:应将其删除。
- 选择最忠实的 test double:依次为 real、fake、stub、mock。仅当跨 system boundary 的调用 会改变状态时才使用 mock,例如发送邮件或扣取卡款;此时使用手写 recorder,而不是 mocking 库。
- fake 必须有状态且诚实:具有相同的 sentinel 和相同的 not-found 行为。同一个 contract suite 应同时针对 fake 和 real implementation 运行。使用嵌入 interface 的单方法 stub 强制进入 error path,绝不要在 fake 上添加 failure knob。
- 通过 针对真实 Postgres 测试 SQL。如果仓库没有
internal/pgtest, 则从此 skill 的 assets 中安装:加载internal/pgtest。references/integration-db.md - 使用 比较,并按
cmp.Diff(want, got)报告。如果(-want +got)中没有go.mod,则在第一次修改测试时通过github.com/google/go-cmp添加它,并在同一次变更中 修复仓库内与此冲突的任何 lint rule 或文档。如果用户或仓库明确禁止添加新依赖 (“stdlib only”),则使用go get比较标量,使用==/slices.Equal比较 slice 和 map,并逐字段比较 struct。无论哪种情况,都不要使用maps.Equal。reflect.DeepEqual - 失败消息应读作 。使用
Func(in) = got, want want/errors.Is匹配错误; 如果调用方需要区分错误,应提供 exported sentinel 或 type。errors.As - 仅当所有 case 都采用相同方式检查时才使用 table:使用带有 字段的 struct slice, 并通过
name运行。只要根据 case 字段进行分支,就应拆分测试。t.Run(tt.name, ...) - 使用 继续执行;当后续测试无法运行时使用
t.Error。绝不要在测试未启动的 goroutine 中调用t.Fatal;应通过 channel 将错误传回。t.Fatal - helper 应接收 、调用
t,在 setup 出错时失败,并注册t.Helper()。 helper 只负责 setup,不负责 assertion。不要编写通用的t.Cleanup。assertEqual - 不要使用 等待。应等待 channel、带 deadline 轮询条件,或者在测试涉及 timer 时使用
time.Sleep(Go 1.25+)。synctest.Test - 仅当测试不共享任何状态时才使用 :不得使用 global、
t.Parallel()、t.Setenv或共享数据行。t.Chdirdatabase 从不共享,因此数据库测试可以并行运行。pgtest - 通过解码来比较 JSON,绝不要按字节比较。比较前应规范化 golden output;每次 产生的 diff 都应由人工审查。
-update - 使用 property(不得 panic、能够 round trip)来 fuzz 处理不可信输入的 parser;
提交 。
testdata/fuzz/ - 通过真实 router 和 middleware 测试 HTTP handler,构建方式应与 相同。
main
Running
运行
Plain runs everything, database tests included: no build tag, no skip,
no make target. Build tags are only for end-to-end suites. Without Docker and without
, fails with a message naming both; don't paper over it with
. Run and before finishing.
go test ./...-shortTEST_DATABASE_URLpgtestt.Skipgo test -race ./...golangci-lint run直接运行 会执行所有测试,包括数据库测试:无需 build tag,无需通过
跳过,也没有 make target。Build tag 仅用于 end-to-end 测试套件。如果没有 Docker,
并且没有设置 , 将失败并给出同时提到两者的消息;不要使用
掩盖此问题。完成前请运行 和 。
go test ./...-shortTEST_DATABASE_URLpgtestt.Skipgo test -race ./...golangci-lint runWhere to look
查询指南
| If you're writing… | Load |
|---|---|
A test that touches Postgres, or a repo with no | |
CI for database tests ( | |
| A unit, table, fake plus contract suite, HTTP, async, golden, fuzz or benchmark test | |
assets/pgtest/assets/golangci-depguard.ymlinternal/pgtest| 如果你正在编写…… | 加载 |
|---|---|
涉及 Postgres 的测试,或仓库尚无 | |
数据库测试的 CI( | |
| unit、table、fake 及 contract suite、HTTP、async、golden、fuzz 或 benchmark 测试 | |
assets/pgtest/assets/golangci-depguard.ymlinternal/pgtestSee also
另请参阅
| Skill | When |
|---|---|
| Everything about the code under test |
| Writing the test first, one behaviour at a time |
| What to test: test kinds, their value, reusable patterns (per repo) |
| Skill | 使用场景 |
|---|---|
| 与被测代码相关的一切内容 |
| 先编写测试,每次只处理一个行为 |
| 测试什么:测试类型、价值及可复用模式(按仓库定义) |