这些团队更依赖强大的单元测试和集成测试,而非端到端测试。通过采用测试驱动开发、结对编程以及良好的设计实践,他们能够频繁地发布小型功能变更,在生产环境中进行实际测试以获取反馈,并利用功能开关机制来降低风险。Ola Hast和Asgaut Mjølne Söderbom在QCon London上关于“利用结对编程实现持续交付”的演讲中就提到了这一点。
Hast指出,他们既信任单独的单元测试和集成测试,也相信这些测试共同组成的整体效果。他们并没有进行端到端测试:
我们通过良好的职责分离、模块化设计、抽象层次、低耦合性以及高内聚性来实现这一目标。这些机制与测试驱动开发和结对编程相辅相成,最终使得代码质量得到显著提升。
之前,他们会进行许多针对整个应用程序的HTTP集成测试,但后来改用了关注度更高的测试方法,这样反馈周期也会变得更短,Hast提到。
Mjølne Söderbom进一步解释说,由于测试环境始终只是生产环境的近似模拟环境,而且往往存在供应链过长或测试数据不准确等问题,因此他们基本上已经不再完全依赖这些测试环境了:
我们更倾向于在生产环境中进行测试,因为只有在那里才能获得最准确的反馈。
他们通过将新功能设置为可开关状态,并分阶段部署小规模代码来降低风险。Mjølne Söderbom表示,这种方法已经实施了好几年,效果非常好。如果生产环境中出现了问题,也很容易找到原因并进行修复,同时还可以轻松回滚或恢复到之前的状态。
在之前的一篇文章中,Hast和Mjølne Söderbom提到,他们的团队会结合测试驱动开发、结对编程以及群体编程来开展工作;团队内部不存在单独完成任务的情况,也不会进行独立的代码审查。这种做法能够有效提升代码质量、减少资源浪费,并促进知识共享:
经过多年的实践,我们最终形成了这样的工作模式:先一起进行测试驱动开发,然后再将代码部署到生产环境中。我们很少会在本地环境或测试环境中测试整个应用程序。这其实并不是我们的初衷,只是这种工作方式带来的自然结果。
结对编程与持续集成可以相辅相成。如果单独进行多次代码提交,很容易导致延迟、大型合并请求以及合并冲突等问题。而通过结对编程,可以即时进行代码审查,更方便地进行代码重构,从而减少错误的发生,并提升团队的整体效率,Hast和Mjølne Söderbom这样解释道。
Hast还提到,他们之前会进行很多针对整个应用程序运行过程的测试,但现在这些测试已经减少到了最低限度。通常他们只会进行一些基本功能测试,而在遇到特殊错误情况时才会添加额外的测试用例。
那些无法通过单元测试来验证的内容,我们更倾向于在生产环境中进行测试。测试结果始终只是对现实情况的近似模拟,而长供应链以及不准确的测试数据也一直是我们面临的问题。因此,我们从生产环境中获得的反馈才是最可靠的。停止使用测试环境从来都不是我们的目标,但这确实带来了另一个意想不到的积极效果。
最重要的是要建立起有效的反馈机制,Mjölne Söderbom指出。正是这些反馈帮助我们确定发展方向,并在必要时及时调整策略:
我们从测试中获得的反馈速度最快,而从生产环境中获得的反馈则最为准确。
Hast进一步解释道:当某件事会带来麻烦时,人们就会更频繁地去做它。
我们公司能够发展到今天的地步,主要是因为我们加快了将代码部署到生产环境中的频率,并且能够迅速发现那些最容易引发问题的环节,然后及时加以修复。这个过程已经持续了10年之久。
Mjölne Söderbom强调,在开发的各个阶段建立快速的反馈机制都是非常重要的。他解释说,TDD正是我们获取早期、快速反馈的重要工具之一:
如果某段代码难以进行测试,那通常意味着它的设计存在问题。这种测试结果会反过来引导我们的设计工作。
很多人认为TDD只是一种测试工具,但实际上它更像是一种设计工具。能够确保开发流程顺畅进行的合理测试机制,其实才是TDD带来的真正好处,Mjölne Söderbom总结道。