go-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Testing

Go 测试

How a Go test is written, in every repo. What to test is the repo's
testing-policy
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
tdd
.
在每个仓库中,Go 测试都按如下方式编写。如果仓库提供了
testing-policy
,那么测试什么就以它为准: 它使用哪些测试类型、每种测试的价值、功能需要哪些测试,以及随着代码库发展可以复用的 模式。它绝不会重复说明测试如何编写,因此二者不会重叠。对于 red-green-refactor 循环, 请使用
tdd
。

Your 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 forDo this instead
testify
assert
/
require
, any assertion library
Plain
if
plus
t.Errorf
;
cmp.Diff
for anything not a scalar
reflect.DeepEqual
cmp.Diff(want, got)
, reported as
(-want +got)
A table with
wantErr bool
and an
if tt.wantErr
Two tests: a success table and an error table checked with
errors.Is
gomock, mockery,
EXPECT().Times(1)
A stateful in-memory fake; assert on the state it ends in
sqlmock
or a mocked
database/sql
driver
A real Postgres from
internal/pgtest
TestMain
starting a container per package
pgtest.New(t)
: one container per repo, a database per test
TRUNCATE
between tests, shared rows
A fresh database per test, so
t.Parallel()
is safe
time.Sleep
to wait for a goroutine
A channel the code signals, a deadline poll, or
testing/synctest
strings.Contains(err.Error(), "...")
errors.Is
/
errors.As
against an exported sentinel or type
A new test dependencyOnly
github.com/google/go-cmp
; nothing else without asking
以下习惯常常未经提示就出现。编写测试前对照下表检查,结束前再检查一次,因为在长任务的 后期这些习惯容易重新出现。
你会使用改为
testify
assert
/
require
、任何 assertion 库
原生
if
加
t.Errorf
;非标量使用
cmp.Diff
reflect.DeepEqual
cmp.Diff(want, got)
,并按
(-want +got)
报告
含有
wantErr bool
及
if tt.wantErr
判断的 table
两个测试:success table 和使用
errors.Is
检查的 error table
gomock、mockery、
EXPECT().Times(1)
有状态的内存 fake;断言其最终状态
sqlmock
或 mock 后的
database/sql
driver
使用
internal/pgtest
中的真实 Postgres
TestMain
为每个 package 启动 container
pgtest.New(t)
:每个仓库一个 container,每个测试一个 database
测试之间使用
TRUNCATE
、共享数据行
每个测试使用全新的 database,因此可以安全使用
t.Parallel()
使用
time.Sleep
等待 goroutine
使用代码发出信号的 channel、带 deadline 的条件轮询,或
testing/synctest
strings.Contains(err.Error(), "...")
使用
errors.Is
/
errors.As
匹配 exported sentinel 或 type
添加新的测试依赖仅允许
github.com/google/go-cmp
;需要其他依赖时先询问

Rules

规则

  1. Test behaviour through the exported API, in the black-box
    package foo_test
    . Use
    package foo
    only when the unit is unexported by design, and
    export_test.go
    as a last resort.
  2. 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.
  3. 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.
  4. 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.
  5. Test SQL against a real Postgres through
    internal/pgtest
    . If the repo has no
    internal/pgtest
    , install it from this skill's assets: load
    references/integration-db.md
    .
  6. Compare with
    cmp.Diff(want, got)
    and report
    (-want +got)
    . If
    go.mod
    lacks
    github.com/google/go-cmp
    , add it with
    go get
    on 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 with
    ==
    , slices and maps with
    slices.Equal
    /
    maps.Equal
    , structs field by field. Never
    reflect.DeepEqual
    either way.
  7. Failure messages read
    Func(in) = got, want want
    . Match errors with
    errors.Is
    /
    errors.As
    ; a caller that needs to tell errors apart gets an exported sentinel or type.
  8. Use a table only when every case is checked the same way: a slice of structs with a
    name
    field run with
    t.Run(tt.name, ...)
    . A branch on a case field means split the test.
  9. t.Error
    to keep going,
    t.Fatal
    when the rest can't run. Never
    t.Fatal
    from a goroutine the test didn't start on; send the error back on a channel.
  10. Helpers take
    t
    , call
    t.Helper()
    , fail on setup errors and register
    t.Cleanup
    . They set up; they don't assert. No generic
    assertEqual
    .
  11. No
    time.Sleep
    to wait. Wait on a channel, poll a condition with a deadline, or run the test in
    synctest.Test
    (Go 1.25+) when it involves timers.
  12. t.Parallel()
    only for tests that share no state: no globals,
    t.Setenv
    ,
    t.Chdir
    or shared rows. A
    pgtest
    database is never shared, so DB tests can be parallel.
  13. Compare JSON by decoding it, never as bytes. Normalise golden output before comparing; a human reviews every
    -update
    diff.
  14. Fuzz parsers of untrusted input with a property (no panic, round trip); commit
    testdata/fuzz/
    .
  15. Test HTTP handlers through the real router and middleware, built the way
    main
    builds them.
  1. 通过 exported API 测试行为,使用 black-box 的
    package foo_test
    。仅当被测单元在设计上 就是 unexported 时才使用
    package foo
    ;
    export_test.go
    仅作为最后手段。
  2. 断言输出和状态,而不是断言调用了哪些方法。如果一次不改变行为的重构会导致测试失败, 那么它只是 change-detector:应将其删除。
  3. 选择最忠实的 test double:依次为 real、fake、stub、mock。仅当跨 system boundary 的调用 会改变状态时才使用 mock,例如发送邮件或扣取卡款;此时使用手写 recorder,而不是 mocking 库。
  4. fake 必须有状态且诚实:具有相同的 sentinel 和相同的 not-found 行为。同一个 contract suite 应同时针对 fake 和 real implementation 运行。使用嵌入 interface 的单方法 stub 强制进入 error path,绝不要在 fake 上添加 failure knob。
  5. 通过
    internal/pgtest
    针对真实 Postgres 测试 SQL。如果仓库没有
    internal/pgtest
    , 则从此 skill 的 assets 中安装:加载
    references/integration-db.md
    。
  6. 使用
    cmp.Diff(want, got)
    比较,并按
    (-want +got)
    报告。如果
    go.mod
    中没有
    github.com/google/go-cmp
    ,则在第一次修改测试时通过
    go get
    添加它,并在同一次变更中 修复仓库内与此冲突的任何 lint rule 或文档。如果用户或仓库明确禁止添加新依赖 (“stdlib only”),则使用
    ==
    比较标量,使用
    slices.Equal
    /
    maps.Equal
    比较 slice 和 map,并逐字段比较 struct。无论哪种情况,都不要使用
    reflect.DeepEqual
    。
  7. 失败消息应读作
    Func(in) = got, want want
    。使用
    errors.Is
    /
    errors.As
    匹配错误; 如果调用方需要区分错误,应提供 exported sentinel 或 type。
  8. 仅当所有 case 都采用相同方式检查时才使用 table:使用带有
    name
    字段的 struct slice, 并通过
    t.Run(tt.name, ...)
    运行。只要根据 case 字段进行分支,就应拆分测试。
  9. 使用
    t.Error
    继续执行;当后续测试无法运行时使用
    t.Fatal
    。绝不要在测试未启动的 goroutine 中调用
    t.Fatal
    ;应通过 channel 将错误传回。
  10. helper 应接收
    t
    、调用
    t.Helper()
    ,在 setup 出错时失败,并注册
    t.Cleanup
    。 helper 只负责 setup,不负责 assertion。不要编写通用的
    assertEqual
    。
  11. 不要使用
    time.Sleep
    等待。应等待 channel、带 deadline 轮询条件,或者在测试涉及 timer 时使用
    synctest.Test
    (Go 1.25+)。
  12. 仅当测试不共享任何状态时才使用
    t.Parallel()
    :不得使用 global、
    t.Setenv
    、
    t.Chdir
    或共享数据行。
    pgtest
    database 从不共享,因此数据库测试可以并行运行。
  13. 通过解码来比较 JSON,绝不要按字节比较。比较前应规范化 golden output;每次
    -update
    产生的 diff 都应由人工审查。
  14. 使用 property(不得 panic、能够 round trip)来 fuzz 处理不可信输入的 parser; 提交
    testdata/fuzz/
    。
  15. 通过真实 router 和 middleware 测试 HTTP handler,构建方式应与
    main
    相同。

Running

运行

Plain
go test ./...
runs everything, database tests included: no build tag, no
-short
skip, no make target. Build tags are only for end-to-end suites. Without Docker and without
TEST_DATABASE_URL
,
pgtest
fails with a message naming both; don't paper over it with
t.Skip
. Run
go test -race ./...
and
golangci-lint run
before finishing.
直接运行
go test ./...
会执行所有测试,包括数据库测试:无需 build tag,无需通过
-short
跳过,也没有 make target。Build tag 仅用于 end-to-end 测试套件。如果没有 Docker, 并且没有设置
TEST_DATABASE_URL
,
pgtest
将失败并给出同时提到两者的消息;不要使用
t.Skip
掩盖此问题。完成前请运行
go test -race ./...
和
golangci-lint run
。

Where to look

查询指南

If you're writing…Load
A test that touches Postgres, or a repo with no
internal/pgtest
yet
references/integration-db.md
CI for database tests (
TEST_DATABASE_URL
, service containers)
references/integration-db.md
A unit, table, fake plus contract suite, HTTP, async, golden, fuzz or benchmark test
references/examples.md
assets/pgtest/
holds the helper and its own tests;
assets/golangci-depguard.yml
holds the lint rule that keeps testcontainers imports inside
internal/pgtest
.
如果你正在编写……加载
涉及 Postgres 的测试,或仓库尚无
internal/pgtest
references/integration-db.md
数据库测试的 CI(
TEST_DATABASE_URL
、service container)
references/integration-db.md
unit、table、fake 及 contract suite、HTTP、async、golden、fuzz 或 benchmark 测试
references/examples.md
assets/pgtest/
包含 helper 及其自身测试;
assets/golangci-depguard.yml
包含用于将 testcontainers import 限制在
internal/pgtest
内的 lint rule。

See also

另请参阅

SkillWhen
go-idiomatic
Everything about the code under test
tdd
Writing the test first, one behaviour at a time
testing-policy
What to test: test kinds, their value, reusable patterns (per repo)
Skill使用场景
go-idiomatic
与被测代码相关的一切内容
tdd
先编写测试,每次只处理一个行为
testing-policy
测试什么:测试类型、价值及可复用模式(按仓库定义)