<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 测试框架</title>
	<atom:link href="http://tonybai.com/tag/%e6%b5%8b%e8%af%95%e6%a1%86%e6%9e%b6/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 25 May 2026 23:53:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>AI 编码工具“真香”还是“智商税”？一位资深码农的“挑衅”与Go开发者的反思</title>
		<link>https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming/</link>
		<comments>https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming/#comments</comments>
		<pubDate>Tue, 03 Jun 2025 13:37:41 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[CodeAssist]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[Cursor]]></category>
		<category><![CDATA[fly.io]]></category>
		<category><![CDATA[formatter]]></category>
		<category><![CDATA[gemini]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[linter]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Trae]]></category>
		<category><![CDATA[大模型]]></category>
		<category><![CDATA[幻觉]]></category>
		<category><![CDATA[智能体]]></category>
		<category><![CDATA[测试框架]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4781</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming 大家好，我是Tony Bai。 最近，fly.io 博客上该公司开发者 Thomas Ptacek 的一篇题为《My AI Skeptic Friends Are All Nuts》的文章，在开发者社区掀起了不小的波澜，一度登顶HN。Ptacek 以一位自称“严肃开发者”（从C语言到Go、Rust均有涉猎）的口吻，向那些对 AI 辅助编程持怀疑态度的“聪明朋友们”发出了略带“挑衅”的宣言：“即使 LLM 今天停止所有进展，它仍然是我职业生涯中发生的第二重要的事情” 。 这篇文章的观点之鲜明、论证之犀利，让我印象深刻。恰逢 前期Google I/O 2025 大会再次展示了 Gemini 等 AI 模型在编码领域的惊人进展，我们不禁要问：AI 编码工具，究竟是能极大提升生产力的“真香”利器，还是又一轮被过度炒作的“智商税”？作为开发者，特别是 Gopher，我们又该如何看待和应对这场正在发生的变革？ 在这篇文章中，我就和大家一起来看看 Thomas Ptacek 对AI辅助编程演进的犀利观点以及他的反思。看看你是否认同他的想法。 误区澄清：现代 AI 辅助编程早已不是“复制粘贴” Ptacek 在文章开篇就点出了一个关键问题：很多人对 AI 辅助编程的印象，还停留在半年前甚至两年前的水平。他写道：“如果你在6个月前（或者，天哪，两年前用Copilot的时候）尝试使用LLM编码并失败了，那么你并没有在做大多数严肃的LLM辅助编码者正在做的事情”。 那么，现在“严肃的LLM辅助编码者”在做什么呢？Ptacek 强调，他们使用的是 Agent (智能体)。这些 AI Agent 不再仅仅是根据提示生成代码片段让你复制粘贴，它们能够： 自主地在你的代码库中进行探索。 直接创建和修改文件。 运行各种工具， 如编译器、测试框架、linter、formatter [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/provocation-about-ai-assisted-programming-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming">本文永久链接</a> &#8211; https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming</p>
<p>大家好，我是Tony Bai。</p>
<p>最近，fly.io 博客上该公司开发者 Thomas Ptacek 的一篇题为《<a href="https://fly.io/blog/youre-all-nuts/">My AI Skeptic Friends Are All Nuts</a>》的文章，在开发者社区掀起了不小的波澜，一度登顶HN。Ptacek 以一位自称“严肃开发者”（从C语言到Go、Rust均有涉猎）的口吻，向那些对 AI 辅助编程持怀疑态度的“聪明朋友们”发出了略带“挑衅”的宣言：<strong>“即使 LLM 今天停止所有进展，它仍然是我职业生涯中发生的第二重要的事情”</strong> 。</p>
<p>这篇文章的观点之鲜明、论证之犀利，让我印象深刻。恰逢 前期<a href="https://tonybai.com/2025/05/25/go-at-googleio-2025/">Google I/O 2025 大会</a>再次展示了 Gemini 等 AI 模型在编码领域的惊人进展，我们不禁要问：AI 编码工具，究竟是能极大提升生产力的“真香”利器，还是又一轮被过度炒作的“智商税”？作为开发者，特别是 Gopher，我们又该如何看待和应对这场正在发生的变革？</p>
<p>在这篇文章中，我就和大家一起来看看 Thomas Ptacek 对AI辅助编程演进的犀利观点以及他的反思。看看你是否认同他的想法。</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<h2>误区澄清：现代 AI 辅助编程早已不是“复制粘贴”</h2>
<p>Ptacek 在文章开篇就点出了一个关键问题：很多人对 AI 辅助编程的印象，还停留在半年前甚至两年前的水平。他写道：“如果你在6个月前（或者，天哪，两年前用Copilot的时候）尝试使用LLM编码并失败了，那么你并没有在做大多数严肃的LLM辅助编码者正在做的事情”。</p>
<p>那么，现在“严肃的LLM辅助编码者”在做什么呢？Ptacek 强调，他们使用的是 <strong>Agent (智能体)</strong>。这些 AI Agent 不再仅仅是根据提示生成代码片段让你复制粘贴，它们能够：</p>
<ul>
<li><strong>自主地在你的代码库中进行探索。</strong></li>
<li><strong>直接创建和修改文件。</strong></li>
<li><strong>运行各种工具，</strong> 如编译器、测试框架、linter、formatter 等。</li>
<li><strong>与 Git 等版本控制系统交互。</strong></li>
<li><strong>根据编译和测试结果进行迭代和修正。</strong></li>
<li><strong>通过 MCP 或类似机制调用你设置的任意工具。</strong></li>
</ul>
<p>Ptacek强调：如果你对 AI 编码的印象还停留在 ChatGPT 网页上简单问答然后手动复制代码，那么你可能真的低估了当前 AI Agent 所能达到的自动化和智能化水平。</p>
<h2>AI Agent 如何提升编码效率？Ptacek 的“积极案例”</h2>
<p>Ptacek 认为，LLM（通过 Agent）能够极大地提升编码效率，主要体现在以下几个方面：</p>
<ol>
<li>
<p><strong>处理“乏味代码”：</strong> LLM 可以编写你需要编写的大部分乏味代码。而大多数项目中的大多数代码都是乏味的。这能让开发者从重复性的工作中解放出来，更快地进入“调整代码并立即看到效果更好”的“黄金时刻 (golden moment)”，获得即时反馈的“多巴胺冲击”。</p>
</li>
<li>
<p><strong>克服项目启动的“惯性”：</strong> 面对一个新项目，繁琐的初始设置、依赖管理、基础架构搭建等往往令人望而却步。LLM Agent 可以被指示去“搞定这些破事，直接将你带到“事情几乎可以工作”的阶段。</p>
</li>
<li><strong>自动化“苦差事”：</strong> 那些你不想做但又必须做的“脏活累活”，比如大规模的单元测试重构，完全可以交给 AI Agent 在虚拟机里折腾几个小时，然后带着一个 PR 回来。这反而会“逼迫”你去做“真正的工作 (real work)”。</li>
</ol>
<h2>回应常见的质疑：Ptacek 的“辩护”</h2>
<p>Ptacek 在文章中也针对开发者对 AI 编码的常见质疑进行了犀利的回击，这些回应也为我们思考 Go 语言在 AI 时代的定位提供了新的视角。</p>
<ul>
<li><strong>关于代码质量与审查责任——“你根本不知道它写的是什么！”</strong></li>
</ul>
<p>Ptacek强调，<strong>开发者始终对合并到 main 分支的代码负责，无论是否使用 LLM。</strong> LLM 生成的代码是“可知的”，你需要阅读它，甚至花时间将其调整为你自己的风格。如果连 LLM 生成的“乏味、重复”的代码都难以理解和消化，那可能是开发人员的“技能问题”。</p>
<ul>
<li><strong>关于“幻觉 (hallucination)”问题——“它会编造不存在的API！”</strong></li>
</ul>
<p>Ptacek 认为，对于编程而言，Agent 通过工具链（linting、编译、运行测试）形成的闭环反馈，已经（或多或少地）解决了“幻觉”问题。“如果它们的LLM编造了一个新的函数签名，Agent会看到错误。它们将其反馈给LLM，LLM会说‘哦，是的，我完全是编造的’，然后重试”。这里不能不提到** Go 语言的<strong>快速编译</strong>特性，使得这种“试错-反馈-修正”的闭环能够非常高效地运转。同时，Go 强大的标准库和清晰的 API 设计，是否也能减少 LLM“编造”API 的概率，或者使其更容易被工具链检测出来。</p>
<ul>
<li><strong>关于“代码像初级开发者写的”——“质量太差！”</strong></li>
</ul>
<p>Ptacek 回应：“一个实习生一个月要花20美元吗？因为 Cursor.ai 就是这个价钱”。他认为，高级开发者的职责之一就是让能力稍逊的编码者（无论是人类还是“智能体”）变得高效。<strong>使用好 Agent 本身就是一项技能和一项涉及提示、索引和（尤其是）工具链的工程项目。</strong> LLM 只有在你允许的情况下才会产生劣质代码。</p>
<ul>
<li><strong>关于“不擅长特定语言 (如 Rust)”——“它写不了我的 Rust！”</strong></li>
</ul>
<p>Ptacek 认为这更多是语言生态和工具链成熟度的问题，而非 LLM 能力的根本缺陷。他特别指出：“我主要用 Go 工作……Go 恰到好处的类型安全、广泛的标准库以及推崇（通常是重复性）惯用法的文化。LLM 在生成 Go 代码方面表现出色。” 想必很多Go开发者也有着与Ptacek相同的感受，<strong>这是 Go 语言在 AI 辅助编程时代的一个显著优势！</strong> Go 的简洁性、明确性、强大的标准库覆盖、以及社区对代码规范和惯用法的重视（例如 Effective Go），使得 Go 代码的模式相对统一和可预测，这为 LLM 的学习和生成提供了极大的便利。</p>
<h2>对“手工艺精神”与“平庸代码”的再思考</h2>
<p>Ptacek 对软件开发中的“手工艺精神”和对“平庸代码”的过度排斥也提出了批判。</p>
<ul>
<li>
<p>他认为：专业软件开发者的工作是用代码为人们解决实际问题。在日常工作中，我们不是工匠。过度追求代码的“优雅”而忽视实际产出，可能是“自我安慰的yak-shaving（指做无关紧要的琐事）”。</p>
</li>
<li>
<p>对于“平庸代码”，他认为：开发者都喜欢对代码自吹自擂。他们担心LLM降低了质量的“天花板”。也许吧。但它们也提高了“地板”。LLM 生成的“平庸但彻底”的代码，可能比人类开发者“抖机灵”但引入缺陷的代码更有价值。</p>
</li>
</ul>
<p>这也引发我们思考：在追求卓越工程的同时，我们是否也应该更务实地看待不同场景下对代码质量的不同要求？LLM 是否能帮助我们更高效地处理那些“允许平庸”但又耗时耗力的部分，从而让我们能将精力投入到真正需要人类智慧和创造力的核心工作中？</p>
<h2>Go 开发者如何拥抱 AI Agent 的时代？</h2>
<p>Ptacek 的文章，无论你是否完全认同其所有观点，都为我们描绘了一个 AI Agent 深度参与软件开发的未来图景。作为 Gopher，我们应该如何应对？</p>
<ol>
<li><strong>更新认知，拥抱变化：</strong> 首先要认识到，现代 AI 辅助编程已经远超简单的代码补全。应该主动去了解和体验基于 Agent 的编码工具。</li>
<li><strong>学习与 AI Agent 高效协作：</strong> 掌握提示工程技巧，学会如何清晰地向 Agent表达需求、提供上下文、引导其生成和修改代码。</li>
<li><strong>发挥 Go 语言的优势：</strong> 利用 Go 的简洁性、强大的标准库、快速的编译和测试工具链，为 AI Agent 构建高效的开发和反馈环境。思考如何让 Go 代码对 AI 更“友好”。</li>
<li><strong>提升自身的核心价值：</strong> 将精力更多地投入到 AI 难以替代的领域：复杂系统设计、架构决策、需求理解与抽象、创新思维、以及对 Go 底层原理和并发模型的深刻理解。</li>
<li><strong>参与构建 Go 的 AI Agent 生态：</strong> Go 语言本身非常适合构建 CLI 工具和后端服务。我们是否可以利用 Go 来创建更强大的、针对 Go 开发的 Agent 辅助工具或平台？</li>
</ol>
<h2>小结：保持开放，主动实践，与 AI 共舞</h2>
<p>AI 编码工具究竟是“真香”还是“智商税”？或许答案因人而异，也因我们如何使用它而异。但 Thomas Ptacek 的“挑衅”至少提醒我们，不能用静止的眼光看待飞速发展的技术。</p>
<p>AI 辅助编程的浪潮已然到来。对于我们 Gopher 而言，Go 语言的特性使其在这波浪潮中具有独特的优势。与其固守过去的经验和偏见，不如保持开放的心态，主动去实践和探索，让 AI Agent 成为我们提升自身能力、加速项目交付、并最终能专注于更有创造性工作的强大伙伴。</p>
<p>毕竟，正如 Ptacek 所说，当他那些“聪明的怀疑论朋友们”最终接受并开始使用这些工具时，他们将会让编码 Agent 比今天强大得多。</p>
<p>而我们，又怎能置身事外呢？</p>
<hr />
<p><strong>聊一聊，也帮个忙：</strong></p>
<ul>
<li><strong>你目前在工作中使用 AI 辅助编程工具（如 Copilot, Cursor.ai, Gemini Code Assist，Trae等）的体验如何？它在哪些方面帮助了你，又有哪些不足？</strong></li>
<li><strong>Ptacek 文章中对 AI 编码的哪个观点让你印象最深刻？你同意还是反对？为什么？</strong></li>
<li><strong>你认为 Go 语言在 AI 辅助编程时代，还有哪些可以进一步优化的方向，以更好地与 LLM Agent 结合？</strong></li>
</ul>
<p>欢迎在<strong>评论区</strong>留下你的思考和经验。如果你觉得这篇文章提供了一个值得探讨的视角，也请<strong>转发给你身边的开发者朋友们</strong>，一起参与这场关于 AI 与编程未来的讨论！</p>
<p><strong>想与我进行更深入的 Go 语言、AI 赋能开发与技术趋势交流吗？</strong> 欢迎加入我的<strong>“Go &amp; AI 精进营”知识星球</strong>。</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<p>我们星球见！</p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/06/03/provocation-about-ai-assisted-programming/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>与Thorsten Ball的共鸣：Go作为教学语言在技术写作中的优越性</title>
		<link>https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing/</link>
		<comments>https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing/#comments</comments>
		<pubDate>Tue, 08 Oct 2024 22:14:22 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[go1.23]]></category>
		<category><![CDATA[Go1.6]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golangci-lint]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GoProgrammingFromBeginnerToMaster]]></category>
		<category><![CDATA[gotest]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[Interpreter]]></category>
		<category><![CDATA[loopvar]]></category>
		<category><![CDATA[sourcegraph]]></category>
		<category><![CDATA[teaching]]></category>
		<category><![CDATA[ThorstenBall]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[可读性]]></category>
		<category><![CDATA[向后兼容性]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[测试框架]]></category>
		<category><![CDATA[用Go语言自制编译器]]></category>
		<category><![CDATA[用Go语言自制解释器]]></category>
		<category><![CDATA[简洁性]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4327</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing 近日，两本备受好评的畅销书《用Go语言自制解释器(Writing An Interpreter In Go)》和《用Go语言自制编译器(Writing A Compiler In Go)》的作者、前Sourcegraph工程师索斯藤·鲍尔（Thorsten Ball）发表了一篇名为“Glad I did it in Go”的文章。在这篇文章中，Thorsten表达了他对8年前编写这两本书时选择Go语言作为教学语言的庆幸之情。 2021年12月17日，我的第一本Go技术图书《Go语言精进之路vol1和vol2》出版了，至今好像是已经是第4次重印了(修正了勘误表中的所有瑕疵)。作为该书作者，当我读到Thorsten Ball的这篇回顾文章时，我感到了一种强烈的共鸣，其中的许多观点与我的不谋而合。尽管我们的书主题不同，但我们都体会到了选择Go语言作为教学语言进行技术写作的巨大优势。 在这篇文章中，在《Go语言精进之路》出版即将三年之际，我想借此机会分享我的thoughts，探讨Go语言如何为技术作者提供了独特的优势。 1. Go的稳定性和向后兼容性 首当其冲的优势就是Go的稳定性和向后兼容性，它们给我留下了深刻的印象。三年快过去了，当初《Go语言精进之路》中使用Go 1.16版本编写的代码示例，在最新的Go 1.23版本中仍然可以完美运行，几乎不需要任何修改。这种稳定性不仅让我的书保持了长期的相关性，也让读者能够轻松地在不同版本的Go环境中实践书中的内容。正如Thorsten所提到的，他只需添加一个简单的go.mod文件，就能使8年前的代码适应新的Go版本依赖管理和构建模式，这种对更新需求的最小化，在快速发展的编程语言世界中，实属难能可贵。 Go的稳定性还体现在语法特性上，《Go语言精进之路》一书中讲解的语法和惯用法在今天依然是完全有效的，除了loopvar的语义变更可能会让极少的内容略显“过时”。Thorsten也提到了这种稳定性的好处：8年前的代码运行golangci-lint得到的警告与当时是相同的(便于读者复现书中的情形)，其书中代码风格仍然符合现在的Go惯例写法。 此外，Thorsten还提及了Go工具链和标准库的稳定性：8年来Go的工具链几乎没有变化，新手容易上手。像Thorsten一样，我也发现Go的开发环境和工具在多年来保持了惊人的一致性。这意味着书中介绍的开发实践和工具使用方法始终有效，大大降低了内容过时的风险。对技术作者来说，这种稳定性是无价的，它允许我们专注于概念和最佳实践，而不是不断更新工具相关的内容。 以上Go的这些稳定性和向后兼容，让我的书中的内容具有了更为持久的生命力，书中内容的价值变得更为长效，也大大减轻了作者对书籍维护和更新的负担，在技术书籍的生命周期中，这一点尤为宝贵。 2. Go的简洁性和可读性 其次，在编写《Go语言精进之路》时，我发现Go的简洁性和可读性为技术写作带来了极大的帮助。许多读者反馈说，即使他们之前没有Go的经验，也能快速上手并理解书中的概念。这种简洁和直观性让Go也成为了编写教程和教学材料的理想选择。此外，正如在项目中所经历的那样，Thorsten也强调了Go语言的语法简单直观在教学过程中的所展现的优势，它既能让初学者快速入门，也能使得书中关于解析器和编译器实现的核心思路能够被清晰地传达给读者，即便在探讨复杂的概念时，也能保持清晰明了。 同时，Thorsten强调内置的gofmt带来的通用风格和测试框架也简化了学习过程，让读者可以专注于理解核心概念和解释器/编译器的实现，而不是纠结于环境设置和代码风格。 3. Go代码易于理解和翻译 Thorsten提到许多读者在从未写过Go代码的前提下，能够将他的Go代码轻松翻译成其他语言，这体现了Go在跨语言学习和理解方面的优势，有利于扩大了书籍的受众群体，而不仅限于Go开发者。Go社区的多样性和活跃度也为此做出了重要贡献，各种语言背景的开发者都能在Go中找到共鸣。这种跨语言的适应性不仅拓展了书籍的应用范围，也增强了其教育价值。 4. 小结 回顾这三年，我与Thorsten一样，越发感慨选择Go作为教学语言进行技术写作是多么明智的决定。当然，我这本书本身就是围绕Go语言展开的^_^，这与Thorsten的书籍主题有所不同。Thorsten在8年前高瞻远瞩地选择Go，才着实令人钦佩，要知道那时的Go刚刚发布1.6版本。Go语言不仅是一个强大的编程工具，更是技术作者的得力助手。它的稳定性、简洁性、易理解性和良好的翻译能力，以及稳定优秀的工具链，为我们创造高质量、长寿命的技术内容提供了坚实的基础。 与Thorsten Ball一样，我也为选择Go感到庆幸。看到自己的作品能够持续为读者提供价值，这种成就感是无可比拟的。Go语言在技术写作中展现出的优越性，不仅使我们的书籍能够经受时间的考验，还为整个技术写作领域树立了新的参考标杆。 展望未来，我相信Go语言将继续是技术作者的优秀选择。它不仅是一种编程语言，更是连接作者、读者与技术的桥梁。 Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！ 著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个链接地址：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。 Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com 我的联系方式： 微博(暂不可用)：https://weibo.com/bigwhite20xx 微博2：https://weibo.com/u/6484441286 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/resonating-with-thorsten-ball-on-go-in-technical-writing-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing">本文永久链接</a> &#8211; https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing</p>
<p>近日，两本备受好评的畅销书《<a href="https://book.douban.com/subject/35909085/">用Go语言自制解释器(Writing An Interpreter In Go)</a>》和《<a href="https://book.douban.com/subject/35909089/">用Go语言自制编译器(Writing A Compiler In Go)</a>》的作者、前<a href="https://github.com/sourcegraph/sourcegraph-public-snapshot">Sourcegraph</a>工程师<a href="https://thorstenball.com/">索斯藤·鲍尔（Thorsten Ball）</a>发表了一篇名为“<a href="https://registerspill.thorstenball.com/p/glad-i-did-it-in-go">Glad I did it in Go</a>”的文章。在这篇文章中，Thorsten表达了他对8年前编写这两本书时选择Go语言作为教学语言的庆幸之情。</p>
<p><img src="https://tonybai.com/wp-content/uploads/resonating-with-thorsten-ball-on-go-in-technical-writing-2.png" alt="" /></p>
<p>2021年12月17日，我的第一本Go技术图书<a href="https://item.jd.com/13694000.html">《Go语言精进之路vol1和vol2》</a>出版了，至今好像是已经是第4次重印了(修正了<a href="https://github.com/bigwhite/GoProgrammingFromBeginnerToMaster/blob/main/errata.md">勘误表</a>中的所有瑕疵)。作为该书作者，当我读到Thorsten Ball的这篇回顾文章时，我感到了一种强烈的共鸣，其中的许多观点与我的不谋而合。尽管我们的书主题不同，但<strong>我们都体会到了选择Go语言作为教学语言进行技术写作的巨大优势</strong>。</p>
<p><img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p>在这篇文章中，在《<a href="https://book.douban.com/subject/35720728">Go语言精进之路</a>》出版即将三年之际，我想借此机会分享我的thoughts，探讨Go语言如何为技术作者提供了独特的优势。</p>
<h2>1. Go的稳定性和向后兼容性</h2>
<p>首当其冲的优势就是Go的稳定性和向后兼容性，它们给我留下了深刻的印象。三年快过去了，当初《Go语言精进之路》中使用<a href="https://tonybai.com/2021/02/25/some-changes-in-go-1-16">Go 1.16版本</a>编写的<a href="https://github.com/bigwhite/GoProgrammingFromBeginnerToMaster/">代码示例</a>，在最新的<a href="https://tonybai.com/2024/08/19/some-changes-in-go-1-23/">Go 1.23版本</a>中仍然可以完美运行，几乎不需要任何修改。这种稳定性不仅让我的书保持了长期的相关性，也让读者能够轻松地在不同版本的Go环境中实践书中的内容。正如Thorsten所提到的，他只需添加一个简单的<a href="https://go.dev/ref/mod">go.mod文件</a>，就能使8年前的代码适应新的<a href="https://tonybai.com/tag/gomodule">Go版本依赖管理和构建模式</a>，这种对更新需求的最小化，在快速发展的编程语言世界中，实属难能可贵。</p>
<p>Go的稳定性还体现在语法特性上，《Go语言精进之路》一书中讲解的语法和惯用法在今天依然是完全有效的，除了<a href="https://tonybai.com/2024/02/18/some-changes-in-go-1-22/">loopvar的语义变更</a>可能会让极少的内容略显“过时”。Thorsten也提到了这种稳定性的好处：8年前的代码运行<a href="https://github.com/golangci/golangci-lint">golangci-lint</a>得到的警告与当时是相同的(便于读者复现书中的情形)，其书中代码风格仍然符合现在的Go惯例写法。</p>
<p>此外，Thorsten还提及了Go工具链和标准库的稳定性：8年来Go的工具链几乎没有变化，新手容易上手。像Thorsten一样，我也发现Go的开发环境和工具在多年来保持了惊人的一致性。这意味着书中介绍的开发实践和工具使用方法始终有效，大大降低了内容过时的风险。对技术作者来说，这种稳定性是无价的，它允许我们专注于概念和最佳实践，而不是不断更新工具相关的内容。</p>
<p>以上Go的这些稳定性和向后兼容，让我的书中的内容具有了更为持久的生命力，书中内容的价值变得更为长效，也大大减轻了作者对书籍维护和更新的负担，在技术书籍的生命周期中，这一点尤为宝贵。</p>
<h2>2. Go的简洁性和可读性</h2>
<p>其次，在编写《<a href="https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master">Go语言精进之路</a>》时，我发现Go的简洁性和可读性为技术写作带来了极大的帮助。许多读者反馈说，即使他们之前没有Go的经验，也能快速上手并理解书中的概念。这种简洁和直观性让Go也成为了编写教程和教学材料的理想选择。此外，正如在项目中所经历的那样，Thorsten也强调了Go语言的语法简单直观在教学过程中的所展现的优势，它既能让初学者快速入门，也能使得书中关于解析器和编译器实现的核心思路能够被清晰地传达给读者，即便在探讨复杂的概念时，也能保持清晰明了。</p>
<p>同时，Thorsten强调内置的gofmt带来的通用风格和测试框架也简化了学习过程，让读者可以专注于理解核心概念和解释器/编译器的实现，而不是纠结于环境设置和代码风格。</p>
<h2>3. Go代码易于理解和翻译</h2>
<p>Thorsten提到许多读者在从未写过Go代码的前提下，能够将他的Go代码轻松翻译成其他语言，这体现了Go在跨语言学习和理解方面的优势，有利于<strong>扩大了书籍的受众群体</strong>，而不仅限于Go开发者。Go社区的多样性和活跃度也为此做出了重要贡献，各种语言背景的开发者都能在Go中找到共鸣。这种跨语言的适应性不仅拓展了书籍的应用范围，也增强了其教育价值。</p>
<h2>4. 小结</h2>
<p>回顾这三年，我与Thorsten一样，越发感慨选择Go作为教学语言进行技术写作是多么明智的决定。当然，我这本书本身就是围绕Go语言展开的^_^，这与Thorsten的书籍主题有所不同。Thorsten在8年前高瞻远瞩地选择Go，才着实令人钦佩，要知道那时的Go刚刚发布<a href="https://tonybai.com/2016/02/21/some-changes-in-go-1-6/">1.6版本</a>。Go语言不仅是一个强大的编程工具，更是技术作者的得力助手。它的稳定性、简洁性、易理解性和良好的翻译能力，以及稳定优秀的工具链，为我们创造高质量、长寿命的技术内容提供了坚实的基础。</p>
<p>与Thorsten Ball一样，我也为选择Go感到庆幸。看到自己的作品能够持续为读者提供价值，这种成就感是无可比拟的。Go语言在技术写作中展现出的优越性，不仅使我们的书籍能够经受时间的考验，还为整个技术写作领域树立了新的参考标杆。</p>
<p>展望未来，我相信Go语言将继续是技术作者的优秀选择。它不仅是一种编程语言，更是连接作者、读者与技术的桥梁。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/10/09/resonating-with-thorsten-ball-on-go-in-technical-writing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Golang测试技术</title>
		<link>https://tonybai.com/2014/10/22/golang-testing-techniques/</link>
		<comments>https://tonybai.com/2014/10/22/golang-testing-techniques/#comments</comments>
		<pubDate>Wed, 22 Oct 2014 07:57:21 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Unittest]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[构建]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[测试框架]]></category>
		<category><![CDATA[竞争检测]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1579</guid>
		<description><![CDATA[本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享&#8220;Testing Techniques&#8221;，即介绍使用Golang开发 时会使用到的测试技术（主要针对单元测试），包括基本技术、高级技术（并发测试、mock/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等）等，感觉总结的很全面，这里整理记录下来，希望能给大家带来帮助。原Slide访问需要自己搭梯子。另外这里也要吐槽一 下：Golang官方站的slide都是以一种特有的golang artical的格式放出的（用这个工具http://go-talks.appspot.com/可以在线观看），没法像pdf那样下载，在国内使用和传播极其不便。 一、基础测试技术 1、测试Go代码 Go语言内置测试框架。 内置的测试框架通过testing包以及go test命令来提供测试功能。 下面是一个完整的测试strings.Index函数的完整测试文件： //strings_test.go (这里样例代码放入strings_test.go文件中) package strings_test import ( &#160;&#160;&#160; &#34;strings&#34; &#160;&#160;&#160; &#34;testing&#34; ) func TestIndex(t *testing.T) { &#160;&#160;&#160; const s, sep, want = &#34;chicken&#34;, &#34;ken&#34;, 4 &#160;&#160;&#160; got := strings.Index(s, sep) &#160;&#160;&#160; if got != want { &#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Errorf(&#34;Index(%q,%q) = %v; want [...]]]></description>
			<content:encoded><![CDATA[<p>本篇文章内容来源于<a href="http://golang.org">Golang</a>核心开发组成员<a href="http://nf.wh3rd.net/">Andrew Gerrand</a>在Google I/O 2014的一次主题分享&ldquo;<a href="https://talks.golang.org/2014/testing.slide#1">Testing Techniques</a>&rdquo;，即介绍使用Golang开发 时会使用到的测试技术（主要针对<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/"><b>单元测试</b></a>），包括基本技术、高级技术（并发测试、<a href="http://tonybai.com/2010/10/29/lcut-add-mock-support/">mock</a>/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等）等，感觉总结的很全面，这里整理记录下来，希望能给大家带来帮助。原Slide访问需要自己搭梯子。另外这里也要吐槽一 下：Golang官方站的slide都是以一种特有的<a href="http://godoc.org/code.google.com/p/go.tools/present">golang artical</a>的格式放出的（用这个工具http://go-talks.appspot.com/可以在线观看），没法像pdf那样下载，在国内使用和传播极其不便。</p>
<p><b>一、基础测试技术</b></p>
<p><b>1、测试Go代码</b></p>
<p>Go语言内置测试框架。</p>
<p>内置的测试框架通过<font face="Courier New">testing</font>包以及<font face="Courier New">go test</font>命令来提供测试功能。</p>
<p>下面是一个完整的测试<font face="Courier New">strings.Index</font>函数的完整测试文件：</p>
<p><font face="Courier New">//strings_test.go (这里样例代码放入strings_test.go文件中)</font><br />
	<font face="Courier New">package strings_test</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;strings&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;testing&quot;<br />
	)</font></p>
<p><font face="Courier New">func TestIndex(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; const s, sep, want = &quot;chicken&quot;, &quot;ken&quot;, 4<br />
	&nbsp;&nbsp;&nbsp; got := strings.Index(s, sep)<br />
	&nbsp;&nbsp;&nbsp; if got != want {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Index(%q,%q) = %v; want %v&quot;, s, sep, got, want)//<u>注意原slide中</u><u>的got和want写反了</u><br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.007s</font></p>
<p>go test的-v选项是表示输出详细的执行信息。</p>
<p>将代码中的want常量值修改为3，我们制造一个无法通过的测试：</p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; FAIL: TestIndex (0.00 seconds)<br />
	&nbsp;&nbsp;&nbsp; strings_test.go:12: Index(&quot;chicken&quot;,&quot;ken&quot;) = 4; want 3<br />
	FAIL<br />
	exit status 1<br />
	FAIL&nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.008s</font></p>
<p><b>2、表驱动测试</b></p>
<p>Golang的struct字面值(struct literals)语法让我们可以轻松写出表驱动测试。</p>
<p><font face="Courier New">package strings_test</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;strings&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;testing&quot;<br />
	)</font></p>
<p><font face="Courier New">func TestIndex(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var tests = []struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s&nbsp;&nbsp; string<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sep string<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;&quot;, &quot;&quot;, 0},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;&quot;, &quot;a&quot;, -1},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;fo&quot;, &quot;foo&quot;, -1},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;foo&quot;, &quot;foo&quot;, 0},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;oofofoofooo&quot;, &quot;f&quot;, 2},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for _, test := range tests {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; actual := strings.Index(test.s, test.sep)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if actual != test.out {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Index(%q,%q) = %v; want %v&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test.s, test.sep, actual, test.out)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.007s</font></p>
<p><b>3、T结构</b></p>
<p><font face="Courier New">*testing.T</font>参数用于错误报告：</p>
<p><font face="Courier New">t.Errorf(&quot;got bar = %v, want %v&quot;, got, want)<br />
	t.Fatalf(&quot;Frobnicate(%v) returned error: %v&quot;, arg, err)<br />
	t.Logf(&quot;iteration %v&quot;, i)</font></p>
<p>也可以用于enable并行测试(parallet test)：<br />
	<font face="Courier New">t.Parallel()</font></p>
<p>控制一个测试是否运行：</p>
<p><font face="Courier New">if runtime.GOARCH == &quot;arm&quot; {<br />
	&nbsp;&nbsp;&nbsp; t.Skip(&quot;this doesn&#39;t work on ARM&quot;)<br />
	}</font></p>
<p><b>4、运行测试</b></p>
<p>我们用<font face="Courier New">go test</font>命令来运行特定包的测试。</p>
<p>默认执行当前路径下包的测试代码。</p>
<p><font face="Courier New">$ go test<br />
	PASS</font></p>
<p><font face="Courier New">$ go test -v<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS</font></p>
<p><font face="Courier New">要运行工程下的所有测试，我们执行如下命令：</font></p>
<p><font face="Courier New">$ go test github.com/nf/&#8230;</font></p>
<p><font face="Courier New">标准库的测试：<br />
	$ go test std</font></p>
<p>注：假设<font face="Courier New">strings_test.go</font>的当前目录为<font face="Courier New">testgo</font>，在testgo目录下执行<font face="Courier New">go test</font>都是OK的。但如果我们切换到testgo的上一级目录执行go test，我们会得到什么结果呢？</p>
<p><font face="Courier New">$go test testgo<br />
	can&#39;t load package: package testgo: cannot find package &quot;testgo&quot; in any of:<br />
	&nbsp;&nbsp;&nbsp; /usr/local/go/src/pkg/testgo (from $GOROOT)<br />
	&nbsp;&nbsp;&nbsp; /Users/tony/Test/GoToolsProjects/src/testgo (from $GOPATH)</font></p>
<p>提示找不到testgo这个包，go test后面接着的应该是一个包名，go test会在GOROOT和GOPATH下查找这个包并执行包的测试。</p>
<p><b>5、测试覆盖率</b></p>
<p>go tool命令可以报告测试覆盖率统计。</p>
<p>我们在testgo下执行go test -cover，结果如下：</p>
<p><font face="Courier New">go build _/Users/tony/Test/Go/testgo: no buildable Go source files in /Users/tony/Test/Go/testgo<br />
	FAIL&nbsp;&nbsp;&nbsp; _/Users/tony/Test/Go/testgo [build failed]</font></p>
<p>显然通过cover参数选项计算测试覆盖率不仅需要测试代码，还要有被测对象（一般是函数）的源码文件。</p>
<p>我们将目录切换到$GOROOT/src/pkg/strings下，执行<font face="Courier New">go test -cover</font>：</p>
<p><font face="Courier New">$go test -v -cover<br />
	=== RUN TestReader<br />
	&#8212; PASS: TestReader (0.00 seconds)<br />
	&#8230; &#8230;<br />
	=== RUN: ExampleTrimPrefix<br />
	&#8212; PASS: ExampleTrimPrefix (1.75us)<br />
	PASS<br />
	coverage: 96.9% of statements<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; strings&nbsp;&nbsp;&nbsp; 0.612s</font></p>
<p>go test可以生成覆盖率的profile文件，这个文件可以被go tool cover工具解析。</p>
<p>在$GOROOT/src/pkg/strings下面执行：</p>
<p><font face="Courier New">$ go test -coverprofile=cover.out</font></p>
<p>会再当前目录下生成cover.out文件。</p>
<p>查看cover.out文件，有两种方法：</p>
<p>a) <font face="Courier New">cover -func=cover.out</font></p>
<p><font face="Courier New">$sudo go tool cover -func=cover.out<br />
	strings/reader.go:24:&nbsp;&nbsp;&nbsp; Len&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 66.7%<br />
	strings/reader.go:31:&nbsp;&nbsp;&nbsp; Read&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:44:&nbsp;&nbsp;&nbsp; ReadAt&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:59:&nbsp;&nbsp;&nbsp; ReadByte&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:69:&nbsp;&nbsp;&nbsp; UnreadByte&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	&#8230; &#8230;<br />
	strings/strings.go:638:&nbsp;&nbsp;&nbsp; Replace&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/strings.go:674:&nbsp;&nbsp;&nbsp; EqualFold&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	total:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; (statements)&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 96.9%</font></p>
<p>b) 可视化查看</p>
<p>执行go tool cover -html=cover.out命令，会在/tmp目录下生成目录coverxxxxxxx，比如/tmp/cover404256298。目录下有一个 coverage.html文件。用浏览器打开coverage.html，即可以可视化的查看代码的测试覆盖情况。</p>
<pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: 'Droid Sans Mono', 'Courier New', monospace; font-size: 18px; line-height: 24px; letter-spacing: -1px; color: rgb(0, 0, 0);">&nbsp;</pre>
<p>关于go tool的cover命令，我的go version go1.3 darwin/amd64默认并不自带，需要通过go get下载。</p>
<p><font face="Courier New">$sudo GOPATH=/Users/tony/Test/GoToolsProjects go get code.google.com/p/go.tools/cmd/cover</font></p>
<p>下载后，cover安装在<font face="Courier New">$GOROOT/pkg/tool/darwin_amd64</font>下面。</p>
<p><b>二、高级测试技术</b></p>
<p><b>1、一个例子程序</b></p>
<p>outyet是一个web服务，用于宣告某个特定Go版本是否已经打标签发布了。其获取方法：</p>
<p><font face="Courier New">go get github.com/golang/example/outyet</font></p>
<p>注：<br />
	go get执行后，<font face="Courier New">cd $GOPATH/src/github.com/golang/example/outyet</font>下，执行<font face="Courier New">go run main.go</font>。然后用浏览器打开<font face="Courier New">http://localhost:8080</font>即可访问该Web服务了。</p>
<p><b>2、测试Http客户端和服务端</b></p>
<p>net/http/httptest包提供了许多帮助函数，用于测试那些发送或处理Http请求的代码。</p>
<p><b>3、httptest.Server</b></p>
<p>httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。</p>
<p><font face="Courier New">type Server struct {<br />
	&nbsp;&nbsp;&nbsp; URL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string // base URL of form http://ipaddr:port with no trailing slash<br />
	&nbsp;&nbsp;&nbsp; Listener net.Listener</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // TLS is the optional TLS configuration, populated with a new config<br />
	&nbsp;&nbsp;&nbsp; // after TLS is started. If set on an unstarted server before StartTLS<br />
	&nbsp;&nbsp;&nbsp; // is called, existing fields are copied into the new config.<br />
	&nbsp;&nbsp;&nbsp; TLS *tls.Config</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Config may be changed after calling NewUnstartedServer and<br />
	&nbsp;&nbsp;&nbsp; // before Start or StartTLS.<br />
	&nbsp;&nbsp;&nbsp; Config *http.Server<br />
	}</font></p>
<p><font face="Courier New">func NewServer(handler http.Handler) *Server</font></p>
<p><font face="Courier New">func (*Server) Close() error</font></p>
<p><b>4、httptest.Server实战</b></p>
<p>下面代码创建了一个临时Http Server，返回简单的Hello应答：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Fprintln(w, &quot;Hello, client&quot;)<br />
	&nbsp;&nbsp;&nbsp; }))<br />
	&nbsp;&nbsp;&nbsp; defer ts.Close()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; res, err := http.Get(ts.URL)<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; greeting, err := ioutil.ReadAll(res.Body)<br />
	&nbsp;&nbsp;&nbsp; res.Body.Close()<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s&quot;, greeting)</font></p>
<p><b>5、httptest.ResponseRecorder</b></p>
<p>httptest.ResponseRecorder是http.ResponseWriter的一个实现，用来记录变化，用在测试的后续检视中。</p>
<p><font face="Courier New">type ResponseRecorder struct {<br />
	&nbsp;&nbsp;&nbsp; Code&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // the HTTP response code from WriteHeader<br />
	&nbsp;&nbsp;&nbsp; HeaderMap http.Header&nbsp;&nbsp; // the HTTP response headers<br />
	&nbsp;&nbsp;&nbsp; Body&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to<br />
	&nbsp;&nbsp;&nbsp; Flushed&nbsp;&nbsp; bool<br />
	}</font></p>
<p><b>6、httptest.ResponseRecorder实战</b></p>
<p>向一个HTTP handler中传入一个ResponseRecorder，通过它我们可以来检视生成的应答。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; handler := func(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;something failed&quot;, http.StatusInternalServerError)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; req, err := http.NewRequest(&quot;GET&quot;, &quot;http://example.com/foo&quot;, nil)<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; w := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp; handler(w, req)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d &#8211; %s&quot;, w.Code, w.Body.String())</font></p>
<p><b>7、竞争检测(race detection)</b></p>
<p>当两个goroutine并发访问同一个变量，且至少一个goroutine对变量进行写操作时，就会发生数据竞争（data race）。</p>
<p>为了协助诊断这种bug，Go提供了一个内置的数据竞争检测工具。</p>
<p>通过传入-race选项，go tool就可以启动竞争检测。</p>
<p><font face="Courier New">$ go test -race mypkg&nbsp;&nbsp;&nbsp; // to test the package<br />
	$ go run -race mysrc.go&nbsp; // to run the source file<br />
	$ go build -race mycmd&nbsp;&nbsp; // to build the command<br />
	$ go install -race mypkg // to install the package</font></p>
<p>注：一个数据竞争检测的例子</p>
<p>例子代码：</p>
<p><font face="Courier New">//testrace.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var i int = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;subroutine: i = &quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(1 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;mainroutine: i = &quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(1 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go run -race testrace.go<br />
	mainroutine: i =&nbsp; 1<br />
	==================<br />
	<b>WARNING: DATA RACE</b><br />
	Read by goroutine 5:<br />
	&nbsp; main.func&middot;001()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:10 +0&#215;49</font></p>
<p><font face="Courier New">Previous write by main goroutine:<br />
	&nbsp; main.main()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:17 +0xd5</font></p>
<p><font face="Courier New">Goroutine 5 (running) created at:<br />
	&nbsp; main.main()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:14 +0xaf<br />
	==================<br />
	subroutine: i =&nbsp; 2<br />
	mainroutine: i =&nbsp; 3<br />
	subroutine: i =&nbsp; 4<br />
	mainroutine: i =&nbsp; 5<br />
	subroutine: i =&nbsp; 6<br />
	mainroutine: i =&nbsp; 7<br />
	subroutine: i =&nbsp; 8</font></p>
<p><b>8、测试并发</b><b>（testing with concurrency)</b></p>
<p>当测试并发代码时，总会有一种使用sleep的冲动。大多时间里，使用sleep既简单又有效。</p>
<p>但大多数时间不是&rdquo;总是&ldquo;。</p>
<p>我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。</p>
<p><b>9、</b><b>使用静态分析工具vet查找错误</b></p>
<p>vet工具用于检测代码中程序员犯的常见错误：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误的printf格式<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误的构建tag<br />
	&nbsp;&nbsp;&nbsp; &#8211; 在闭包中使用错误的range循环变量<br />
	&nbsp;&nbsp;&nbsp; &#8211; 无用的赋值操作<br />
	&nbsp;&nbsp;&nbsp; &#8211; 无法到达的代码<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误使用mutex<br />
	&nbsp;&nbsp;&nbsp; 等等。</p>
<p>使用方法：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; go vet [package]</font></p>
<p><b>10、</b><b>从内部测试</b></p>
<p>golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。</p>
<p>注：比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。</p>
<p><b>11、从外部测试</b></p>
<p>有些时候，你需要从被测包的外部对被测包进行测试，比如测试代码在package foo_test下，而不是在package foo下。</p>
<p>这样可以打破依赖循环，比如：</p>
<p>&nbsp;&nbsp;&nbsp; &#8211; testing包使用fmt<br />
	&nbsp;&nbsp;&nbsp; &#8211; fmt包的测试代码还必须导入testing包<br />
	&nbsp;&nbsp;&nbsp; &#8211; 于是，fmt包的测试代码放在fmt_test包下，这样既可以导入testing包，也可以同时导入fmt包。</p>
<p><b>12、Mocks和fakes</b></p>
<p>通过在代码中使用interface，Go可以避免使用mock和fake测试机制。</p>
<p>例如，如果你正在编写一个文件格式解析器，不要这样设计函数：</p>
<p><font face="Courier New">func Parser(f *os.File) error</font></p>
<p>作为替代，你可以编写一个接受interface类型的函数:</p>
<p><font face="Courier New">func Parser(r io.Reader) error</font></p>
<p>和<font face="Courier New">bytes.Buffer、strings.Reader</font>一样，*os.File也实现了<font face="Courier New">io.Reader</font>接口。</p>
<p><b>13、子进程测试</b></p>
<p>有些时候，你需要测试的是一个进程的行为，而不仅仅是一个函数。例如：</p>
<p><font face="Courier New">func Crasher() {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Going down in flames!&quot;)<br />
	&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	}</font></p>
<p>为了测试上面的代码，我们将测试程序本身作为一个子进程进行测试：</p>
<p><font face="Courier New">func TestCrasher(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; if os.Getenv(&quot;BE_CRASHER&quot;) == &quot;1&quot; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Crasher()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; cmd := exec.Command(os.Args[0], &quot;-test.run=TestCrasher&quot;)<br />
	&nbsp;&nbsp;&nbsp; cmd.Env = append(os.Environ(), &quot;BE_CRASHER=1&quot;)<br />
	&nbsp;&nbsp;&nbsp; err := cmd.Run()<br />
	&nbsp;&nbsp;&nbsp; if e, ok := err.(*exec.ExitError); ok &amp;&amp; !e.Success() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; t.Fatalf(&quot;process ran with err %v, want exit status 1&quot;, err)<br />
	}</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/22/golang-testing-techniques/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>
