<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>测试 on 止语Lab</title>
        <link>https://www.wujiachen.com.cn/tags/%E6%B5%8B%E8%AF%95/</link>
        <description>Recent content in 测试 on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Wed, 15 Apr 2026 19:03:49 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/%E6%B5%8B%E8%AF%95/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>Go 的测试框架不想让你 TDD</title>
            <link>https://www.wujiachen.com.cn/posts/go-tdd-benchmark/</link>
            <pubDate>Wed, 15 Apr 2026 19:03:45 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/go-tdd-benchmark/</guid>
            <description>&lt;img src=&#34;https://www.wujiachen.com.cn/&#34; alt=&#34;Featured image of post Go 的测试框架不想让你 TDD&#34; /&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/cover.png&#34; alt=&#34;封面&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;你有没有觉得，Go 的测试写起来哪里不太对？&lt;/p&gt;&#xA;&lt;p&gt;别急，我不是要教你写测试——这类文章已经够多了。我想和你一起挖一挖：Go 的 testing 包为什么长这样？为什么没有 assert？为什么 Test 和 Benchmark 混在一个包里？为什么连 Go 的创造者自己，在专门讲测试的演讲里，一条 TDD 都没提？&lt;/p&gt;&#xA;&lt;p&gt;这些&amp;quot;为什么&amp;quot;背后，藏着一条刻意拒绝 TDD 教条的设计路线。&lt;/p&gt;&#xA;&lt;h2 id=&#34;一你有没有觉得-go-的测试哪里不对劲&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e4%bd%a0%e6%9c%89%e6%b2%a1%e6%9c%89%e8%a7%89%e5%be%97-go-%e7%9a%84%e6%b5%8b%e8%af%95%e5%93%aa%e9%87%8c%e4%b8%8d%e5%af%b9%e5%8a%b2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、你有没有觉得 Go 的测试哪里不对劲？&#xA;&lt;/h2&gt;&lt;p&gt;想象一下这个场景：你是一个有 Java/Python 背景的开发者，刚开始写 Go。你打开一个 &lt;code&gt;_test.go&lt;/code&gt; 文件，期待看到熟悉的 &lt;code&gt;assert.Equal()&lt;/code&gt;，结果看到的是：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;want&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Add(1, 2) = %d, want %d&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;want&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;你的第一反应可能是：&amp;ldquo;这也太原始了吧？&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;然后你去搜索&amp;quot;Go 测试最佳实践&amp;quot;，发现几乎所有教程都在教你 table-driven tests 和 &lt;code&gt;if + Errorf&lt;/code&gt;——没有人告诉你为什么没有 assert。&lt;/p&gt;&#xA;&lt;p&gt;再然后，你注意到 Benchmark 和 Test 长在同一个包里，用同一套框架，但它们显然在做不同的事。所有教程把它们并列讲解：&amp;ldquo;这是单元测试，这是性能基准。&amp;ldquo;但没有教程告诉你，它们为什么被放在同一个框架里。&lt;/p&gt;&#xA;&lt;p&gt;我跑了一组数据来回答这个问题。&lt;/p&gt;&#xA;&lt;p&gt;我统计了 Go 标准库源码中所有 &lt;code&gt;*_test.go&lt;/code&gt; 文件的测试函数分布。结果是：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;类型&lt;/th&gt;&#xA;          &lt;th&gt;数量&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;占比&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;func TestXxx&lt;/td&gt;&#xA;          &lt;td&gt;9198&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;82.3%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;func BenchmarkXxx&lt;/td&gt;&#xA;          &lt;td&gt;1915&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;17.1%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;func FuzzXxx&lt;/td&gt;&#xA;          &lt;td&gt;56&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.5%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;9198 个 Test，1915 个 Benchmark——Test 是 Benchmark 的 4.8 倍。但更关键的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;标准库中 0 处 testify（Go 社区最流行的第三方断言库）使用&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;0 处第三方 assert 风格调用&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;if 条件判断模式 24971 处&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Go 官方自己都不用 assert。官方不用第三方库是常规操作——但框架自身也没有提供任何 assert 函数，这就是设计选择而非依赖策略了。这不是&amp;quot;忘了加&amp;rdquo;，是故意不加。&lt;/p&gt;&#xA;&lt;p&gt;56 个 Fuzz 函数是 Go 1.18 引入的模糊测试，代表了 Go 测试体系的第三个维度——但那是另一个故事了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch1-crossroad.png&#34; alt=&#34;分叉路口：TDD vs Go testing&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;二没有-assert-的信号&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e6%b2%a1%e6%9c%89-assert-%e7%9a%84%e4%bf%a1%e5%8f%b7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、没有 assert 的信号&#xA;&lt;/h2&gt;&lt;p&gt;&amp;ldquo;没有 assert&amp;quot;这件事，大多数人把它当缺陷。但换个角度看：这是信号。&lt;/p&gt;&#xA;&lt;p&gt;我对比了 Go testing 包和 JUnit 5 Jupiter API 的公共面积：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;维度&lt;/th&gt;&#xA;          &lt;th&gt;Go testing&lt;/th&gt;&#xA;          &lt;th&gt;JUnit 5 Jupiter&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;核心 API 数量&lt;/td&gt;&#xA;          &lt;td&gt;~60&lt;/td&gt;&#xA;          &lt;td&gt;150+（含关联包）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;断言机制&lt;/td&gt;&#xA;          &lt;td&gt;if + Errorf（手写）&lt;/td&gt;&#xA;          &lt;td&gt;Assertions 类（200+ 方法）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;生命周期回调&lt;/td&gt;&#xA;          &lt;td&gt;无注解&lt;/td&gt;&#xA;          &lt;td&gt;@BeforeAll/@BeforeEach 等&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;参数化测试&lt;/td&gt;&#xA;          &lt;td&gt;t.Run + table&lt;/td&gt;&#xA;          &lt;td&gt;@ParameterizedTest 系列&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;扩展机制&lt;/td&gt;&#xA;          &lt;td&gt;t.Helper() 自建&lt;/td&gt;&#xA;          &lt;td&gt;Extension API 完整体系&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Go 的 60 个 API 覆盖了测试、基准、模糊测试三个领域。JUnit 5 仅测试领域就用了 150+ API。&lt;/p&gt;&#xA;&lt;p&gt;这是&amp;quot;刻意不做&amp;rdquo;。&lt;/p&gt;&#xA;&lt;p&gt;那多写的代码到底有多少？用 5 个常见断言场景做了对比——纯 testing 包 51 行，testify 32 行，差 37%。摩擦真实存在，但不是翻倍级别的差距。&lt;/p&gt;&#xA;&lt;p&gt;更有意思的是这个数字：Go 标准库中 &lt;code&gt;t.Helper()&lt;/code&gt; 被调用了 2559 次。&lt;code&gt;t.Helper()&lt;/code&gt; 是什么？它是一个标记函数——在你自己写的断言辅助函数里调用它，Go 的测试框架就能在报错时显示调用者的行号，而不是辅助函数内部的行号。&lt;/p&gt;&#xA;&lt;p&gt;Go 需要断言，但让你自己建，框架只帮你把行号搞对。&lt;strong&gt;断言是你自己的事，框架不替你做。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch2-toolbox-compare.png&#34; alt=&#34;工具箱对比：JUnit vs Go testing&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;三benchmark-和-test-不是一家人&#34;&gt;&lt;a href=&#34;#%e4%b8%89benchmark-%e5%92%8c-test-%e4%b8%8d%e6%98%af%e4%b8%80%e5%ae%b6%e4%ba%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、Benchmark 和 Test 不是一家人&#xA;&lt;/h2&gt;&lt;p&gt;大多数教程把 Test 和 Benchmark 并列教，好像它们只是 testing 包的两个功能。但仔细看 API 就会发现，它们做的事情完全不同：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;testing.T 的独有方法&lt;/strong&gt;：Chdir、Deadline、Parallel、Setenv&#xA;——全是关于&amp;quot;验证正确性&amp;quot;的。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;testing.B 的独有方法&lt;/strong&gt;：Loop、ReportAllocs、ReportMetric、ResetTimer、RunParallel、SetBytes、StartTimer、StopTimer&amp;hellip;&#xA;——全是关于&amp;quot;度量性能&amp;quot;的。&lt;/p&gt;&#xA;&lt;p&gt;它们通过 &lt;strong&gt;testing.TB 接口&lt;/strong&gt;——一个定义了 Log/Error/Fatal/Cleanup/TempDir 等基础行为的接口——共享了公共能力，但核心能力完全正交。T 验证&amp;quot;对不对&amp;rdquo;，B 度量&amp;quot;快不快&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;这是设计哲学。&lt;/p&gt;&#xA;&lt;p&gt;Go 把正确性验证和性能度量放在同一个框架里，不是图省事。从结果看，两者在同一个框架里这件事本身就说明性能在 Go 的测试体系里不是二等公民——17.1% 的 Benchmark 比例不是装饰。相比之下，JUnit 生态中 Benchmark 测试的占比远低于 Go 标准库的 17.1%——大多数 Java 项目的基准测试是事后补的，不是和功能测试一起写的。&lt;/p&gt;&#xA;&lt;p&gt;对比一下：在 JUnit 生态里，性能测试是 JMH 的活，和 JUnit 是两个世界。在 pytest 里，benchmark 是 pytest-benchmark 插件，和 pytest 本体也是分开的。Go 是少数把 Test 和 Benchmark 原生整合在同一个框架里的主流语言。&lt;/p&gt;&#xA;&lt;p&gt;这个设计不是偶然。可测量才能优化——如果 Benchmark 和 Test 是两个框架，开发者会倾向于&amp;quot;先写功能测试，有空再补性能测试&amp;quot;。但 Go 把它们放在同一个入口——&lt;code&gt;go test&lt;/code&gt; 跑 Test，&lt;code&gt;go test -bench&lt;/code&gt; 跑 Benchmark——框架统一，命令一体，性能测试的门槛被降到了最低。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch3-orthogonal-coords.png&#34; alt=&#34;正交坐标系：Test vs Benchmark&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;四并发tdd-的最大盲区&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e5%b9%b6%e5%8f%91tdd-%e7%9a%84%e6%9c%80%e5%a4%a7%e7%9b%b2%e5%8c%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、并发：TDD 的最大盲区&#xA;&lt;/h2&gt;&lt;p&gt;Go 最引以为傲的特性——goroutine 和 channel——恰好是 TDD 最覆盖不了的领域。&lt;/p&gt;&#xA;&lt;p&gt;为什么？因为并发代码的测试面临一个根本困境：&lt;strong&gt;&amp;ldquo;还没发生&amp;quot;和&amp;quot;永远不会发生&amp;quot;之间没有可靠的区分&lt;/strong&gt;。你想测试&amp;quot;这个 goroutine 不会往 channel 里写数据&amp;rdquo;，但你怎么判断？等 1 秒？等 10 秒？等 1 分钟？&lt;/p&gt;&#xA;&lt;p&gt;Go 官方博客说得很直白：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&amp;ldquo;We can make the test less flaky at the expense of making it slower, and we can make it less slow at the expense of making it flakier, but we can&amp;rsquo;t make it both fast and reliable.&amp;rdquo;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;翻译：快和可靠，你只能选一个。&lt;/p&gt;&#xA;&lt;p&gt;更关键的是时间线：&lt;strong&gt;Go 1.24（2025 年 2 月发布）才实验性引入了 &lt;code&gt;testing/synctest&lt;/code&gt; 包&lt;/strong&gt;。这意味着 Go 从 2009 年开源到 2025 年 synctest 引入，16 年间没有官方的确定性并发测试方案。&lt;/p&gt;&#xA;&lt;p&gt;16 年。&lt;/p&gt;&#xA;&lt;p&gt;日常开发中，&lt;code&gt;go test -race&lt;/code&gt; 是发现数据竞争的第一道防线——但它只能发现已触发的竞争，不能证明竞争不存在。&lt;/p&gt;&#xA;&lt;p&gt;这不是疏忽——并发测试是一个硬问题。synctest 是 Go 对这个硬问题的第一个官方回答。它的思路很 Go：给你一个确定性的调度环境，让你在测试里精确控制 goroutine 的执行顺序，不再靠 &lt;code&gt;time.Sleep&lt;/code&gt; 碰运气。&lt;/p&gt;&#xA;&lt;p&gt;这恰恰说明了 Go 测试哲学的本质：&lt;strong&gt;不是拒绝测试，而是在找到正确方案之前，宁可不提供。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch4-concurrency-maze.png&#34; alt=&#34;并发迷宫：goroutine难以捕捉&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;五russ-cox-的选择&#34;&gt;&lt;a href=&#34;#%e4%ba%94russ-cox-%e7%9a%84%e9%80%89%e6%8b%a9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、Russ Cox 的选择&#xA;&lt;/h2&gt;&lt;p&gt;上面的分析都是我的解读——Go 团队自己怎么说？&lt;/p&gt;&#xA;&lt;p&gt;Russ Cox，Go 项目的技术负责人，在 2023 年的 GopherCon AU 上做了一个演讲叫&amp;quot;Go Testing By Example&amp;quot;。这个演讲给出了 20 条测试建议。&lt;/p&gt;&#xA;&lt;p&gt;我逐条读完了。20 条建议里，没有一条提到&amp;quot;TDD&amp;quot;、&amp;ldquo;Red-Green-Refactor&amp;quot;或&amp;quot;测试先行&amp;rdquo;。在一个专门讲测试的演讲中，TDD 是业界最知名的测试方法论——完全不提意味着主动选择不推荐。这是回避，不是缺席。&lt;/p&gt;&#xA;&lt;p&gt;他推荐的是什么？&lt;strong&gt;参考实现对比法&lt;/strong&gt;——写一个简单但显然正确的参考实现，然后穷举小规模输入，比较生产代码和参考实现的输出。这意味着你不需要预测每个用例的期望值——你只需要一个&amp;quot;笨但对&amp;quot;的版本做对照。&lt;/p&gt;&#xA;&lt;p&gt;这和经典 TDD 的断言模式完全不同。TDD 要求你为每个用例预测期望输出（assert），Russ Cox 建议你写一个参考实现然后比较等价性。TDD 是&amp;quot;我知道答案&amp;quot;，Russ Cox 是&amp;quot;我有一个简单版本可以对照&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;他还说了一句很关键的话：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&amp;ldquo;Coverage is no substitute for thought.&amp;rdquo;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;覆盖率不能替代思考。这句话直接指向了 TDD 的一个副作用：当你机械地追求红绿循环，你关注的是&amp;quot;测试有没有通过&amp;quot;，而不是&amp;quot;我在测什么&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;另一位 Go 社区的重要声音 Peter Bourgon（InfluxDB 联合创始人）更直接——他认为好的可观测性比好的测试更重要，100% 测试覆盖率&amp;quot;几乎肯定是适得其反的&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;你不需要同意他们。但 20 条建议 0 条 TDD——这不是演讲者忘了提，是在他看来，测试有比 TDD 更实用的做法。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch5-timeline.png&#34; alt=&#34;Go testing 包进化时间线&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;六你的项目需要什么层次的验证&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e4%bd%a0%e7%9a%84%e9%a1%b9%e7%9b%ae%e9%9c%80%e8%a6%81%e4%bb%80%e4%b9%88%e5%b1%82%e6%ac%a1%e7%9a%84%e9%aa%8c%e8%af%81&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、你的项目需要什么层次的验证&#xA;&lt;/h2&gt;&lt;p&gt;说了这么多，我不是要说服你&amp;quot;Go 不该 TDD&amp;quot;。我想给你一个判断框架。&lt;/p&gt;&#xA;&lt;p&gt;回到核心论点：&lt;strong&gt;Go 的测试哲学是&amp;quot;测试必存，但不必先行&amp;quot;&lt;/strong&gt;。测试的质量和存在比编写顺序更重要。&lt;/p&gt;&#xA;&lt;p&gt;那你的项目该走哪条路？&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://img.wujiachen.com.cn/go-tdd-benchmark/ch6-decision-tree.png&#34; alt=&#34;决策树：项目测试策略选择&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;如果你在探索阶段——不确定 API 长什么样，先写测试就是在给不存在的接口写规约。TDD 的红绿循环确实能帮你理清思路，但摩擦也真实存在（多写 37% 代码）。&lt;/p&gt;&#xA;&lt;p&gt;设计清晰的团队则相反——已经知道接口长什么样，先写测试就是给自己加摩擦。Go 惯用路线（先实现再验证）效率更高，尤其是当你需要 RWMutex、需要考虑并发安全时——这些在 TDD 的小步迭代中很难自然生长出来。&lt;/p&gt;&#xA;&lt;p&gt;性能敏感的项目多了一步：先跑 Benchmark 再决定实现方案——性能测试先行，功能测试后补。&lt;/p&gt;&#xA;&lt;p&gt;并发代码是另一个故事——TDD 帮不了你太多。Go 1.24 的 synctest 提供了确定性并发测试的起点，但在它成熟之前，可观测性（日志、metrics、trace）可能比测试更能帮你发现并发 bug。&lt;/p&gt;&#xA;&lt;p&gt;现实项目往往同时踩中几个维度——这时候优先级就看哪个维度最可能出错。&lt;/p&gt;&#xA;&lt;p&gt;Go 标准库的选择是：0 testify，2559 个 t.Helper()，9198 个 Test，1915 个 Benchmark。这是自建断言体系、性能非二等公民、测试必存但不必先行的实践证明。&lt;/p&gt;&#xA;&lt;p&gt;Go 选了一条路，这条路的核心逻辑是：给你最小的工具集，让你自己决定怎么用。23k+ stars 的 testify 说明社区自己填上了 assert 的空缺——而 Go 官方没有反对，也没有收编。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Go 的 testing 包不是 TDD 框架做得不好，而是一套不想让你 TDD 的设计。&lt;/strong&gt; 理解这个意图，你才能为自己的项目做出正确的选择。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
